Files
thetool/public/js/pages/Radius/RadiusRouterManager.js
2026-02-02 13:33:27 +01:00

681 lines
31 KiB
JavaScript

const RadiusRouterManager = {
name: 'RadiusRouterManager',
props: {
show: Boolean,
userItem: Object
},
template: `
<div>
<!-- Main Router Management Modal -->
<tt-dialog
:show="show"
:title="'Router Management - ' + (userItem.username || '')"
@close="$emit('close')"
size="wide"
>
<div class="modal-body-scrollable">
<div v-if="!routerDevice && !routerLoading" class="table-placeholder" style="min-height: 300px;">
<i class="fa-duotone fa-router-slash" style="font-size: 48px; opacity: 0.3;"></i>
<div style="margin-top: 16px;">Kein Router mit dieser IP gefunden</div>
</div>
<div v-else>
<!-- Router Info Header -->
<div class="router-info-header">
<i class="fa-duotone fa-router"></i>
<div class="router-header-text">
<div class="router-title">
<tt-skeleton v-if="routerLoading" width="200px" height="22px" />
<span v-else>{{ routerDevice.deviceInfo.hardwareVersion || 'Router' }}</span>
</div>
<div class="router-subtitle" :style="routerLoading ? 'margin-top: 2px' : ''">
<tt-skeleton v-if="routerLoading" width="140px" height="15px" />
<span v-else>{{ routerDevice.username || userItem.username }}</span>
</div>
</div>
<button class="ghost-btn refresh-btn" @click="refreshDevice" :disabled="routerLoading || refreshLoading" title="Daten aktualisieren">
<i class="fa-duotone fa-arrows-rotate" :class="{ 'fa-spin': refreshLoading }"></i>
</button>
</div>
<!-- Router Information Grid -->
<div class="router-info-grid">
<tt-info-card icon="fa-microchip" label="Hardware Modell" :value="routerDevice?.deviceInfo?.hardwareVersion" :loading="routerLoading" />
<tt-info-card icon="fa-code-branch" label="Software Version" :value="routerDevice?.deviceInfo?.softwareVersion" :loading="routerLoading" />
<tt-info-card icon="fa-barcode" label="CWMP Account" :value="routerDevice?.deviceInfo?.serialNumber" :loading="routerLoading" />
<tt-info-card icon="fa-fingerprint" label="ACS ID" :value="routerDevice?.deviceId" :loading="routerLoading" />
<tt-info-card icon="fa-globe" label="Externe IP" :value="routerDevice?.externalIp" :loading="routerLoading" />
<tt-info-card icon="fa-network-wired" label="Management IP" :value="routerDevice?.managementIp" :loading="routerLoading" />
</div>
<!-- Router Actions Section -->
<div class="router-actions-section">
<h4 class="router-actions-header">
<i class="fa-duotone fa-bolt"></i>
Router Aktionen
</h4>
<div class="router-actions-grid">
<button class="ghost-btn action-btn" @click="runRemoteAccess" :disabled="routerLoading || routerActionLoading || speedtestLoading">
<i class="fa-duotone fa-key"></i>
<span>Remote-Zugriff</span>
</button>
<button class="ghost-btn action-btn" @click="rebootRouter" :disabled="routerLoading || routerActionLoading || speedtestLoading">
<i class="fa-duotone fa-power-off"></i>
<span>Neustart</span>
</button>
<button class="ghost-btn action-btn" @click="pingRouter" :disabled="routerLoading || routerActionLoading || speedtestLoading">
<i class="fa-duotone fa-signal-bars"></i>
<span>Ping</span>
</button>
<button class="ghost-btn action-btn" @click="runSpeedtest" :disabled="routerLoading || routerActionLoading || speedtestLoading">
<i class="fa-duotone fa-gauge-high"></i>
<span>Speedtest</span>
</button>
<button class="ghost-btn action-btn" @click="openNetworkStructure" :disabled="routerLoading || routerActionLoading || speedtestLoading">
<i class="fa-duotone fa-sitemap"></i>
<span>Netzwerkstruktur</span>
</button>
<button class="ghost-btn action-btn" @click="openEventLog" :disabled="routerLoading || routerActionLoading || speedtestLoading">
<i class="fa-duotone fa-list-timeline"></i>
<span>Ereignisprotokoll</span>
</button>
<button class="ghost-btn action-btn" @click="openWlanKeyModal" :disabled="routerLoading || routerActionLoading || wlanKeyLoading">
<i class="fa-duotone fa-sliders"></i>
<span>Netzwerk-Konfig</span>
</button>
</div>
</div>
</div>
</div>
</tt-dialog>
<!-- SUB MODALS (Managed by this component) -->
<!-- Ping Modal -->
<tt-dialog :show="showPingModal" title="Ping Ergebnis" @close="showPingModal = false">
<tt-loading-indicator v-if="routerActionLoading && !pingResult" text="Ping läuft..." style="height: 150px;" />
<div v-else-if="pingResult">
<div class="kv-redesign">
<div class="kv-row"><span class="kv-label">Gesendet</span><code class="kv-value">{{ pingResult.packetsTransmitted }}</code></div>
<div class="kv-row"><span class="kv-label">Empfangen</span><code class="kv-value">{{ pingResult.packetsReceived }}</code></div>
<div class="kv-row"><span class="kv-label">Verlust</span><code class="kv-value">{{ pingResult.packetLoss }}%</code></div>
<div class="kv-row"><span class="kv-label">Min / Avg / Max</span><code class="kv-value">{{ pingResult.min }} / {{ pingResult.avg }} / {{ pingResult.max }} ms</code></div>
</div>
</div>
<div v-else class="table-placeholder" style="height: 150px;">Kein Ergebnis.</div>
</tt-dialog>
<!-- Speedtest Modal -->
<tt-dialog :show="showSpeedtestModal" title="Speedtest Ergebnis" @close="showSpeedtestModal = false" size="wide">
<tt-loading-indicator v-if="speedtestLoading && speedtestHistory.length === 0" text="Speedtest wird initialisiert..." style="height: 200px;" />
<div v-else>
<div class="table-wrap" style="max-height: 500px; overflow-y: auto;">
<table class="tt-table compact">
<thead>
<tr>
<th style="width: 60px;">#</th>
<th style="text-align: right">Bandbreite</th>
<th style="text-align: right">Übertragen</th>
<th style="text-align: right">Pakete</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, idx) in speedtestHistory" :key="idx">
<td class="mono small">{{ idx + 1 }}</td>
<td class="mono small" style="text-align: right">{{ row.bpsFormatted }}</td>
<td class="mono small" style="text-align: right">{{ row.bytesFormatted }}</td>
<td class="mono small" style="text-align: right">{{ row.packets }}</td>
</tr>
</tbody>
</table>
<div ref="speedtestBottom"></div>
</div>
<div v-if="speedtestLoading" class="center mt-3 muted small">
<i class="fa-duotone fa-spinner fa-spin"></i> Aktualisiere...
</div>
<div v-else class="center mt-3" style="color: var(--ok);">
<i class="fa-duotone fa-check-circle"></i> Abgeschlossen
</div>
</div>
</tt-dialog>
<!-- Remote Access Modal -->
<tt-dialog :show="showRemoteAccessModal" title="Remote Zugriff Konfiguration" @close="showRemoteAccessModal = false">
<tt-loading-indicator v-if="remoteAccessLoading" :text="remoteAccessStep" style="height: 200px;" />
<div v-else-if="remoteAccessResult">
<div class="alert ok mb-4" style="background-color: #eaf7ef; border: 1px solid #c9e6d8; color: #206a42; padding: 12px; border-radius: 8px;">
<i class="fa-duotone fa-check-circle"></i> Konfiguration erfolgreich abgeschlossen.
</div>
<div class="kv-redesign">
<div class="kv-row">
<span class="kv-label">Remote Link</span>
<div class="kv-value inline-copy">
<a :href="remoteAccessResult.link" target="_blank" class="link">{{ remoteAccessResult.link }}</a>
<tt-copy-button :text="remoteAccessResult.link" />
</div>
</div>
<div class="kv-row">
<span class="kv-label">Username</span>
<div class="kv-value inline-copy">
<code class="mono">{{ remoteAccessResult.username }}</code>
<tt-copy-button :text="remoteAccessResult.username" />
</div>
</div>
<div class="kv-row">
<span class="kv-label">Password</span>
<div class="kv-value inline-copy">
<code class="mono">{{ remoteAccessResult.password }}</code>
<tt-copy-button :text="remoteAccessResult.password" />
</div>
</div>
</div>
<div class="mt-4 pt-3" style="border-top: 1px solid var(--border);">
<button class="ghost-btn" @click="runRemoteAccess(true)" :disabled="remoteAccessLoading">
<i class="fa-duotone fa-rotate"></i>
<span>Zugangsdaten neu erstellen</span>
</button>
</div>
</div>
<div v-else class="table-placeholder" style="height: 200px;">Ein Fehler ist aufgetreten.</div>
</tt-dialog>
<!-- Network Structure Modal -->
<tt-dialog :show="showNetworkStructureModal" title="Netzwerkstruktur" @close="showNetworkStructureModal = false" size="wide">
<tt-loading-indicator v-if="networkStructureLoading" text="Lade Struktur..." style="min-height: 300px;" />
<div v-else-if="rootDevice">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
<span v-if="networkStructureCached" class="small" style="color: var(--tt-text-tertiary);">(Cache)</span>
<span v-else></span>
<button class="ghost-btn" @click="openNetworkStructure(true)" :disabled="networkStructureLoading" title="Neu laden">
<i class="fa-duotone fa-arrows-rotate" :class="{ 'fa-spin': networkStructureLoading }"></i>
</button>
</div>
<div class="network-tree-container">
<!-- Uses the recursive component -->
<radius-network-node :device="rootDevice" />
</div>
</div>
<div v-else class="table-placeholder" style="min-height: 300px;">Keine Daten verfügbar.</div>
</tt-dialog>
<!-- Event Log Modal -->
<tt-dialog :show="showEventLogModal" title="Ereignisprotokoll" @close="showEventLogModal = false" size="wide">
<tt-loading-indicator v-if="eventLogLoading" text="Lade Ereignisprotokoll..." style="min-height: 300px;" />
<div v-else-if="eventLogData && eventLogData.length > 0">
<div class="table-wrap" style="max-height: 500px; overflow-y: auto;">
<table class="tt-table compact">
<thead>
<tr>
<th style="width: 100px;">Datum</th>
<th style="width: 80px;">Uhrzeit</th>
<th style="width: 120px;">Gruppe</th>
<th>Nachricht</th>
</tr>
</thead>
<tbody>
<tr v-for="(event, idx) in eventLogData" :key="idx">
<td class="mono small">{{ event.date }}</td>
<td class="mono small">{{ event.time }}</td>
<td class="small">{{ event.group }}</td>
<td class="small">{{ event.msg }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div v-else class="table-placeholder" style="min-height: 300px;">Keine Ereignisse verfügbar.</div>
</tt-dialog>
<!-- WLAN & Network Config Modal -->
<tt-dialog :show="showWlanKeyModal" title="Netzwerk-Konfiguration" @close="showWlanKeyModal = false" size="wide">
<tt-loading-indicator v-if="wlanKeyLoading" text="Lade Konfiguration..." style="min-height: 300px;" />
<div v-else-if="wlanKeyData" class="network-config-modal">
<!-- Grid Layout -->
<div class="config-grid">
<!-- WLAN Section -->
<div class="config-card">
<div class="config-card-header">
<i class="fa-duotone fa-wifi"></i>
<span>WLAN</span>
<span v-if="wlanKeyData.ap_enabled !== undefined" class="status-dot" :class="wlanKeyData.ap_enabled ? 'active' : 'inactive'"></span>
<span v-if="wlanKeyDataCached" class="cache-badge"><i class="fa-duotone fa-database"></i> Cache</span>
<button class="config-action-btn" @click="copyAllNetworkConfig" title="Alles kopieren">
<i class="fa-duotone fa-copy"></i>
</button>
<button class="config-action-btn" @click="openWlanKeyModal(true)" :disabled="wlanKeyLoading" title="Neu laden">
<i class="fa-duotone fa-arrows-rotate" :class="{ 'fa-spin': wlanKeyLoading }"></i>
</button>
</div>
<div class="config-card-body">
<div class="config-row highlight">
<span class="config-label">SSID</span>
<div class="config-value with-copy">
<code>{{ wlanKeyData.ssid || '-' }}</code>
<tt-copy-button v-if="wlanKeyData.ssid" :text="wlanKeyData.ssid" />
</div>
</div>
<div class="config-row" v-if="wlanKeyData.ssid_secondary && wlanKeyData.ssid_secondary !== wlanKeyData.ssid">
<span class="config-label">SSID 5GHz</span>
<div class="config-value with-copy">
<code>{{ wlanKeyData.ssid_secondary }}</code>
<tt-copy-button :text="wlanKeyData.ssid_secondary" />
</div>
</div>
<div class="config-row highlight">
<span class="config-label">Passwort</span>
<div class="config-value with-copy">
<code class="password">{{ wlanKeyData.psk || '-' }}</code>
<tt-copy-button v-if="wlanKeyData.psk" :text="wlanKeyData.psk" />
</div>
</div>
<div class="config-row">
<span class="config-label">Sicherheit</span>
<code class="config-value">{{ wlanKeyData.wpa_type?.toUpperCase() || 'WPA2' }}</code>
</div>
</div>
</div>
<!-- LAN Section -->
<div class="config-card">
<div class="config-card-header">
<i class="fa-duotone fa-network-wired"></i>
<span>LAN</span>
</div>
<div class="config-card-body">
<div class="config-row highlight">
<span class="config-label">Router-IP</span>
<div class="config-value with-copy">
<code>{{ wlanKeyData.lan?.ip || '-' }}</code>
<tt-copy-button v-if="wlanKeyData.lan?.ip" :text="wlanKeyData.lan.ip" />
</div>
</div>
<div class="config-row">
<span class="config-label">Subnetz</span>
<code class="config-value">{{ wlanKeyData.lan?.subnet || '-' }}</code>
</div>
</div>
</div>
<!-- DHCP Section -->
<div class="config-card">
<div class="config-card-header">
<i class="fa-duotone fa-server"></i>
<span>DHCP</span>
</div>
<div class="config-card-body">
<div class="config-row">
<span class="config-label">Bereich</span>
<code class="config-value" v-if="wlanKeyData.lan?.dhcp_start && wlanKeyData.lan?.dhcp_end">
{{ wlanKeyData.lan.dhcp_start }} - {{ wlanKeyData.lan.dhcp_end }}
</code>
<code class="config-value" v-else>-</code>
</div>
<div class="config-row">
<span class="config-label">DNS</span>
<code class="config-value small">{{ wlanKeyData.lan?.dns_servers || '-' }}</code>
</div>
</div>
</div>
<!-- Device Section -->
<div class="config-card">
<div class="config-card-header">
<i class="fa-duotone fa-router"></i>
<span>Gerät</span>
</div>
<div class="config-card-body">
<div class="config-row" v-if="wlanKeyData.device_name">
<span class="config-label">Modell</span>
<code class="config-value">{{ wlanKeyData.device_name }}</code>
</div>
<div class="config-row" v-if="wlanKeyData.known_devices_count !== undefined">
<span class="config-label">WLAN-Geräte</span>
<code class="config-value">{{ wlanKeyData.known_devices_count }}</code>
</div>
</div>
</div>
</div>
</div>
<div v-else class="table-placeholder" style="min-height: 300px;">Keine Daten verfügbar.</div>
</tt-dialog>
</div>
`,
data: () => ({
routerLoading: false,
routerActionLoading: false,
routerDevice: null,
// Sub-Modal States
showPingModal: false,
pingResult: null,
showSpeedtestModal: false,
speedtestLoading: false,
speedtestResult: null,
speedtestHistory: [],
speedtestHasStarted: false,
showRemoteAccessModal: false,
remoteAccessLoading: false,
remoteAccessResult: null,
remoteAccessStep: '',
showNetworkStructureModal: false,
networkStructureLoading: false,
networkStructureCached: false,
rootDevice: null,
showEventLogModal: false,
eventLogLoading: false,
eventLogData: null,
refreshLoading: false,
showWlanKeyModal: false,
wlanKeyLoading: false,
wlanKeyData: null,
wlanKeyDataCached: false
}),
watch: {
show: {
handler(val) {
if (val && this.userItem) {
this.loadRouterData();
}
},
immediate: true
},
userItem(val) {
if (val && this.show) {
this.loadRouterData();
}
}
},
methods: {
async loadRouterData() {
this.routerLoading = true;
this.routerDevice = null;
this.pingResult = null;
this.speedtestResult = null;
this.speedtestLoading = false;
try {
const { data: deviceData } = await axios.get(`${window.TT_CONFIG.BASE_PATH}/Radius/genieacsGetDeviceByMac`, {
params: { mac: this.userItem.username }
});
if (deviceData?.success) {
this.routerDevice = deviceData;
}
} catch (error) {
console.error('Error fetching router:', error);
window.notify('error', 'Fehler beim Laden des Routers');
}
this.routerLoading = false;
},
async refreshDevice() {
if (!this.routerDevice?.deviceId) return;
this.refreshLoading = true;
try {
const { data } = await axios.post(`${window.TT_CONFIG.BASE_PATH}/Radius/genieacsRefreshDevice`, {
deviceId: this.routerDevice.deviceId
});
if (data?.success) {
this.routerDevice.deviceInfo = data.deviceInfo;
this.routerDevice.externalIp = data.externalIp;
this.routerDevice.managementIp = data.managementIp;
window.notify('success', 'Daten aktualisiert');
}
} catch (error) {
console.error('Error refreshing device:', error);
window.notify('error', 'Fehler beim Aktualisieren');
}
this.refreshLoading = false;
},
async rebootRouter() {
if (!this.routerDevice || !this.routerDevice.deviceId) return;
if (!confirm('Möchten Sie den Router wirklich neu starten?')) return;
this.routerActionLoading = true;
try {
const { data } = await axios.post(`${window.TT_CONFIG.BASE_PATH}/Radius/genieacsRebootDevice`, {
deviceId: this.routerDevice.deviceId
});
if (data.success) {
window.notify('success', 'Router-Neustart gestartet');
} else {
window.notify('error', data.message || 'Fehler beim Neustart');
}
} catch (error) {
console.error('Error rebooting router:', error);
window.notify('error', 'Fehler beim Neustarten des Routers');
}
this.routerActionLoading = false;
},
async pingRouter() {
if (!this.routerDevice) return;
const pingIp = this.routerDevice.managementIp || this.routerDevice.ip;
if (!pingIp) return;
this.showPingModal = true;
this.routerActionLoading = true;
this.pingResult = null;
try {
const { data } = await axios.get(`${window.TT_CONFIG.BASE_PATH}/Radius/genieacsPing`, {
params: { ip: pingIp }
});
if (data.success && data.result) {
this.pingResult = data.result;
window.notify('success', 'Ping erfolgreich');
} else {
window.notify('error', 'Ping fehlgeschlagen');
}
} catch (error) {
console.error('Error pinging router:', error);
window.notify('error', 'Fehler beim Pingen des Routers');
}
this.routerActionLoading = false;
},
async runSpeedtest() {
if (!this.routerDevice || !this.routerDevice.deviceId) return;
this.showSpeedtestModal = true;
this.speedtestLoading = true;
this.speedtestResult = null;
this.speedtestHistory = [];
this.speedtestHasStarted = false;
try {
const { data } = await axios.post(`${window.TT_CONFIG.BASE_PATH}/Radius/genieacsRunSpeedtest`, {
deviceId: this.routerDevice.deviceId
});
if (data.success) {
this.pollSpeedtestResult();
} else {
throw new Error(data.message || "Speedtest konnte nicht gestartet werden");
}
} catch (e) {
window.notify('error', e.response?.data?.message || e.message || 'Fehler beim Starten des Speedtests');
this.speedtestLoading = false;
}
},
async pollSpeedtestResult() {
let attempts = 0;
const maxAttempts = 240;
const poll = async () => {
if (!this.showSpeedtestModal) return;
if (attempts >= maxAttempts) {
this.speedtestLoading = false;
window.notify('error', 'Speedtest Zeitüberschreitung');
return;
}
attempts++;
try {
const { data } = await axios.post(`${window.TT_CONFIG.BASE_PATH}/Radius/genieacsGetSpeedtestResult`, {
deviceId: this.routerDevice.deviceId
});
if (data.success && data.result) {
this.speedtestHistory.push(data.result);
this.$nextTick(() => {
if (this.$refs.speedtestBottom) {
this.$refs.speedtestBottom.scrollIntoView({ behavior: 'smooth' });
}
});
if (data.result.bps > 0) this.speedtestHasStarted = true;
if (this.speedtestHasStarted && data.result.bps === 0) {
this.speedtestLoading = false;
window.notify('success', 'Speedtest abgeschlossen');
return;
}
}
} catch (e) {
console.error(e);
}
if (this.speedtestLoading) setTimeout(poll, 1000);
};
poll();
},
async runRemoteAccess(forceRecreate = false) {
if (!this.routerDevice || !this.routerDevice.deviceId) return;
this.showRemoteAccessModal = true;
this.remoteAccessLoading = true;
this.remoteAccessStep = forceRecreate ? 'Erstelle neue Zugangsdaten...' : 'Konfiguriere Zugriff...';
this.remoteAccessResult = null;
try {
const { data } = await axios.post(`${window.TT_CONFIG.BASE_PATH}/Radius/genieacsRemoteAccess`, {
deviceId: this.routerDevice.deviceId,
forceRecreate: forceRecreate
});
if (data.success) {
this.remoteAccessResult = data;
if (forceRecreate) {
window.notify('success', 'Neue Zugangsdaten erstellt');
}
} else {
throw new Error(data.message || "Unbekannter Fehler");
}
} catch (error) {
window.notify('error', error.response?.data?.message || error.message || 'Fehler bei Remote Access');
} finally {
this.remoteAccessLoading = false;
}
},
async openNetworkStructure(forceRefresh = false) {
if (!this.routerDevice || !this.routerDevice.deviceId) return;
this.showNetworkStructureModal = true;
this.networkStructureLoading = true;
this.rootDevice = null;
this.networkStructureCached = false;
try {
const { data } = await axios.post(`${window.TT_CONFIG.BASE_PATH}/Radius/genieacsNetworkStructure`, {
deviceId: this.routerDevice.deviceId,
forceRefresh: forceRefresh
});
if (data.root) {
this.rootDevice = data.root;
this.networkStructureCached = data.cached === true;
}
} catch (error) {
console.error(error);
window.notify('error', 'Fehler beim Laden der Netzwerkstruktur');
} finally {
this.networkStructureLoading = false;
}
},
async openEventLog() {
if (!this.routerDevice || !this.routerDevice.deviceId) return;
this.showEventLogModal = true;
this.eventLogLoading = true;
this.eventLogData = null;
try {
const { data } = await axios.post(`${window.TT_CONFIG.BASE_PATH}/Radius/genieacsEventLog`, {
deviceId: this.routerDevice.deviceId
});
if (data.success && data.events) {
this.eventLogData = data.events;
} else {
throw new Error(data.message || "Keine Ereignisse gefunden");
}
} catch (error) {
console.error(error);
window.notify('error', error.response?.data?.message || 'Fehler beim Laden des Ereignisprotokolls');
} finally {
this.eventLogLoading = false;
}
},
async openWlanKeyModal(forceRefresh = false) {
if (!this.routerDevice || !this.routerDevice.deviceId) return;
this.showWlanKeyModal = true;
this.wlanKeyLoading = true;
this.wlanKeyData = null;
this.wlanKeyDataCached = false;
try {
const { data } = await axios.post(`${window.TT_CONFIG.BASE_PATH}/Radius/genieacsFritzboxWlanKey`, {
deviceId: this.routerDevice.deviceId,
forceRefresh: forceRefresh
});
if (data.success && data.wlan) {
this.wlanKeyData = data.wlan;
this.wlanKeyDataCached = data.cached === true;
} else {
throw new Error(data.message || "Keine WLAN-Daten gefunden");
}
} catch (error) {
console.error(error);
window.notify('error', error.response?.data?.message || 'Fehler beim Laden der WLAN-Daten');
} finally {
this.wlanKeyLoading = false;
}
},
copyAllNetworkConfig() {
if (!this.wlanKeyData) return;
const d = this.wlanKeyData;
const lines = [
'=== WLAN ===',
`SSID: ${d.ssid || '-'}`,
d.ssid_secondary && d.ssid_secondary !== d.ssid ? `SSID 5GHz: ${d.ssid_secondary}` : null,
`Passwort: ${d.psk || '-'}`,
`Sicherheit: ${d.wpa_type?.toUpperCase() || 'WPA2'}`,
d.ap_enabled !== undefined ? `WLAN aktiv: ${d.ap_enabled ? 'Ja' : 'Nein'}` : null,
'',
'=== LAN ===',
`Router-IP: ${d.lan?.ip || '-'}`,
`Subnetz: ${d.lan?.subnet || '-'}`,
'',
'=== DHCP ===',
`Bereich: ${d.lan?.dhcp_start && d.lan?.dhcp_end ? `${d.lan.dhcp_start} - ${d.lan.dhcp_end}` : '-'}`,
`DNS: ${d.lan?.dns_servers || '-'}`,
'',
'=== Gerät ===',
d.device_name ? `Modell: ${d.device_name}` : null,
d.known_devices_count !== undefined ? `WLAN-Geräte: ${d.known_devices_count}` : null,
].filter(Boolean).join('\n');
navigator.clipboard.writeText(lines).then(() => {
window.notify('success', 'Netzwerk-Konfiguration kopiert');
}).catch(() => {
window.notify('error', 'Kopieren fehlgeschlagen');
});
}
}
};
if (window.VueApp) {
window.VueApp.component('radius-router-manager', RadiusRouterManager);
}