Add new components for managing free users and ONT lookup, enhance UI styling,...
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
/* ===== RadiusUnused.js ===== */
|
||||
Vue.component('radius-unused-users', {
|
||||
template: `
|
||||
<div class="radius-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>
|
||||
</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>
|
||||
</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.">
|
||||
<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>
|
||||
<div>Die Abfrage läuft...</div>
|
||||
<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>
|
||||
<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>
|
||||
</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); } },
|
||||
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; }
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user