diff --git a/Layout/default/menu.php b/Layout/default/menu.php
index fd2461812..d7f8e5333 100644
--- a/Layout/default/menu.php
+++ b/Layout/default/menu.php
@@ -134,6 +134,7 @@
isAdmin()) : ?>
"> IPAM
isAdmin()) : ?>"> Device Congestion
isAdmin()) : ?>"> Wartungsmeldungen
+ isAdmin()) : ?>"> Radius
diff --git a/application/Radius/RadiusController.php b/application/Radius/RadiusController.php
new file mode 100644
index 000000000..cd4a893d2
--- /dev/null
+++ b/application/Radius/RadiusController.php
@@ -0,0 +1,22 @@
+needlogin=true;
+ $me = new User();
+ $me->loadMe();
+ $this->layout()->set("me", $me);
+ $this->me = $me;
+
+ if (!$this->me->is("Admin")) $this->redirect("Dashboard");
+ }
+
+ protected function indexAction() {
+ $this->layout()->set('additionalJS', ["plugins/chart.js/chart.4.4.6.js", "plugins/chart.js/chartjs-adapter-moment.min.js"]);
+ Helper::renderVue($this, $this->mod, "Radius", []);
+ }
+
+
+}
\ No newline at end of file
diff --git a/public/js/pages/Radius/Radius.css b/public/js/pages/Radius/Radius.css
new file mode 100644
index 000000000..d219763fd
--- /dev/null
+++ b/public/js/pages/Radius/Radius.css
@@ -0,0 +1,55 @@
+.radius-view-selector {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(200px, max-content));
+ grid-gap: 10px;
+ justify-content: start;
+ margin-bottom: 20px;
+}
+
+@media (max-width: 576px) {
+ .radius-view-selector {
+ grid-template-columns: 1fr;
+ }
+}
+
+.filters {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+ grid-gap: 15px;
+ margin-bottom: 20px;
+ justify-content: center;
+ align-items: center;
+}
+
+.status-dot {
+ display: inline-block;
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+}
+
+.status-dot.online {
+ background-color: green;
+}
+
+.status-dot.offline {
+ background-color: grey;
+}
+
+.free-users-container {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ grid-gap: 20px;
+}
+
+@media (max-width: 768px) {
+ .free-users-container {
+ grid-template-columns: 1fr;
+ }
+}
+
+.free-users-column {
+ background-color: #f8f9fa;
+ padding: 15px;
+ border-radius: 5px;
+}
diff --git a/public/js/pages/Radius/Radius.js b/public/js/pages/Radius/Radius.js
new file mode 100644
index 000000000..c030bf534
--- /dev/null
+++ b/public/js/pages/Radius/Radius.js
@@ -0,0 +1,229 @@
+Vue.component('radius', {
+ template: `
+
+
+ Radius
+
+
+
+
+
+
+
+
+
+
+
+
Freie NAT Benutzer ({{ freeNatUsers.length }})
+
+
+
+ | Username |
+ Info |
+
+
+
+
+ | {{ user.username }} |
+ {{ user.info }} |
+
+
+
+
+
+
Freie STF Benutzer ({{ freeStfUsers.length }})
+
+
+
+ | Username |
+ Info |
+
+
+
+
+ | {{ user.username }} |
+ {{ user.info }} |
+
+
+
+
+
+
+
+
+
+
+
+ | Status: |
+ {{ radacctData.online ? 'Online' : 'Offline' }} |
+
+
+ | IP: |
+ {{ radacctData.ip }} |
+
+
+ | Username: |
+ {{ radacctData.username }} |
+
+
+ | Customer Number: |
+ {{ radacctData.customerNumber }} |
+
+
+ | Customer Name: |
+ {{ radacctData.customerName }} |
+
+
+ | Info: |
+ {{ radacctData.info }} |
+
+
+ | WLAN Password: |
+ {{ radacctData.wlanPassword }} |
+
+
+ | Bandbreite: |
+ {{ radacctData.actualBandwidth }} |
+
+
+
+
+
+ `,
+ data() {
+ return {
+ view: 'radius',
+ billAddrAutoCompleteUrl: window.TT_CONFIG['BASE_PATH'] + '/Address/Api?do=findAddress&fibu_primary_account=1',
+ radiusUsers: [],
+ freeNatUsers: [],
+ freeStfUsers: [],
+ username: '',
+ info: '',
+ custnum: '',
+ window: window,
+ showRadacctModal: false,
+ radacctData: null,
+ }
+ },
+ async mounted() {
+ await this.loadFreeUsers();
+ },
+ methods: {
+ hideRadacctModal() {
+ console.log('hideRadacctModal');
+ this.showRadacctModal = false;
+ },
+ async loadRadiusUsers() {
+ let custnum = '';
+ if (this.$refs.billAddr.displayValue.length > 5) {
+ custnum = this.$refs.billAddr.displayValue.match(/\[(\d+)\]/)[1];
+ }
+
+ const params = new URLSearchParams({
+ username: this.username,
+ info: this.info,
+ custnum: custnum,
+ });
+ const response = await fetch(`http://radius.xinon.at/api.php?${params.toString()}`, {
+ headers: {
+ 'Authorization': 'Basic ' + btoa('admin:saveman')
+ }
+ });
+ if (response.ok) {
+ this.radiusUsers = await response.json();
+ } else {
+ console.error('Failed to load radius users');
+ }
+ },
+ async fetchRadacctData(username) {
+ const params = new URLSearchParams({
+ action: 'fetchRadacct',
+ username: username,
+ });
+ const response = await fetch(`http://radius.xinon.at/api.php?${params.toString()}`, {
+ headers: {
+ 'Authorization': 'Basic ' + btoa('admin:saveman')
+ }
+ });
+ if (response.ok) {
+ this.radacctData = await response.json();
+ this.showRadacctModal = true;
+ } else {
+ console.error('Failed to fetch radacct data');
+ }
+ },
+ async loadFreeUsers() {
+ try {
+ const natResponse = await fetch('http://radius.xinon.at/api.php?action=free_user&filter=nat', {
+ headers: {
+ 'Authorization': 'Basic ' + btoa('admin:saveman')
+ }
+ });
+ const stfResponse = await fetch('http://radius.xinon.at/api.php?action=free_user&filter=stf', {
+ headers: {
+ 'Authorization': 'Basic ' + btoa('admin:saveman')
+ }
+ });
+
+ if (natResponse.ok && stfResponse.ok) {
+ const natData = await natResponse.json();
+ const stfData = await stfResponse.json();
+
+ this.freeNatUsers = natData.users;
+ this.freeStfUsers = stfData.users;
+ } else {
+ console.error('Failed to load free users');
+ }
+ } catch (error) {
+ console.error('Error loading free users:', error);
+ }
+
+ },
+ },
+});