diff --git a/public/js/app.js b/public/js/app.js index fdcc891..aa45588 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -125,7 +125,7 @@ let siteThemeQuery = null; // For media query cleanup let siteThemeHandler = null; let backgroundIntervals = []; // To track setIntervals - + // Load sort state from localStorage or use default let currentSort = { column: 'up', direction: 'desc' }; try { @@ -136,7 +136,7 @@ } catch (e) { console.warn('Failed to load sort state', e); } - + let myMap2D = null; let editingRouteId = null; @@ -157,7 +157,7 @@ // Event listeners dom.btnSettings.addEventListener('click', openSettings); dom.modalClose.addEventListener('click', closeSettings); - + // Toggle server source option based on type if (dom.sourceType) { dom.sourceType.addEventListener('change', () => { @@ -172,7 +172,7 @@ } if (dom.btnCancelEditRoute) { - dom.btnCancelEditRoute.onclick = cancelEditRoute; + dom.btnCancelEditRoute.onclick = cancelEditRoute; } dom.settingsModal.addEventListener('click', (e) => { @@ -201,7 +201,7 @@ // Site settings dom.btnSaveSiteSettings.addEventListener('click', saveSiteSettings); dom.btnAddRoute.addEventListener('click', addLatencyRoute); - + // Auth password change if (dom.btnChangePassword) { dom.btnChangePassword.addEventListener('click', saveChangePassword); @@ -251,7 +251,7 @@ offset: 1 } ], { - duration: 400, + duration: 600, easing: 'cubic-bezier(0.16, 1, 0.3, 1)', fill: 'none' }); @@ -305,7 +305,7 @@ offset: 1 } ], { - duration: 350, + duration: 500, easing: 'cubic-bezier(0.4, 0, 0.2, 1)', fill: 'forwards' // Hold final frame until we remove class }); @@ -660,7 +660,7 @@ try { const response = await fetch('/api/metrics/latency'); const data = await response.json(); - currentLatencies = data.routes || []; + currentLatencies = data.routes || []; if (allServersData.length > 0) { updateMap2D(allServersData); } @@ -672,74 +672,74 @@ // ---- Global 2D Map ---- async function initMap2D() { if (!dom.globeContainer) return; - + if (typeof echarts === 'undefined') { - console.warn('[Map2D] ECharts library not loaded.'); - dom.globeContainer.innerHTML = `
地图库加载失败
`; - return; + console.warn('[Map2D] ECharts library not loaded.'); + dom.globeContainer.innerHTML = `
地图库加载失败
`; + return; } try { - // Fetch map data - const resp = await fetch('https://cdn.jsdelivr.net/npm/echarts@4.9.0/map/json/world.json'); - const worldJSON = await resp.json(); - - // Transform to Pacific-centered correctly - const transformCoords = (coords) => { - if (!Array.isArray(coords) || coords.length === 0) return; - const first = coords[0]; - if (!Array.isArray(first)) return; + // Fetch map data + const resp = await fetch('https://cdn.jsdelivr.net/npm/echarts@4.9.0/map/json/world.json'); + const worldJSON = await resp.json(); - if (typeof first[0] === 'number') { // Ring - let sum = 0; - coords.forEach(pt => sum += pt[0]); - let avg = sum / coords.length; - if (avg < -20) { - coords.forEach(pt => pt[0] += 360); - } - } else { - coords.forEach(transformCoords); - } - }; + // Transform to Pacific-centered correctly + const transformCoords = (coords) => { + if (!Array.isArray(coords) || coords.length === 0) return; + const first = coords[0]; + if (!Array.isArray(first)) return; - if (worldJSON && worldJSON.features) { - worldJSON.features.forEach(feature => { - if (feature.geometry && feature.geometry.coordinates) { - transformCoords(feature.geometry.coordinates); - } - }); + if (typeof first[0] === 'number') { // Ring + let sum = 0; + coords.forEach(pt => sum += pt[0]); + let avg = sum / coords.length; + if (avg < -20) { + coords.forEach(pt => pt[0] += 360); + } + } else { + coords.forEach(transformCoords); } - - echarts.registerMap('world', worldJSON); + }; - if (myMap2D) { - myMap2D.dispose(); - if (mapResizeHandler) { - window.removeEventListener('resize', mapResizeHandler); - } + if (worldJSON && worldJSON.features) { + worldJSON.features.forEach(feature => { + if (feature.geometry && feature.geometry.coordinates) { + transformCoords(feature.geometry.coordinates); + } + }); + } + + echarts.registerMap('world', worldJSON); + + if (myMap2D) { + myMap2D.dispose(); + if (mapResizeHandler) { + window.removeEventListener('resize', mapResizeHandler); } + } - myMap2D = echarts.init(dom.globeContainer); - - const isLight = document.documentElement.classList.contains('light-theme'); - - // Helper to transform app coordinates to shifted map coordinates - const shiftLng = (lng) => (lng < -20 ? lng + 360 : lng); + myMap2D = echarts.init(dom.globeContainer); - const option = { - backgroundColor: 'transparent', - tooltip: { - show: true, - trigger: 'item', - confine: true, - transitionDuration: 0, - backgroundColor: 'rgba(10, 14, 26, 0.9)', - borderColor: 'var(--accent-indigo)', - textStyle: { color: '#fff', fontSize: 12 }, - formatter: (params) => { - const d = params.data; - if (!d) return ''; - return ` + const isLight = document.documentElement.classList.contains('light-theme'); + + // Helper to transform app coordinates to shifted map coordinates + const shiftLng = (lng) => (lng < -20 ? lng + 360 : lng); + + const option = { + backgroundColor: 'transparent', + tooltip: { + show: true, + trigger: 'item', + confine: true, + transitionDuration: 0, + backgroundColor: 'rgba(10, 14, 26, 0.9)', + borderColor: 'var(--accent-indigo)', + textStyle: { color: '#fff', fontSize: 12 }, + formatter: (params) => { + const d = params.data; + if (!d) return ''; + return `
${escapeHtml(d.job)}
${escapeHtml(d.city || '')}, ${escapeHtml(d.countryName || d.country || '')}
@@ -748,53 +748,53 @@
`; - } - }, - geo: { - map: 'world', - roam: true, - center: [165, 20], // Centered in Pacific - zoom: 1.1, - aspectScale: 0.85, - emphasis: { - label: { show: false }, - itemStyle: { areaColor: isLight ? '#f0f2f5' : '#2d334d' } - }, - itemStyle: { - areaColor: isLight ? '#f4f6fa' : '#1a1d2e', - borderColor: isLight ? '#cbd5e1' : '#2d334d', - borderWidth: 1 - }, - select: { - itemStyle: { areaColor: isLight ? '#f4f8ff' : '#2d334d' } - } - }, - series: [{ - type: 'scatter', - coordinateSystem: 'geo', - geoIndex: 0, - symbolSize: 5, - itemStyle: { - color: '#06b6d4', - shadowBlur: 3, - shadowColor: 'rgba(6, 182, 212, 0.5)' - }, - data: [] - }] - }; + } + }, + geo: { + map: 'world', + roam: true, + center: [165, 20], // Centered in Pacific + zoom: 1.1, + aspectScale: 0.85, + emphasis: { + label: { show: false }, + itemStyle: { areaColor: isLight ? '#f0f2f5' : '#2d334d' } + }, + itemStyle: { + areaColor: isLight ? '#f4f6fa' : '#1a1d2e', + borderColor: isLight ? '#cbd5e1' : '#2d334d', + borderWidth: 1 + }, + select: { + itemStyle: { areaColor: isLight ? '#f4f8ff' : '#2d334d' } + } + }, + series: [{ + type: 'scatter', + coordinateSystem: 'geo', + geoIndex: 0, + symbolSize: 5, + itemStyle: { + color: '#06b6d4', + shadowBlur: 3, + shadowColor: 'rgba(6, 182, 212, 0.5)' + }, + data: [] + }] + }; - myMap2D.setOption(option); + myMap2D.setOption(option); - mapResizeHandler = debounce(() => { - if (myMap2D) myMap2D.resize(); - }, 100); - window.addEventListener('resize', mapResizeHandler); - - if (allServersData.length > 0) { - updateMap2D(allServersData); - } + mapResizeHandler = debounce(() => { + if (myMap2D) myMap2D.resize(); + }, 100); + window.addEventListener('resize', mapResizeHandler); + + if (allServersData.length > 0) { + updateMap2D(allServersData); + } } catch (err) { - console.error('[Map2D] Initialization failed:', err); + console.error('[Map2D] Initialization failed:', err); } } @@ -802,15 +802,15 @@ if (!myMap2D) return; const isLight = theme === 'light'; myMap2D.setOption({ - geo: { - itemStyle: { - areaColor: isLight ? '#f4f6fa' : '#1a1d2e', - borderColor: isLight ? '#cbd5e1' : '#2d334d' - }, - emphasis: { - itemStyle: { areaColor: isLight ? '#f4f8ff' : '#2d334d' } - } + geo: { + itemStyle: { + areaColor: isLight ? '#f4f6fa' : '#1a1d2e', + borderColor: isLight ? '#cbd5e1' : '#2d334d' + }, + emphasis: { + itemStyle: { areaColor: isLight ? '#f4f8ff' : '#2d334d' } } + } }); } function updateMap2D(servers) { @@ -820,175 +820,175 @@ const shiftLng = (lng) => (lng < -20 ? lng + 360 : lng); const geoData = servers - .filter(s => s.lat && s.lng) - .map(s => ({ - name: s.job, - value: [shiftLng(s.lng), s.lat], - job: s.job, - city: s.city, - country: s.country, - countryName: s.countryName, - netRx: s.netRx, - netTx: s.netTx - })); + .filter(s => s.lat && s.lng) + .map(s => ({ + name: s.job, + value: [shiftLng(s.lng), s.lat], + job: s.job, + city: s.city, + country: s.country, + countryName: s.countryName, + netRx: s.netRx, + netTx: s.netTx + })); // Combine all series const finalSeries = [ - { - type: 'scatter', - coordinateSystem: 'geo', - geoIndex: 0, - symbolSize: 6, - itemStyle: { - color: '#06b6d4', - shadowBlur: 3, - shadowColor: 'rgba(6, 182, 212, 0.5)' - }, - data: geoData, - zlevel: 1 - } + { + type: 'scatter', + coordinateSystem: 'geo', + geoIndex: 0, + symbolSize: 6, + itemStyle: { + color: '#06b6d4', + shadowBlur: 3, + shadowColor: 'rgba(6, 182, 212, 0.5)' + }, + data: geoData, + zlevel: 1 + } ]; // Add latency routes if configured if (currentLatencies && currentLatencies.length > 0) { - const countryCoords = { - 'china': [116.4074, 39.9042], - 'beijing': [116.4074, 39.9042], - 'shanghai': [121.4737, 31.2304], - 'hong kong': [114.1694, 22.3193], - 'taiwan': [120.9605, 23.6978], - 'united states': [-95.7129, 37.0902], - 'us seattle': [-122.3321, 47.6062], - 'seattle': [-122.3321, 47.6062], - 'us chicago': [-87.6298, 41.8781], - 'chicago': [-87.6298, 41.8781], - 'news york': [-74.0060, 40.7128], - 'new york corp': [-74.0060, 40.7128], - 'new york': [-74.0060, 40.7128], - 'san francisco': [-122.4194, 37.7749], - 'los angeles': [-118.2437, 34.0522], - 'japan': [138.2529, 36.2048], - 'tokyo': [139.6917, 35.6895], - 'singapore': [103.8198, 1.3521], - 'germany': [10.4515, 51.1657], - 'frankfurt': [8.6821, 50.1109], - 'united kingdom': [-3.436, 55.3781], - 'london': [-0.1276, 51.5074], - 'france': [2.2137, 46.2276], - 'paris': [2.3522, 48.8566], - 'south korea': [127.7669, 35.9078], - 'korea': [127.7669, 35.9078], - 'seoul': [126.9780, 37.5665] - }; + const countryCoords = { + 'china': [116.4074, 39.9042], + 'beijing': [116.4074, 39.9042], + 'shanghai': [121.4737, 31.2304], + 'hong kong': [114.1694, 22.3193], + 'taiwan': [120.9605, 23.6978], + 'united states': [-95.7129, 37.0902], + 'us seattle': [-122.3321, 47.6062], + 'seattle': [-122.3321, 47.6062], + 'us chicago': [-87.6298, 41.8781], + 'chicago': [-87.6298, 41.8781], + 'news york': [-74.0060, 40.7128], + 'new york corp': [-74.0060, 40.7128], + 'new york': [-74.0060, 40.7128], + 'san francisco': [-122.4194, 37.7749], + 'los angeles': [-118.2437, 34.0522], + 'japan': [138.2529, 36.2048], + 'tokyo': [139.6917, 35.6895], + 'singapore': [103.8198, 1.3521], + 'germany': [10.4515, 51.1657], + 'frankfurt': [8.6821, 50.1109], + 'united kingdom': [-3.436, 55.3781], + 'london': [-0.1276, 51.5074], + 'france': [2.2137, 46.2276], + 'paris': [2.3522, 48.8566], + 'south korea': [127.7669, 35.9078], + 'korea': [127.7669, 35.9078], + 'seoul': [126.9780, 37.5665] + }; - const getCoords = (name) => { - const lowerName = (name || '').toLowerCase().trim(); - if (countryCoords[lowerName]) return countryCoords[lowerName]; - - const s = servers.find(sv => - (sv.countryName || '').toLowerCase() === lowerName || - (sv.country || '').toLowerCase() === lowerName || - (sv.city || '').toLowerCase() === lowerName - ); - if (s && s.lng && s.lat) return [shiftLng(s.lng), s.lat]; - return null; - }; + const getCoords = (name) => { + const lowerName = (name || '').toLowerCase().trim(); + if (countryCoords[lowerName]) return countryCoords[lowerName]; - const getShiftedCoords = (name) => { - const c = countryCoords[(name || '').toLowerCase().trim()]; - if (c) return [shiftLng(c[0]), c[1]]; - const raw = getCoords(name); - if (raw) return [shiftLng(raw[0]), raw[1]]; - return null; - }; + const s = servers.find(sv => + (sv.countryName || '').toLowerCase() === lowerName || + (sv.country || '').toLowerCase() === lowerName || + (sv.city || '').toLowerCase() === lowerName + ); + if (s && s.lng && s.lat) return [shiftLng(s.lng), s.lat]; + return null; + }; - // Group latency routes by path to handle overlap visually - const routeGroups = {}; - currentLatencies.forEach(route => { - const start = getShiftedCoords(route.source); - const end = getShiftedCoords(route.dest); - if (start && end) { - // Canonical points for grouping (independent of direction) - // Sort by longitude then latitude - const pts = [start, end].slice().sort((a, b) => a[0] - b[0] || a[1] - b[1]); - const key = `${pts[0][0].toFixed(4)},${pts[0][1].toFixed(4)}_${pts[1][0].toFixed(4)},${pts[1][1].toFixed(4)}`; - if (!routeGroups[key]) routeGroups[key] = []; - routeGroups[key].push({ route, start, end }); - } - }); + const getShiftedCoords = (name) => { + const c = countryCoords[(name || '').toLowerCase().trim()]; + if (c) return [shiftLng(c[0]), c[1]]; + const raw = getCoords(name); + if (raw) return [shiftLng(raw[0]), raw[1]]; + return null; + }; - Object.keys(routeGroups).forEach(key => { - const routes = routeGroups[key]; - const count = routes.length; - - routes.forEach((item, i) => { - const { route, start, end } = item; - - // Identify if the route is in the "canonical" direction - const pts = [start, end].slice().sort((a, b) => a[0] - b[0] || a[1] - b[1]); - const isForward = (start === pts[0]); - - // Calculate curveness: ensure all lines are slightly curved - let finalCurve = 0; - if (count === 1) { - finalCurve = 0.15; // Default slight curve for single lines - } else { - // Spread overlapping lines: 0.15, -0.15, 0.3, -0.3... - // This creates an "eye" or "fan" effect where no line is straight - const magnitude = 0.15 + Math.floor(i / 2) * 0.15; - const spread = (i % 2 === 0) ? magnitude : -magnitude; - // Adjust sign based on direction so they occupy unique visual slots - finalCurve = isForward ? spread : -spread; - } + // Group latency routes by path to handle overlap visually + const routeGroups = {}; + currentLatencies.forEach(route => { + const start = getShiftedCoords(route.source); + const end = getShiftedCoords(route.dest); + if (start && end) { + // Canonical points for grouping (independent of direction) + // Sort by longitude then latitude + const pts = [start, end].slice().sort((a, b) => a[0] - b[0] || a[1] - b[1]); + const key = `${pts[0][0].toFixed(4)},${pts[0][1].toFixed(4)}_${pts[1][0].toFixed(4)},${pts[1][1].toFixed(4)}`; + if (!routeGroups[key]) routeGroups[key] = []; + routeGroups[key].push({ route, start, end }); + } + }); - finalSeries.push({ - type: 'lines', - coordinateSystem: 'geo', - zlevel: 2, - latencyLine: true, - effect: { - show: true, - period: 4, - trailLength: 0.1, - color: 'rgba(99, 102, 241, 0.8)', - symbol: 'arrow', - symbolSize: 6 - }, - lineStyle: { - color: 'rgba(99, 102, 241, 0.3)', - width: 2, - curveness: finalCurve - }, - tooltip: { - formatter: () => { - const latVal = (route.latency !== null && route.latency !== undefined) ? `${route.latency.toFixed(2)} ms` : '测量中...'; - return ` + Object.keys(routeGroups).forEach(key => { + const routes = routeGroups[key]; + const count = routes.length; + + routes.forEach((item, i) => { + const { route, start, end } = item; + + // Identify if the route is in the "canonical" direction + const pts = [start, end].slice().sort((a, b) => a[0] - b[0] || a[1] - b[1]); + const isForward = (start === pts[0]); + + // Calculate curveness: ensure all lines are slightly curved + let finalCurve = 0; + if (count === 1) { + finalCurve = 0.15; // Default slight curve for single lines + } else { + // Spread overlapping lines: 0.15, -0.15, 0.3, -0.3... + // This creates an "eye" or "fan" effect where no line is straight + const magnitude = 0.15 + Math.floor(i / 2) * 0.15; + const spread = (i % 2 === 0) ? magnitude : -magnitude; + // Adjust sign based on direction so they occupy unique visual slots + finalCurve = isForward ? spread : -spread; + } + + finalSeries.push({ + type: 'lines', + coordinateSystem: 'geo', + zlevel: 2, + latencyLine: true, + effect: { + show: true, + period: 4, + trailLength: 0.1, + color: 'rgba(99, 102, 241, 0.8)', + symbol: 'arrow', + symbolSize: 6 + }, + lineStyle: { + color: 'rgba(99, 102, 241, 0.3)', + width: 2, + curveness: finalCurve + }, + tooltip: { + formatter: () => { + const latVal = (route.latency !== null && route.latency !== undefined) ? `${route.latency.toFixed(2)} ms` : '测量中...'; + return `
${route.source} ↔ ${route.dest}
延时: ${latVal}
`; - } - }, - data: [{ - fromName: route.source, - toName: route.dest, - coords: [start, end] - }] - }); - }); + } + }, + data: [{ + fromName: route.source, + toName: route.dest, + coords: [start, end] + }] + }); }); + }); } myMap2D.setOption({ - series: finalSeries + series: finalSeries }); // Update footer stats if (dom.globeTotalNodes) dom.globeTotalNodes.textContent = geoData.length; if (dom.globeTotalRegions) { - const regions = new Set(geoData.map(d => d.country)).size; - dom.globeTotalRegions.textContent = regions; + const regions = new Set(geoData.map(d => d.country)).size; + dom.globeTotalRegions.textContent = regions; } } @@ -1023,7 +1023,7 @@ // Update server table renderFilteredServers(); - + // Update globe updateMap2D(data.servers || []); @@ -1044,8 +1044,8 @@ // Apply search filter const searchTerm = (dom.serverSearchFilter?.value || '').toLowerCase().trim(); if (searchTerm) { - filtered = filtered.filter(s => - (s.job || '').toLowerCase().includes(searchTerm) || + filtered = filtered.filter(s => + (s.job || '').toLowerCase().includes(searchTerm) || (s.instance || '').toLowerCase().includes(searchTerm) ); } @@ -1282,11 +1282,11 @@ async function openServerDetail(instance, job, source) { // Cleanup old charts if any were still present from a previous open (safety) if (currentServerDetail.charts) { - Object.values(currentServerDetail.charts).forEach(chart => { - if (chart && chart.destroy) chart.destroy(); - }); + Object.values(currentServerDetail.charts).forEach(chart => { + if (chart && chart.destroy) chart.destroy(); + }); } - + currentServerDetail = { instance, job, source, charts: {} }; dom.serverDetailTitle.textContent = `${job}`; dom.serverDetailModal.classList.add('active'); @@ -1333,7 +1333,7 @@ // Disk Total if (dom.detailDiskTotal) { - dom.detailDiskTotal.textContent = formatBytes(data.totalDiskSize || 0); + dom.detailDiskTotal.textContent = formatBytes(data.totalDiskSize || 0); } // Define metrics to show @@ -1416,21 +1416,21 @@ // Handle partitions integration: Move the expandable partition section UNDER the Disk Usage metric if (data.partitions && data.partitions.length > 0) { - dom.detailPartitionsContainer.style.display = 'block'; - dom.partitionSummary.textContent = `${data.partitions.length} 个本地分区`; - - // Find the disk metric item and insert the partition container after it - const diskMetricItem = document.getElementById('metric-rootFsUsedPct'); - if (diskMetricItem) { - diskMetricItem.after(dom.detailPartitionsContainer); - } + dom.detailPartitionsContainer.style.display = 'block'; + dom.partitionSummary.textContent = `${data.partitions.length} 个本地分区`; - dom.partitionHeader.onclick = (e) => { - e.stopPropagation(); - dom.detailPartitionsContainer.classList.toggle('active'); - }; + // Find the disk metric item and insert the partition container after it + const diskMetricItem = document.getElementById('metric-rootFsUsedPct'); + if (diskMetricItem) { + diskMetricItem.after(dom.detailPartitionsContainer); + } - dom.detailPartitionsList.innerHTML = data.partitions.map(p => ` + dom.partitionHeader.onclick = (e) => { + e.stopPropagation(); + dom.detailPartitionsContainer.classList.toggle('active'); + }; + + dom.detailPartitionsList.innerHTML = data.partitions.map(p => `
${escapeHtml(p.mountpoint)} @@ -1442,7 +1442,7 @@
`).join(''); } else { - dom.detailPartitionsContainer.style.display = 'none'; + dom.detailPartitionsContainer.style.display = 'none'; } } @@ -1507,20 +1507,20 @@ const data = await res.json(); if (metricKey === 'networkTrend' && data.stats) { - const stats = data.stats; - const summaryDiv = document.getElementById(`summary-${metricKey}`); - if (summaryDiv) { - summaryDiv.style.display = 'flex'; - const rxEl = document.getElementById(`stat-${metricKey}-rx`); - const txEl = document.getElementById(`stat-${metricKey}-tx`); - const p95El = document.getElementById(`stat-${metricKey}-p95`); - const totalEl = document.getElementById(`stat-${metricKey}-total`); - - if (rxEl) rxEl.textContent = formatBytes(stats.rxTotal); - if (txEl) txEl.textContent = formatBytes(stats.txTotal); - if (p95El) p95El.textContent = formatBandwidth(stats.p95); - if (totalEl) totalEl.textContent = formatBytes(stats.total); - } + const stats = data.stats; + const summaryDiv = document.getElementById(`summary-${metricKey}`); + if (summaryDiv) { + summaryDiv.style.display = 'flex'; + const rxEl = document.getElementById(`stat-${metricKey}-rx`); + const txEl = document.getElementById(`stat-${metricKey}-tx`); + const p95El = document.getElementById(`stat-${metricKey}-p95`); + const totalEl = document.getElementById(`stat-${metricKey}-total`); + + if (rxEl) rxEl.textContent = formatBytes(stats.rxTotal); + if (txEl) txEl.textContent = formatBytes(stats.txTotal); + if (p95El) p95El.textContent = formatBandwidth(stats.p95); + if (totalEl) totalEl.textContent = formatBytes(stats.total); + } } chart.setData(data); @@ -1582,7 +1582,7 @@ hideMessage(); hideSiteMessage(); hideChangePasswordMessage(); - + // Reset password fields if (dom.oldPasswordInput) dom.oldPasswordInput.value = ''; if (dom.newPasswordInput) dom.newPasswordInput.value = ''; @@ -1617,7 +1617,7 @@ if (networkChart) { networkChart.showP95 = !!settings.show_95_bandwidth; networkChart.p95Type = settings.p95_type || 'tx'; - + if (dom.legendP95) { dom.legendP95.classList.toggle('disabled', !networkChart.showP95); } @@ -1639,9 +1639,9 @@ // Listen for system theme changes if set to auto (cleanup existing listener first) if (siteThemeQuery && siteThemeHandler) { - siteThemeQuery.removeEventListener('change', siteThemeHandler); + siteThemeQuery.removeEventListener('change', siteThemeHandler); } - + siteThemeQuery = window.matchMedia('(prefers-color-scheme: light)'); siteThemeHandler = () => { const currentSavedTheme = localStorage.getItem('theme'); @@ -1685,22 +1685,22 @@ `; } - + // P95 setting if (settings.show_95_bandwidth !== undefined || settings.p95_type !== undefined) { if (networkChart) { if (settings.show_95_bandwidth !== undefined) { - networkChart.showP95 = !!settings.show_95_bandwidth; - if (dom.legendP95) { - dom.legendP95.classList.toggle('disabled', !networkChart.showP95); - } + networkChart.showP95 = !!settings.show_95_bandwidth; + if (dom.legendP95) { + dom.legendP95.classList.toggle('disabled', !networkChart.showP95); + } } if (settings.p95_type !== undefined) { - networkChart.p95Type = settings.p95_type; - if (dom.p95LabelText) { - const types = { tx: '上行', rx: '下行', both: '上行+下行' }; - dom.p95LabelText.textContent = types[settings.p95_type] || '上行'; - } + networkChart.p95Type = settings.p95_type; + if (dom.p95LabelText) { + const types = { tx: '上行', rx: '下行', both: '上行+下行' }; + dom.p95LabelText.textContent = types[settings.p95_type] || '上行'; + } } networkChart.draw(); } @@ -1785,30 +1785,30 @@ `).join(''); } - window.editRoute = function(id, source_id, source, dest, target) { - editingRouteId = id; - dom.routeSourceSelect.value = source_id; - dom.routeSourceInput.value = source; - dom.routeDestInput.value = dest; - dom.routeTargetInput.value = target; - - dom.btnAddRoute.textContent = '保存修改'; - dom.btnCancelEditRoute.style.display = 'block'; - - // Select the tab just in case (though it's already there) - const tab = Array.from(dom.modalTabs).find(t => t.dataset.tab === 'routes'); - if (tab) tab.click(); + window.editRoute = function (id, source_id, source, dest, target) { + editingRouteId = id; + dom.routeSourceSelect.value = source_id; + dom.routeSourceInput.value = source; + dom.routeDestInput.value = dest; + dom.routeTargetInput.value = target; + + dom.btnAddRoute.textContent = '保存修改'; + dom.btnCancelEditRoute.style.display = 'block'; + + // Select the tab just in case (though it's already there) + const tab = Array.from(dom.modalTabs).find(t => t.dataset.tab === 'routes'); + if (tab) tab.click(); }; function cancelEditRoute() { - editingRouteId = null; - dom.routeSourceSelect.value = ""; - dom.routeSourceInput.value = ""; - dom.routeDestInput.value = ""; - dom.routeTargetInput.value = ""; - - dom.btnAddRoute.textContent = '添加线路'; - dom.btnCancelEditRoute.style.display = 'none'; + editingRouteId = null; + dom.routeSourceSelect.value = ""; + dom.routeSourceInput.value = ""; + dom.routeDestInput.value = ""; + dom.routeTargetInput.value = ""; + + dom.btnAddRoute.textContent = '添加线路'; + dom.btnCancelEditRoute.style.display = 'none'; } async function addLatencyRoute() { @@ -1853,7 +1853,7 @@ } } - window.deleteLatencyRoute = async function(id) { + window.deleteLatencyRoute = async function (id) { if (!user) { showSiteMessage('请登录后再操作', 'error'); openLoginModal(); @@ -1973,10 +1973,10 @@ } if (dom.routeSourceSelect) { - const currentVal = dom.routeSourceSelect.value; - dom.routeSourceSelect.innerHTML = '' + - sources.map(s => ``).join(''); - dom.routeSourceSelect.value = currentVal; + const currentVal = dom.routeSourceSelect.value; + dom.routeSourceSelect.innerHTML = '' + + sources.map(s => ``).join(''); + dom.routeSourceSelect.value = currentVal; } }