Radius/add network structure

This commit is contained in:
Luca Haid
2025-12-09 05:34:24 +00:00
parent 60556e5d63
commit 167b038c20
37 changed files with 6833 additions and 2920 deletions
+139 -28
View File
@@ -1,48 +1,159 @@
/* ===== RadiusFreeUsers.js ===== */
Vue.component('radius-free-users', {
/* ===== RadiusFreeUsers.js (Vue 3 + TT-Core) ===== */
const RadiusFreeUsers = {
name: 'RadiusFreeUsers',
template: `
<div class="radius-scope">
<div class="grid cols-1 cols-2-xl">
<div class="subcard" style="border-right: 1px solid var(--border); padding-right: 12px;">
<div class="tt-scope">
<div class="free-users-grid">
<div class="free-users-column">
<div class="h5" style="display:flex;align-items:center;justify-content:space-between;gap:10px;">
<span><i class="fa-duotone fa-shield-keyhole"></i> Freie NAT Benutzer <span class="badge">{{ filteredNat.length }}</span></span>
<button class="ghost-btn" @click="reloadNat" :disabled="loadingNat"><span v-if="!loadingNat"><i class="fa-duotone fa-rotate-right"></i> Neu laden</span><span v-else class="btn-loader"></span></button>
<button class="ghost-btn" @click="reloadNat" :disabled="loadingNat">
<span v-if="!loadingNat"><i class="fa-duotone fa-rotate-right"></i> Neu laden</span>
<span v-else class="btn-loader"></span>
</button>
</div>
<radius-table-view :items="filteredNat" :is-loading="loadingNat" :has-searched="true" density="ultra-compact" table-class="no-min-width" no-results-placeholder-text="Keine Treffer" :skeleton-row-count="8">
<template #head><thead><tr><th>Username</th><th>Info</th></tr></thead></template>
<template #skeleton-row><td colspan="2"><div class="skeleton-line"></div></td></template>
<tt-data-table
:items="filteredNat"
:is-loading="loadingNat"
:has-searched="true"
density="ultra-compact"
table-class="no-min-width"
no-results-placeholder-text="Keine Treffer"
:skeleton-row-count="8"
>
<template #head>
<thead>
<tr>
<th>Username</th>
<th>Info</th>
</tr>
</thead>
</template>
<template #skeleton-row>
<td colspan="2"><tt-skeleton /></td>
</template>
<template #row="{ item }">
<td><a class="link" target="_blank" :href="'http://radius.xinon.at/edit_user.php?user=' + item.Username" data-tooltip="User in Radius öffnen" data-tooltip-align="right">{{ item.Username }}</a></td>
<td>
<a class="link" target="_blank"
:href="'http://radius.xinon.at/edit_user.php?user=' + item.Username"
data-tooltip="User in Radius öffnen"
data-tooltip-align="right">{{ item.Username }}</a>
</td>
<td class="clamp-2 mono">{{ item.Info }}</td>
</template>
</radius-table-view>
<div v-if="!loadingNat && filteredNat.length" class="results-summary">{{ filteredNat.length }} Treffer gefunden</div>
</tt-data-table>
<div v-if="!loadingNat && filteredNat.length" class="results-summary">
{{ filteredNat.length }} Treffer gefunden
</div>
</div>
<div class="subcard" style="padding-left: 12px;">
<div class="free-users-column">
<div class="h5" style="display:flex;align-items:center;justify-content:space-between;gap:10px;">
<span><i class="fa-duotone fa-id-card-clip"></i> Freie STF Benutzer <span class="badge">{{ filteredStf.length }}</span></span>
<button class="ghost-btn" @click="reloadStf" :disabled="loadingStf"><span v-if="!loadingStf"><i class="fa-duotone fa-rotate-right"></i> Neu laden</span><span v-else class="btn-loader"></span></button>
<button class="ghost-btn" @click="reloadStf" :disabled="loadingStf">
<span v-if="!loadingStf"><i class="fa-duotone fa-rotate-right"></i> Neu laden</span>
<span v-else class="btn-loader"></span>
</button>
</div>
<radius-table-view :items="filteredStf" :is-loading="loadingStf" :has-searched="true" density="ultra-compact" table-class="no-min-width" no-results-placeholder-text="Keine Treffer" :skeleton-row-count="8">
<template #head><thead><tr><th>Username</th><th>Info</th></tr></thead></template>
<template #skeleton-row><td colspan="2"><div class="skeleton-line"></div></td></template>
<tt-data-table
:items="filteredStf"
:is-loading="loadingStf"
:has-searched="true"
density="ultra-compact"
table-class="no-min-width"
no-results-placeholder-text="Keine Treffer"
:skeleton-row-count="8"
>
<template #head>
<thead>
<tr>
<th>Username</th>
<th>Info</th>
</tr>
</thead>
</template>
<template #skeleton-row>
<td colspan="2"><tt-skeleton /></td>
</template>
<template #row="{ item }">
<td><a class="link" target="_blank" :href="'http://radius.xinon.at/edit_user.php?user=' + item.Username" data-tooltip="User in Radius öffnen" data-tooltip-align="right">{{ item.Username }}</a></td>
<td>
<a class="link" target="_blank"
:href="'http://radius.xinon.at/edit_user.php?user=' + item.Username"
data-tooltip="User in Radius öffnen"
data-tooltip-align="right">{{ item.Username }}</a>
</td>
<td class="clamp-2 mono">{{ item.Info }}</td>
</template>
</radius-table-view>
<div v-if="!loadingStf && filteredStf.length" class="results-summary">{{ filteredStf.length }} Treffer gefunden</div>
</tt-data-table>
<div v-if="!loadingStf && filteredStf.length" class="results-summary">
{{ filteredStf.length }} Treffer gefunden
</div>
</div>
</div>
</div>
`,
data: () => ({ nat: [], stf: [], loadingNat: false, loadingStf: false, _initialized: false }),
computed: { filteredNat() { return this.nat.filter(this.isTrulyFree); }, filteredStf() { return this.stf.filter(this.isTrulyFree); } },
data: () => ({
nat: [],
stf: [],
loadingNat: false,
loadingStf: false,
_initialized: false
}),
computed: {
filteredNat() {
return this.nat.filter(this.isTrulyFree);
},
filteredStf() {
return this.stf.filter(this.isTrulyFree);
}
},
methods: {
initIfNeeded(){ if (this._initialized) return; this._initialized = true; this.reloadNat(); this.reloadStf(); },
isTrulyFree(user) { return !/frei[a-z]/.test((user.Info || '').toLowerCase()); },
normalizeUsers(arr){ if (!Array.isArray(arr)) return []; return arr.map(u => ({ Username: (u.Username || u.username || '').trim(), Info: (u.Info || u.info || '').toString().replace(/\s+$/,'') })).filter(u => u.Username); },
async reloadNat() { this.nat = []; this.loadingNat = true; try { const r = await fetch(window.TT_CONFIG.BASE_PATH + '/Radius/proxyUnsecureHTTPRequestToRadius?action2=free_user&filter=nat'); this.nat = this.normalizeUsers(r.ok ? (await r.json()).users : []); } catch { this.nat = []; } this.loadingNat = false; },
async reloadStf() { this.stf = []; this.loadingStf = true; try { const r = await fetch(window.TT_CONFIG.BASE_PATH + '/Radius/proxyUnsecureHTTPRequestToRadius?action2=free_user&filter=stf'); this.stf = this.normalizeUsers(r.ok ? (await r.json()).users : []); } catch { this.stf = []; } this.loadingStf = false; }
initIfNeeded() {
if (this._initialized) return;
this._initialized = true;
this.reloadNat();
this.reloadStf();
},
isTrulyFree(user) {
return !/frei[a-z]/.test((user.Info || '').toLowerCase());
},
normalizeUsers(arr) {
if (!Array.isArray(arr)) return [];
return arr.map(u => ({
Username: (u.Username || u.username || '').trim(),
Info: (u.Info || u.info || '').toString().replace(/\s+$/, '')
})).filter(u => u.Username);
},
async reloadNat() {
this.nat = [];
this.loadingNat = true;
try {
const { data } = await axios.get(`${window.TT_CONFIG.BASE_PATH}/Radius/proxyUnsecureHTTPRequestToRadius`, {
params: { action2: 'free_user', filter: 'nat' }
});
this.nat = this.normalizeUsers(data?.users || []);
} catch (error) {
this.nat = [];
}
this.loadingNat = false;
},
async reloadStf() {
this.stf = [];
this.loadingStf = true;
try {
const { data } = await axios.get(`${window.TT_CONFIG.BASE_PATH}/Radius/proxyUnsecureHTTPRequestToRadius`, {
params: { action2: 'free_user', filter: 'stf' }
});
this.stf = this.normalizeUsers(data?.users || []);
} catch (error) {
this.stf = [];
}
this.loadingStf = false;
}
}
});
};
// Register component with Vue 3 app
if (window.VueApp) {
window.VueApp.component('radius-free-users', RadiusFreeUsers);
}