const ttSwitchCSS = ` .tt-switch { position: relative; display: inline-block; width: 44px; height: 24px; } .tt-switch input { opacity: 0; width: 0; height: 0; } .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; } .slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: .4s; } input:checked + .slider { background-color: #28a745; } input:focus + .slider { box-shadow: 0 0 1px #28a745; } input:checked + .slider:before { transform: translateX(20px); } .slider.round { border-radius: 24px; } .slider.round:before { border-radius: 50%; } `; const styleSheet = document.createElement("style"); styleSheet.innerText = ttSwitchCSS; document.head.appendChild(styleSheet); Vue.component('tt-switch', { template: ` `, props: { value: { type: Boolean, default: false } } }); Vue.component('device-monitoring-modal', { //language=Vue template: `
Keine allgemeinen Monitoring-Daten gefunden.
{{ item.name.replace('ICMP: ', '') }} {{ formatGeneralValue(item) }} {{ item.units }}

{{ formatUptime(item.value) }}

Seit {{ moment(Date.now() - item.value * 1000).format('DD.MM.YYYY HH:mm') }}
{{ generalData.problemCounts['24h'] }}letzte 24h
{{ generalData.problemCounts['7d'] }}letzte 7T
{{ generalData.problemCounts['30d'] }}letzte 30T
Bitte eine oder mehrere Schnittstellen auswählen, um Graphen anzuzeigen.
{{ iface.name }}
Empfangen (Mbps)Min: {{ statistics[iface.name].rx.min }}Avg: {{ statistics[iface.name].rx.avg }}Max: {{ statistics[iface.name].rx.max }}95%: {{ statistics[iface.name].rx.p95 }}
Gesendet (Mbps)Min: {{ statistics[iface.name].tx.min }}Avg: {{ statistics[iface.name].tx.avg }}Max: {{ statistics[iface.name].tx.max }}95%: {{ statistics[iface.name].tx.p95 }}
Keine Report-Daten für den gewählten Zeitraum.
Schnittstelle Speed (Mbps) Max In (Mbps) Avg In (Mbps) Auslastung In (%) Max Out (Mbps) Avg Out (Mbps) Auslastung Out (%)
{{ d.name }} {{ d.speed }} {{ d.rx.max }} {{ d.rx.avg }} {{ d.rx.usage }}% {{ d.tx.max }} {{ d.tx.avg }} {{ d.tx.usage }}%
Keine Probleme für dieses Gerät gefunden.
Aktuelle Probleme
{{ p.name }}{{ moment(p.clock * 1000).fromNow() }}
{{ p.opdata }}
Behobene Probleme (letzte 7 Tage)
{{ p.name }}{{ moment(p.clock * 1000).fromNow() }}
Keine SNMP-Schnittstelle auf diesem Host gefunden.
Keine Schnittstellen gefunden.
{{ iface.name }}
`, props: ['hostId', 'deviceName'], data() { return { moment: window.moment, activeTab: 'overview', tabs: [ { id: 'overview', name: 'Übersicht', icon: 'fas fa-tachometer-alt' }, { id: 'interfaces', name: 'Schnittstellen', icon: 'fas fa-ethernet' }, // { id: 'reports', name: 'Reports', icon: 'fas fa-chart-pie' }, { id: 'problems', name: 'Probleme', icon: 'fas fa-exclamation-triangle' }, { id: 'configuration', name: 'Konfiguration', icon: 'fas fa-cogs' }, ], loading: { overview: true, interfaces: false, problems: false, configuration: false, reports: false, individualInterfaces: {} }, generalData: null, problemData: { current: [], resolved: [] }, allInterfaces: [], selectedInterfaces: [], interfaceTimeRange: '24h', timeRanges: [{ text: '6H', value: '6h' }, { text: '24H', value: '24h' }, { text: '7T', value: '7d' }, { text: '30T', value: '30d' }], interfaceChartData: {}, chartInstances: {}, dataNormalizationMode: 'avg', downsampleThreshold: 500, configData: { snmp: null, interfaces: [] }, interfaceSearch: '', snmpV3Levels: [{text: 'noAuthNoPriv', value: '0'}, {text: 'authNoPriv', value: '1'}, {text: 'authPriv', value: '2'}], snmpV3Auth: [{text: 'MD5', value: '0'}, {text: 'SHA-1', value: '1'}], snmpV3Priv: [{text: 'DES', value: '0'}, {text: 'AES-128', value: '1'}], authPassphrase: '', privPassphrase: '', reportData: [], reportTimeRange: '7d', reportTimeRanges: [{ text: 'Letzte 7 Tage', value: '7d' }, { text: 'Letzte 30 Tage', value: '30d' }], reportSortKey: 'name', reportSortDir: 'asc', }; }, computed: { interfaceOptions() { return this.allInterfaces.map(iface => ({ text: iface.name, value: iface.name })); }, selectedInterfacesData() { return this.allInterfaces.filter(iface => this.selectedInterfaces.includes(iface.name)); }, filteredInterfaces() { if (!this.configData.interfaces || !Array.isArray(this.configData.interfaces)) return []; if (!this.interfaceSearch) { return this.configData.interfaces; } const search = this.interfaceSearch.toLowerCase(); return this.configData.interfaces.filter(iface => iface.name.toLowerCase().includes(search) ); }, statistics() { if (this.selectedInterfaces.length === 0 || Object.keys(this.interfaceChartData).length === 0) return {}; const stats = {}; this.selectedInterfacesData.forEach(iface => { const calculate = (data) => { if (!data || data.length === 0) return { min: 'N/A', max: 'N/A', avg: 'N/A', p95: 'N/A' }; const values = data.map(p => p.y); const sorted = [...values].sort((a, b) => a - b); const sum = values.reduce((acc, val) => acc + val, 0); return { min: this.formatStat(sorted[0]), max: this.formatStat(sorted[sorted.length - 1]), avg: this.formatStat(sum / values.length), p95: this.formatStat(sorted[Math.floor(sorted.length * 0.95)]), }; }; stats[iface.name] = { rx: calculate(this.interfaceChartData[iface.rx?.itemid]), tx: calculate(this.interfaceChartData[iface.tx?.itemid]) }; }); return stats; }, sortedReportData() { if (!this.reportData) return []; return [...this.reportData].sort((a, b) => { let aVal = this.reportSortKey.split('.').reduce((o, i) => o[i], a); let bVal = this.reportSortKey.split('.').reduce((o, i) => o[i], b); if (typeof aVal === 'string' && aVal.toLowerCase() === 'n/a') aVal = -1; if (typeof bVal === 'string' && bVal.toLowerCase() === 'n/a') bVal = -1; let modifier = this.reportSortDir === 'asc' ? 1 : -1; if (aVal < bVal) return -1 * modifier; if (aVal > bVal) return 1 * modifier; return 0; }); } }, async mounted() { if (typeof Chart.register === 'function' && window.ChartZoom) Chart.register(window.ChartZoom); moment.locale('de'); this.fetchTabData(); }, methods: { formatStat: val => typeof val === 'number' ? val.toFixed(2) : val, formatUptime: s => `${Math.floor(s/(3600*24))}t ${Math.floor(s%(3600*24)/3600)}h ${Math.floor(s%3600/60)}m`, formatGeneralValue: item => (item.units === 's') ? parseFloat(item.value).toFixed(3) : (item.units === '%') ? parseFloat(item.value).toFixed(2) : item.value, getSeverityClass: s => ['sev-info', 'sev-info', 'sev-warning', 'sev-average', 'sev-high', 'sev-disaster'][s] || 'sev-info', getSeverityIcon: s => ['fa-info-circle', 'fa-info-circle', 'fa-exclamation-circle', 'fa-exclamation-triangle', 'fa-radiation-alt', 'fa-biohazard'][s] || 'fa-info-circle', async fetchTabData() { const tab = this.activeTab; if (this.loading[tab] && tab !== 'overview') return; this.loading[tab] = true; try { if (tab === 'overview') { this.generalData = (await axios.get(`${window.TT_CONFIG.BASE_URL}/DeviceMonitoring/generalData`, { params: { hostId: this.hostId } })).data; } else if (tab === 'interfaces' && this.allInterfaces.length === 0) { this.allInterfaces = (await axios.get(`${window.TT_CONFIG.BASE_URL}/DeviceMonitoring/listInterfaces`, { params: { hostId: this.hostId } })).data; } else if (tab === 'problems') { this.problemData = (await axios.get(`${window.TT_CONFIG.BASE_URL}/DeviceMonitoring/getProblems`, { params: { hostId: this.hostId } })).data; } else if (tab === 'configuration') { this.configData = (await axios.get(`${window.TT_CONFIG.BASE_URL}/DeviceMonitoring/getConfigurationData`, { params: { hostId: this.hostId } })).data; if (this.configData.interfaces && Array.isArray(this.configData.interfaces)) { this.configData.interfaces.forEach(iface => this.$set(iface, 'loading', false)); } } else if (tab === 'reports') { await this.fetchReportData(); } } catch (e) { console.error(`Failed to load data for tab ${tab}`, e); window.notify('error', `Laden von ${tab}-Daten fehlgeschlagen.`); } finally { this.loading[tab] = false; } }, async saveSnmpConfig() { const detailsToSave = JSON.parse(JSON.stringify(this.configData.snmp.details)); if (this.authPassphrase) detailsToSave.authpassphrase = this.authPassphrase; else delete detailsToSave.authpassphrase; if (this.privPassphrase) detailsToSave.privpassphrase = this.privPassphrase; else delete detailsToSave.privpassphrase; try { await axios.post(`${window.TT_CONFIG.BASE_URL}/DeviceMonitoring/updateSnmp`, { interfaceId: this.configData.snmp.interfaceid, details: detailsToSave }); window.notify('success', 'SNMP-Konfiguration gespeichert.'); this.authPassphrase = ''; this.privPassphrase = ''; } catch(e) { window.notify('error', 'Fehler beim Speichern der SNMP-Konfiguration.'); } }, async toggleInterfaceAlarm(iface) { this.$set(iface, 'loading', true); try { await axios.post(`${window.TT_CONFIG.BASE_URL}/DeviceMonitoring/updateInterfaceAlarm`, { hostId: this.hostId, item: iface, enabled: iface.isAlarmed }); window.notify('success', `Alarm für ${iface.name} ${iface.isAlarmed ? 'aktiviert' : 'deaktiviert'}.`); } catch(e) { window.notify('error', 'Fehler beim Ändern des Alarms.'); iface.isAlarmed = !iface.isAlarmed; } finally { this.$set(iface, 'loading', false); } }, async fetchReportData() { this.loading.reports = true; try { this.reportData = (await axios.get(`${window.TT_CONFIG.BASE_URL}/DeviceMonitoring/getReportData`, { params: { hostId: this.hostId, timeRange: this.reportTimeRange } })).data; } catch (e) { window.notify('error', 'Fehler beim Laden der Report-Daten.'); } finally { this.loading.reports = false; } }, sortReport(key) { if (this.reportSortKey === key) { this.reportSortDir = this.reportSortDir === 'asc' ? 'desc' : 'asc'; } else { this.reportSortKey = key; this.reportSortDir = 'asc'; } }, getSortIcon(key) { if (this.reportSortKey !== key) return 'fas fa-sort'; return this.reportSortDir === 'asc' ? 'fas fa-sort-up' : 'fas fa-sort-down'; }, async handleInterfaceSelectionChange(newSelection, oldSelection) { const added = newSelection.filter(name => !oldSelection.includes(name)); const removed = oldSelection.filter(name => !newSelection.includes(name)); removed.forEach(name => { const iface = this.allInterfaces.find(i => i.name === name); if (iface) { if (this.chartInstances[iface.name]) { this.chartInstances[iface.name].destroy(); delete this.chartInstances[iface.name]; } } }); for (const name of added) await this.fetchAndRenderInterface(this.allInterfaces.find(i => i.name === name)); }, async fetchAndRenderInterface(iface) { const itemIds = [iface.rx?.itemid, iface.tx?.itemid].filter(Boolean); if (itemIds.length === 0) return; this.$set(this.loading.individualInterfaces, iface.name, true); try { const res = await axios.post(`${window.TT_CONFIG.BASE_URL}/DeviceMonitoring/interfaceData`, { itemIds, timeRange: this.interfaceTimeRange }); this.$set(this.interfaceChartData, iface.rx?.itemid, res.data[iface.rx?.itemid] || []); this.$set(this.interfaceChartData, iface.tx?.itemid, res.data[iface.tx?.itemid] || []); this.$nextTick(() => this.renderChart(iface)); } catch(e) { console.error(`Failed to fetch history for ${iface.name}`, e); } finally { this.$set(this.loading.individualInterfaces, iface.name, false); } }, renderChart(iface) { this.$nextTick(() => { const canvas = this.$refs['chartCanvas-' + iface.name]?.[0]; if (!canvas) return; if (this.chartInstances[iface.name]) this.chartInstances[iface.name].destroy(); this.chartInstances[iface.name] = new Chart(canvas.getContext('2d'), { /* ... Chart options ... */ }); }); }, openLiveChartPopup(iface) { const url = `${window.TT_CONFIG.BASE_URL}/DeviceMonitoring/liveGraphPage?rx_id=${iface.rx?.itemid || ''}&tx_id=${iface.tx?.itemid || ''}&name=${encodeURIComponent(iface.name)}`; window.open(url, `livegraph_${iface.name.replace(/[^a-zA-Z0-9]/g, "_")}`, 'width=800,height=450,resizable=yes,scrollbars=yes'); }, resetAllChartsZoom() { Object.values(this.chartInstances).forEach(chart => chart.resetZoom()); }, }, watch: { activeTab: 'fetchTabData', selectedInterfaces(newVal, oldVal) { this.handleInterfaceSelectionChange(newVal, oldVal); }, interfaceTimeRange() { this.handleInterfaceSelectionChange(this.selectedInterfaces, this.selectedInterfaces); }, dataNormalizationMode() { this.handleInterfaceSelectionChange(this.selectedInterfaces, this.selectedInterfaces); }, reportTimeRange: 'fetchReportData' } });