Files
thetool/public/js/pages/Radius/RadiusUsers.js
2025-12-13 21:27:43 +00:00

423 lines
16 KiB
JavaScript

/* ===== RadiusUsers.js (Vue 3 + TT-Core) ===== */
const RadiusUsers = {
name: 'RadiusUsers',
template: `
<div class="tt-scope">
<div class="filters-layout">
<div class="field">
<tt-smart-autocomplete
v-model="billAddrDisplay"
:wide="true"
placeholder="Kunde suchen"
@select="onAddrSelect"
@enter="loadRadiusUsers"
@mode-change="onModeChange"
/>
</div>
<div class="field">
<div class="input-wrap ip-field-wrapper">
<span class="ip-focus-tooltip">z.B. nat* für lazy Suche</span>
<i class="fa-duotone fa-user input-icon"></i>
<input
class="ri"
v-model="username"
placeholder="Username"
@keydown.enter="loadRadiusUsers"
autocomplete="off"
autocapitalize="none"
autocorrect="off"
inputmode="text"
name="radius-username"
/>
</div>
</div>
<div class="field">
<div class="input-wrap ip-field-wrapper">
<span class="ip-focus-tooltip">Prefixe: '=' exakt, '*' Verlauf (lazy), '*=' Verlauf (exakt)</span>
<div class="input-wrap">
<i class="fa-duotone fa-network-wired input-icon"></i>
<input
class="ri"
v-model="ip"
placeholder="IP-Adresse"
@keydown.enter="loadRadiusUsers"
autocomplete="off"
autocapitalize="none"
autocorrect="off"
inputmode="decimal"
name="radius-ip"
/>
</div>
</div>
</div>
<div class="field">
<div class="input-wrap">
<i class="fa-duotone fa-note-sticky input-icon"></i>
<input
class="ri"
v-model="info"
placeholder="Info"
@keydown.enter="loadRadiusUsers"
/>
</div>
</div>
<div class="cluster" style="gap: 8px;">
<div class="field">
<label class="switch-field">
<span class="mini muted">Online-Status</span>
<span class="switch">
<input type="checkbox" v-model="checkOnlineState">
<span class="switch-track">
<i class="fa-duotone fa-signal-bars-good on"></i>
<i class="fa-duotone fa-signal-bars-slash off"></i>
</span>
</span>
</label>
</div>
<button class="primary-btn" @click="loadRadiusUsers" :disabled="isLoading" style="flex-grow: 1;">
<span v-if="!isLoading"><i class="fa-duotone fa-magnifying-glass"></i></span>
<span v-else class="btn-loader"></span>
</button>
<button
class="danger-btn"
@click="clearFilters"
:disabled="!hasFilters"
data-tooltip="Eingaben leeren"
data-tooltip-align="left"
>
<i class="fa-duotone fa-xmark"></i>
</button>
</div>
</div>
<div class="results-container mt-between">
<tt-data-table
:items="visibleUsers"
:is-loading="isLoading"
:has-searched="hasSearched"
:skeleton-row-count="6"
initial-placeholder-text="Beginnen Sie Ihre Suche, indem Sie Filter eingeben."
>
<template #head>
<thead>
<tr>
<th style="text-align: center; width: 170px;">Kundennummer</th>
<th style="text-align: center; width: 183px;">Username</th>
<th style="text-align: center">Info</th>
<th style="text-align: center; width: 190px;">Status</th>
<th style="text-align: center; width: 180px;">Aktionen</th>
</tr>
</thead>
</template>
<template #skeleton-row>
<td><tt-skeleton /></td>
<td><tt-skeleton /></td>
<td><tt-skeleton /></td>
<td><tt-skeleton /></td>
<td><tt-skeleton height="36px" /></td>
</template>
<template #row="{ item }">
<td>
<a class="link" target="_blank"
:href="window.TT_CONFIG.BASE_PATH + '/Address?filter%5Bcustomer_number%5D=' + item.customerNumber"
data-tooltip="Kunden in neuem Tab ö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"
data-tooltip-align="right"
>{{ item.username }}</a>
<tt-copy-button :text="item.username" tooltip-align="right" />
</td>
<td class="mono clamp-2">{{ item.info || '—' }}</td>
<td>
<tt-status-chip
v-if="checkOnlineState"
:username="item.username"
:key="item.username + '_'+searchCount"
@scan-ip="onScanIp($event, item)"
/>
</td>
<td class="nowrap cluster" style="gap: 4px; justify-content: center;">
<button class="ghost-btn" @click="openRadacctModal(item.username)" data-tooltip="Details">
<i class="fa-duotone fa-circle-info"></i>
</button>
<button class="ghost-btn" @click="openTransferModal(item.username)" data-tooltip="Transfer Statistik"
data-tooltip-align="left">
<i class="fa-duotone fa-chart-line"></i>
</button>
<button class="ghost-btn" @click="openRouterManager(item)"
data-tooltip="Router Management" data-tooltip-align="left">
<i class="fa-duotone fa-router"></i>
</button>
</td>
</template>
<template #observer>
<div ref="sentinel" style="height: 1px;"></div>
</template>
</tt-data-table>
<div v-if="hasSearched" class="results-summary">
<span v-if="isLoading">Suche läuft...</span>
<span v-else-if="radiusUsers.length">{{ radiusUsers.length }} Treffer gefunden</span>
</div>
</div>
<!-- Modals -->
<radius-radacct-modal
:show="showRadacctModal"
:username="selectedUsername"
@close="showRadacctModal = false"
/>
<radius-transfer-modal
:show="showTransferModal"
:username="selectedUsername"
@close="showTransferModal = false"
/>
<radius-router-manager
v-if="showRouterManager"
:show="showRouterManager"
:user-item="selectedUserItem"
@close="showRouterManager = false"
/>
<!-- Extension ID Modal (kept embedded as it's small) -->
<tt-dialog :show="showExtensionIdModal" title="Extension ID Konfigurieren" @close="showExtensionIdModal=false">
<div>
<div class="field">
<label style="margin-bottom: 8px; font-size: 14px;">Chrome Extension ID</label>
<div class="input-wrap">
<i class="fa-duotone fa-puzzle-piece input-icon"></i>
<input
class="ri"
type="text"
v-model.trim="extensionId"
placeholder="z.B. jglijfiddilckddlmbnlojmmlahboffh"
/>
</div>
</div>
<div class="cluster" style="justify-content: flex-end; margin-top: 24px; gap: 12px;">
<button class="primary-btn" @click="saveExtensionId">Speichern</button>
</div>
</div>
</tt-dialog>
</div>
`,
data: () => ({
window: window,
// Filters
billAddrDisplay: '',
billAddrCustnum: '',
username: '',
ip: '',
info: '',
searchMode: 'autocomplete',
// List Data
radiusUsers: [],
checkOnlineState: false,
isLoading: false,
searchCount: 0,
hasSearched: false,
visibleCount: 50,
observer: null,
// Modal State
selectedUsername: '',
selectedUserItem: null,
showRadacctModal: false,
showTransferModal: false,
showRouterManager: false,
// Extension Config
showExtensionIdModal: false,
extensionId: 'jglijfiddilckddlmbnlojmmlahboffh'
}),
computed: {
hasFilters() {
return this.billAddrDisplay || this.username || this.ip || this.info;
},
visibleUsers() {
return this.radiusUsers.slice(0, this.visibleCount);
}
},
mounted() {
const urlParams = new URLSearchParams(window.location.search);
const infoParam = urlParams.get('info');
if (infoParam) {
this.info = infoParam;
this.loadRadiusUsers();
}
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);
const savedExtensionId = localStorage.getItem('radiusExtensionId');
if (savedExtensionId) {
this.extensionId = savedExtensionId;
}
window.addEventListener('keydown', this.handleKeydown);
},
beforeUnmount() {
if (this.observer) this.observer.disconnect();
window.removeEventListener('keydown', this.handleKeydown);
},
updated() {
if (this.observer && this.$refs.sentinel) {
this.observer.disconnect();
this.observer.observe(this.$refs.sentinel);
}
},
methods: {
handleKeydown(e) {
if (e.code === 'KeyE' && e.ctrlKey && e.altKey) {
e.preventDefault();
this.showExtensionIdModal = true;
}
},
saveExtensionId() {
localStorage.setItem('radiusExtensionId', this.extensionId);
this.showExtensionIdModal = false;
window.notify('success', 'Extension ID gespeichert.');
},
onAddrSelect({custnum, display}) {
this.billAddrCustnum = custnum || '';
this.billAddrDisplay = display || '';
},
onModeChange(newMode) {
this.searchMode = newMode;
},
async loadRadiusUsers() {
this.isLoading = true;
this.radiusUsers = [];
this.hasSearched = true;
this.visibleCount = 50;
try {
const params = {
username: this.username || '',
info: this.info || '',
ip: this.ip || ''
};
if (this.searchMode === 'text') params.estmk_nr = this.billAddrDisplay || '';
else params.custnum = this.billAddrCustnum || '';
const { data } = await axios.get(`${window.TT_CONFIG.BASE_PATH}/Radius/proxyUnsecureHTTPRequestToRadius`, { params });
if (Array.isArray(data) && data.length < 6) this.checkOnlineState = true;
this.radiusUsers = Array.isArray(data) ? data : [];
} catch (e) {
console.error(e);
}
this.isLoading = false;
const urlParams = new URLSearchParams(window.location.search);
const connectParam = urlParams.get('connect');
if (connectParam && connectParam.toLowerCase() === 'true' && this.radiusUsers.length === 1) {
try {
const { data: radacct } = await axios.get(`${window.TT_CONFIG.BASE_PATH}/Radius/proxyUnsecureHTTPRequestToRadius`, {
params: { action2: 'fetchRadacct', username: this.radiusUsers[0].username }
});
if (radacct?.ip) {
this.onScanIp({ip: radacct.ip}, this.radiusUsers[0]);
}
} catch (error) {
console.error('Error fetching radacct:', error);
}
}
this.searchCount++;
},
clearFilters() {
this.billAddrDisplay = '';
this.billAddrCustnum = '';
this.username = '';
this.ip = '';
this.info = '';
this.radiusUsers = [];
this.hasSearched = false;
this.searchCount++;
this.visibleCount = 50;
},
loadMore() {
if (this.visibleCount < this.radiusUsers.length) this.visibleCount += 50;
},
// Modal Openers
openRadacctModal(username) {
this.selectedUsername = username;
this.showRadacctModal = true;
},
openTransferModal(username) {
this.selectedUsername = username;
this.showTransferModal = true;
},
openRouterManager(item) {
this.selectedUserItem = item;
this.showRouterManager = true;
},
// IP Scan Logic (Must remain in parent or be extracted to service, kept here for table interaction)
async onScanIp(payload, item) {
const {ip} = payload;
const info = item.info;
if (!ip) return;
window.notify('info', `Starte Scan für ${ip}...`);
try {
const { data } = await axios.get(`http://localhost:8094/scan`, {
params: { ip }
});
if (data.status === 'success' && data.url) {
const extensionId = this.extensionId;
const message = {
type: "INITIATE_ROUTER_LOGIN",
payload: {
ip: ip,
url: data.url,
info: info
}
};
if (window.chrome && chrome.runtime && chrome.runtime.sendMessage) {
try {
chrome.runtime.sendMessage(extensionId, message, (response) => {
if (chrome.runtime.lastError) {
console.warn("Senden an Erweiterung fehlgeschlagen:", chrome.runtime.lastError.message);
window.notify('warning', 'Scan-Daten konnten nicht an die Erweiterung gesendet werden. (Drücke STRG + ALT + E zum Konfigurieren)');
} else {
console.log("Erweiterung hat geantwortet:", response);
}
});
} catch (e) {
console.error("Fehler beim Senden an die Erweiterung:", e);
window.notify('error', 'Fehler beim Senden an die Erweiterung.');
}
} else {
console.warn("Chrome Extension Messaging API nicht verfügbar.");
window.notify('warning', 'Chrome Messaging API nicht gefunden.');
}
} else if (data.status === 'not_found') {
window.notify('warning', `Kein Gerät für ${ip} gefunden.`);
} else {
window.notify('error', 'Ungültige Antwort vom Scan-Server.');
}
} catch (error) {
console.error('IP-Scan fehlgeschlagen:', error);
window.notify('error', `Scan für ${ip} fehlgeschlagen.`);
}
}
}
};
if (window.VueApp) {
window.VueApp.component('radius-users', RadiusUsers);
}