Files
thetool/public/js/pages/Radius/RadiusUnused.js

47 lines
5.2 KiB
JavaScript

/* ===== 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; }
}
});