added new radius module

This commit is contained in:
Luca Haid
2025-01-14 08:52:50 +01:00
parent e74a03e1a1
commit 35e5040155
4 changed files with 307 additions and 0 deletions

View File

@@ -134,6 +134,7 @@
<?php if($me->isAdmin()) : ?><li><a href="<?=self::getUrl("IpNetwork")?>"><i class="fa-solid fa-network-wired text-info"></i> IPAM</a></li><?php endif; ?>
<?php if($me->isAdmin()) : ?><li><a href="<?=self::getUrl("DeviceMonitoring/congestion")?>"><i class="fa-solid fa-block-brick-fire text-info"></i> Device Congestion</a></li><?php endif; ?>
<?php if($me->isAdmin()) : ?><li><a href="<?=self::getUrl("MaintenanceNotification")?>"><i class="fa-solid fa-envelope-badge text-info"></i> Wartungsmeldungen</a></li><?php endif; ?>
<?php if($me->isAdmin()) : ?><li><a href="<?=self::getUrl("Radius")?>"><i class="fas fa-broadcast-tower text-info"></i> Radius</a></li><?php endif; ?>
</ul>
</li>
<?php endif; ?>

View File

@@ -0,0 +1,22 @@
<?php
class RadiusController extends mfBaseController {
private User $me;
protected function init(): void {
$this->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", []);
}
}

View File

@@ -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;
}

View File

@@ -0,0 +1,229 @@
Vue.component('radius', {
template: `
<tt-card>
<template v-slot:header>
<h3>Radius</h3>
</template>
<div class="radius-view-selector">
<tt-button
icon="fas fa-user"
additional-class="btn-primary"
text="Radius Benutzer"
@click="view = 'radius'"
></tt-button>
<tt-button
icon="fas fa-user-plus"
additional-class="btn-primary"
text="Freie Radius Benutzer"
@click="view = 'free'"
></tt-button>
</div>
<div v-if="view === 'radius'">
<div class="filters">
<tt-autocomplete sm :api-url="billAddrAutoCompleteUrl" label="Rechnungsadresse" v-model="custnum" ref="billAddr"/>
<tt-input sm label="Username" name="username" v-model="username"/>
<tt-input sm label="Info" name="info" v-model="info"/>
<tt-button sm icon="fas fa-search" text="Suchen" additional-class="btn-primary" style="justify-self:center" @click="loadRadiusUsers"/>
</div>
<table class="table">
<thead>
<tr>
<th>Kundennummer</th>
<th>Username</th>
<th>Info</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
<tr v-for="user in radiusUsers" :key="user.id">
<td>
<a target="_blank" :href="window['TT_CONFIG']['BASE_PATH'] + '/Address?filter%5Bcustomer_number%5D=' + user.customerNumber">
{{ user.customerNumber }}
</a>
</td>
<td>
<a target="_blank" :href="'http://radius.xinon.at/edit_user.php?user=' + user.username">
{{ user.username }}
</a>
</td>
<td>{{ user.info }}</td>
<td>
<tt-button sm icon="fas fa-sync" text="Fetch" @click="fetchRadacctData(user.username)" additional-class="btn-primary"/>
</td>
</tr>
</tbody>
</table>
</div>
<div v-if="view === 'free'" class="free-users-container">
<div class="free-users-column">
<h4>Freie NAT Benutzer ({{ freeNatUsers.length }})</h4>
<table class="table">
<thead>
<tr>
<th>Username</th>
<th>Info</th>
</tr>
</thead>
<tbody>
<tr v-for="user in freeNatUsers" :key="user.id">
<td>{{ user.username }}</td>
<td>{{ user.info }}</td>
</tr>
</tbody>
</table>
</div>
<div class="free-users-column">
<h4>Freie STF Benutzer ({{ freeStfUsers.length }})</h4>
<table class="table">
<thead>
<tr>
<th>Username</th>
<th>Info</th>
</tr>
</thead>
<tbody>
<tr v-for="user in freeStfUsers" :key="user.id">
<td>{{ user.username }}</td>
<td>{{ user.info }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<tt-modal :show.sync="showRadacctModal" title="Radacct Data" :save="false" :delete="false">
<div v-if="radacctData">
<table class="table">
<tr>
<th>Status:</th>
<td><span :class="['status-dot', radacctData.online ? 'online' : 'offline']"></span> {{ radacctData.online ? 'Online' : 'Offline' }}</td>
</tr>
<tr>
<th>IP:</th>
<td>{{ radacctData.ip }}</td>
</tr>
<tr>
<th>Username:</th>
<td><a target="_blank" :href="'http://radius.xinon.at/edit_user.php?user=' + radacctData.username">{{ radacctData.username }}</a></td>
</tr>
<tr>
<th>Customer Number:</th>
<td>{{ radacctData.customerNumber }}</td>
</tr>
<tr>
<th>Customer Name:</th>
<td>{{ radacctData.customerName }}</td>
</tr>
<tr>
<th>Info:</th>
<td>{{ radacctData.info }}</td>
</tr>
<tr>
<th>WLAN Password:</th>
<td>{{ radacctData.wlanPassword }}</td>
</tr>
<tr>
<th>Bandbreite:</th>
<td>{{ radacctData.actualBandwidth }}</td>
</tr>
</table>
</div>
</tt-modal>
</tt-card>
`,
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);
}
},
},
});