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;
}
}