Radius/add network structure
This commit is contained in:
@@ -1,15 +1,35 @@
|
||||
/* ===== RadiusUnused.js ===== */
|
||||
Vue.component('radius-unused-users', {
|
||||
/* ===== RadiusUnused.js (Vue 3 + TT-Core) ===== */
|
||||
|
||||
const RadiusUnusedUsers = {
|
||||
name: 'RadiusUnusedUsers',
|
||||
template: `
|
||||
<div class="radius-scope">
|
||||
<div class="tt-scope">
|
||||
<div style="display:flex; align-items:center; justify-content:space-between; gap:16px; flex-wrap:wrap; margin-bottom: 12px;">
|
||||
<div class="cluster">
|
||||
<button v-for="f in filters" :key="f.id" class="tab-btn" :class="{active: activeFilter === f.id}" @click="setFilter(f.id)" :disabled="isLoading || !users.length"><i :class="f.icon"></i> {{f.name}}</button>
|
||||
<button
|
||||
v-for="f in filters"
|
||||
:key="f.id"
|
||||
class="tab-btn"
|
||||
:class="{active: activeFilter === f.id}"
|
||||
@click="setFilter(f.id)"
|
||||
:disabled="isLoading || !users.length">
|
||||
<i :class="f.icon"></i> {{f.name}}
|
||||
</button>
|
||||
</div>
|
||||
<button class="ghost-btn" @click="fetchUnusedUsers" :disabled="isLoading" style="min-width: 120px;"><span v-if="!isLoading"><i class="fa-duotone fa-rotate-right"></i> Neu laden</span><span v-else class="btn-loader"></span></button>
|
||||
<button class="ghost-btn" @click="fetchUnusedUsers" :disabled="isLoading" style="min-width: 120px;">
|
||||
<span v-if="!isLoading"><i class="fa-duotone fa-rotate-right"></i> Neu laden</span>
|
||||
<span v-else class="btn-loader"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="results-container">
|
||||
<radius-table-view :items="visibleFilteredUsers" :is-loading="isLoading" :has-searched="hasSearched" initial-placeholder-icon="fa-duotone fa-play-circle" initial-placeholder-text="Klicken Sie auf 'Neu laden', um nach inaktiven Benutzern zu suchen." no-results-placeholder-text="Keine Treffer für den aktuellen Filter gefunden.">
|
||||
<tt-data-table
|
||||
:items="visibleFilteredUsers"
|
||||
:is-loading="isLoading"
|
||||
:has-searched="hasSearched"
|
||||
initial-placeholder-icon="fa-duotone fa-play-circle"
|
||||
initial-placeholder-text="Klicken Sie auf 'Neu laden', um nach inaktiven Benutzern zu suchen."
|
||||
no-results-placeholder-text="Keine Treffer für den aktuellen Filter gefunden."
|
||||
>
|
||||
<template #loading-placeholder>
|
||||
<div class="table-placeholder">
|
||||
<i class="fa-duotone fa-hourglass-half animated-hourglass" style="font-size: 36px; margin-bottom: 10px; color: var(--brand-blue);"></i>
|
||||
@@ -17,31 +37,129 @@ Vue.component('radius-unused-users', {
|
||||
<div class="muted small">Dies kann einen Moment dauern, da große Datenmengen analysiert werden.</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #head><thead><tr><th style="width: 130px;">Kundennummer</th><th style="width: 170px;">Username</th><th style="width: 170px;">Letzter Login</th><th>Info</th><th style="width: 100px; text-align: right;">Sessions</th><th style="width: 150px; text-align: right;">Dauer</th><th style="width: 150px; text-align: right;">Traffic</th></tr></thead></template>
|
||||
<template #skeleton-row><td><div class="skeleton-line"></div></td><td><div class="skeleton-line"></div></td><td><div class="skeleton-line"></div></td><td><div class="skeleton-line"></div></td><td><div class="skeleton-line"></div></td><td><div class="skeleton-line"></div></td><td><div class="skeleton-line"></div></td></template>
|
||||
<template #row="{ item }">
|
||||
<td><a v-if="item.customerNumber" class="link" target="_blank" :href="window.TT_CONFIG.BASE_PATH + '/Address?filter%5Bcustomer_number%5D=' + item.customerNumber" data-tooltip="Kunden öffnen" data-tooltip-align="right">{{ item.customerNumber }}</a></td>
|
||||
<td class="nowrap"><a class="link" target="_blank" :href="'http://radius.xinon.at/edit_user.php?user=' + item.username" data-tooltip="User in Radius öffnen">{{ item.username }}</a></td>
|
||||
<td class="mono small">{{ item.lastLogin }}</td><td class="mono clamp-2 small">{{ item.info }}</td>
|
||||
<td style="text-align: right;">{{ item.totalSessions }}</td>
|
||||
<td style="text-align: right;">{{ window.RadiusUtils.formatDuration(item.totalDurationSeconds) }}</td>
|
||||
<td style="text-align: right;">{{ window.RadiusUtils.formatBytes(item.totalTrafficBytes) }}</td>
|
||||
<template #head>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 130px;">Kundennummer</th>
|
||||
<th style="width: 170px;">Username</th>
|
||||
<th style="width: 170px;">Letzter Login</th>
|
||||
<th>Info</th>
|
||||
<th style="width: 100px; text-align: right;">Sessions</th>
|
||||
<th style="width: 150px; text-align: right;">Dauer</th>
|
||||
<th style="width: 150px; text-align: right;">Traffic</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</template>
|
||||
<template #observer><div ref="sentinel" style="height: 1px;"></div></template>
|
||||
</radius-table-view>
|
||||
<div v-if="hasSearched && !isLoading && filteredUsers.length" class="results-summary">{{ filteredUsers.length }} Treffer gefunden</div>
|
||||
<template #skeleton-row>
|
||||
<td><tt-skeleton /></td>
|
||||
<td><tt-skeleton /></td>
|
||||
<td><tt-skeleton /></td>
|
||||
<td><tt-skeleton /></td>
|
||||
<td><tt-skeleton /></td>
|
||||
<td><tt-skeleton /></td>
|
||||
<td><tt-skeleton /></td>
|
||||
</template>
|
||||
<template #row="{ item }">
|
||||
<td>
|
||||
<a v-if="item.customerNumber" class="link" target="_blank"
|
||||
:href="window.TT_CONFIG.BASE_PATH + '/Address?filter%5Bcustomer_number%5D=' + item.customerNumber"
|
||||
data-tooltip="Kunden öffnen"
|
||||
data-tooltip-align="right">{{ item.customerNumber }}</a>
|
||||
</td>
|
||||
<td class="nowrap">
|
||||
<a class="link" target="_blank"
|
||||
:href="'http://radius.xinon.at/edit_user.php?user=' + item.username"
|
||||
data-tooltip="User in Radius öffnen">{{ item.username }}</a>
|
||||
</td>
|
||||
<td class="mono small">{{ item.lastLogin }}</td>
|
||||
<td class="mono clamp-2 small">{{ item.info }}</td>
|
||||
<td style="text-align: right;">{{ item.totalSessions }}</td>
|
||||
<td style="text-align: right;">{{ window.TT_CORE.formatDuration(item.totalDurationSeconds) }}</td>
|
||||
<td style="text-align: right;">{{ window.TT_CORE.formatBytes(item.totalTrafficBytes) }}</td>
|
||||
</template>
|
||||
<template #observer>
|
||||
<div ref="sentinel" style="height: 1px;"></div>
|
||||
</template>
|
||||
</tt-data-table>
|
||||
<div v-if="hasSearched && !isLoading && filteredUsers.length" class="results-summary">
|
||||
{{ filteredUsers.length }} Treffer gefunden
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
data: () => ({ users: [], isLoading: false, _initialized: false, hasSearched: false, window: window, visibleCount: 50, observer: null, activeFilter: 'all', filters: [{id: 'all', name:'Alle', icon:'fa-duotone fa-globe'},{id:'nat', name:'NAT*', icon:'fa-duotone fa-users'},{id:'st', name:'ST*', icon:'fa-duotone fa-server'},{id:'stf', name:'STF*', icon:'fa-duotone fa-id-card-clip'}] }),
|
||||
computed: { filteredUsers() { return this.activeFilter === 'all' ? this.users : this.users.filter(u => u.username && u.username.startsWith(this.activeFilter)); }, visibleFilteredUsers() { return this.filteredUsers.slice(0, this.visibleCount); } },
|
||||
mounted() { this.observer = new IntersectionObserver(([e]) => { if (e && e.isIntersecting) this.loadMore(); }, { root: this.$refs.tableWrap, threshold: 0.1 }); if (this.$refs.sentinel) this.observer.observe(this.$refs.sentinel); },
|
||||
beforeDestroy() { if (this.observer) this.observer.disconnect(); },
|
||||
updated() { if (this.observer && this.$refs.sentinel) { this.observer.disconnect(); this.observer.observe(this.$refs.sentinel); } },
|
||||
data: () => ({
|
||||
users: [],
|
||||
isLoading: false,
|
||||
_initialized: false,
|
||||
hasSearched: false,
|
||||
window: window,
|
||||
visibleCount: 50,
|
||||
observer: null,
|
||||
activeFilter: 'all',
|
||||
filters: [
|
||||
{id: 'all', name:'Alle', icon:'fa-duotone fa-globe'},
|
||||
{id:'nat', name:'NAT*', icon:'fa-duotone fa-users'},
|
||||
{id:'st', name:'ST*', icon:'fa-duotone fa-server'},
|
||||
{id:'stf', name:'STF*', icon:'fa-duotone fa-id-card-clip'}
|
||||
]
|
||||
}),
|
||||
computed: {
|
||||
filteredUsers() {
|
||||
return this.activeFilter === 'all'
|
||||
? this.users
|
||||
: this.users.filter(u => u.username && u.username.startsWith(this.activeFilter));
|
||||
},
|
||||
visibleFilteredUsers() {
|
||||
return this.filteredUsers.slice(0, this.visibleCount);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.observer = new IntersectionObserver(([e]) => {
|
||||
if (e && e.isIntersecting) this.loadMore();
|
||||
}, { root: this.$refs.tableWrap, threshold: 0.1 });
|
||||
if (this.$refs.sentinel) this.observer.observe(this.$refs.sentinel);
|
||||
},
|
||||
beforeUnmount() {
|
||||
if (this.observer) this.observer.disconnect();
|
||||
},
|
||||
updated() {
|
||||
if (this.observer && this.$refs.sentinel) {
|
||||
this.observer.disconnect();
|
||||
this.observer.observe(this.$refs.sentinel);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initIfNeeded() { if (this._initialized) return; this._initialized = true; },
|
||||
setFilter(filter) { this.activeFilter = filter; this.visibleCount = 50; },
|
||||
async fetchUnusedUsers() { this.isLoading = true; this.hasSearched = true; this.visibleCount = 50; this.users = []; try { const res = await fetch(`${window.TT_CONFIG.BASE_PATH}/Radius/proxyUnsecureHTTPRequestToRadius?action2=reportUnused`); this.users = res.ok ? (await res.json() || []) : []; } catch (e) { console.error("Failed to fetch unused users:", e); this.users = []; } this.isLoading = false; },
|
||||
loadMore() { if (this.visibleCount < this.filteredUsers.length) this.visibleCount += 50; }
|
||||
initIfNeeded() {
|
||||
if (this._initialized) return;
|
||||
this._initialized = true;
|
||||
},
|
||||
setFilter(filter) {
|
||||
this.activeFilter = filter;
|
||||
this.visibleCount = 50;
|
||||
},
|
||||
async fetchUnusedUsers() {
|
||||
this.isLoading = true;
|
||||
this.hasSearched = true;
|
||||
this.visibleCount = 50;
|
||||
this.users = [];
|
||||
try {
|
||||
const { data } = await axios.get(`${window.TT_CONFIG.BASE_PATH}/Radius/proxyUnsecureHTTPRequestToRadius`, {
|
||||
params: { action2: 'reportUnused' }
|
||||
});
|
||||
this.users = data || [];
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch unused users:", error);
|
||||
this.users = [];
|
||||
}
|
||||
this.isLoading = false;
|
||||
},
|
||||
loadMore() {
|
||||
if (this.visibleCount < this.filteredUsers.length) this.visibleCount += 50;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Register component with Vue 3 app
|
||||
if (window.VueApp) {
|
||||
window.VueApp.component('radius-unused-users', RadiusUnusedUsers);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user