423 lines
16 KiB
JavaScript
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);
|
|
} |