diff --git a/public/js/app.js b/public/js/app.js index 21fabc0..30f80ab 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -161,7 +161,7 @@ networkChart.draw(); }); } - + // Source filter listener if (dom.sourceFilter) { dom.sourceFilter.addEventListener('change', () => { @@ -346,22 +346,22 @@ // ---- Update Dashboard ---- function updateDashboard(data) { // Server count - dom.totalServers.textContent = `${data.activeServers} / ${data.totalServers}`; + dom.totalServers.textContent = `${data.activeServers}/${data.totalServers}`; // CPU const cpuPct = data.cpu.percent; dom.cpuPercent.textContent = formatPercent(cpuPct); - dom.cpuDetail.textContent = `${data.cpu.used.toFixed(1)} / ${data.cpu.total.toFixed(0)} 核心`; + dom.cpuDetail.textContent = `${data.cpu.used.toFixed(1)}/${data.cpu.total.toFixed(0)} 核心`; // Memory const memPct = data.memory.percent; dom.memPercent.textContent = formatPercent(memPct); - dom.memDetail.textContent = `${formatBytes(data.memory.used)} / ${formatBytes(data.memory.total)}`; + dom.memDetail.textContent = `${formatBytes(data.memory.used)}/${formatBytes(data.memory.total)}`; // Disk const diskPct = data.disk.percent; dom.diskPercent.textContent = formatPercent(diskPct); - dom.diskDetail.textContent = `${formatBytes(data.disk.used)} / ${formatBytes(data.disk.total)}`; + dom.diskDetail.textContent = `${formatBytes(data.disk.used)}/${formatBytes(data.disk.total)}`; // Bandwidth dom.totalBandwidth.textContent = formatBandwidth(data.network.total || 0); @@ -392,7 +392,7 @@ if (currentSourceFilter !== 'all') { filtered = allServersData.filter(s => s.source === currentSourceFilter); } - + // Sort servers: online first, then alphabetically by name (job) filtered.sort((a, b) => { if (a.up !== b.up) return a.up ? -1 : 1; @@ -403,7 +403,7 @@ const totalFiltered = filtered.length; const totalPages = Math.ceil(totalFiltered / pageSize) || 1; - + if (currentPage > totalPages) currentPage = totalPages; const startIndex = (currentPage - 1) * pageSize; @@ -415,7 +415,7 @@ function renderPagination(totalPages) { if (!dom.paginationControls) return; - + if (totalPages <= 1) { dom.paginationControls.innerHTML = ''; return; @@ -424,25 +424,25 @@ let html = ''; // Previous button html += ``; - + // Page numbers for (let i = 1; i <= totalPages; i++) { - if (i === 1 || i === totalPages || (i >= currentPage - 2 && i <= currentPage + 2)) { - html += ``; - } else if (i === currentPage - 3 || i === currentPage + 3) { - html += `...`; - } + if (i === 1 || i === totalPages || (i >= currentPage - 2 && i <= currentPage + 2)) { + html += ``; + } else if (i === currentPage - 3 || i === currentPage + 3) { + html += `...`; + } } // Next button html += ``; - + dom.paginationControls.innerHTML = html; } - window.changePage = function(page) { - currentPage = page; - renderFilteredServers(); + window.changePage = function (page) { + currentPage = page; + renderFilteredServers(); }; // ---- Server Table ---- @@ -548,24 +548,22 @@ // Define metrics to show const cpuValueHtml = ` -
- ${formatPercent(data.cpuBusy)} -
- I/O Wait: ${data.cpuIowait.toFixed(1)}% -
+
+ ${formatPercent(data.cpuBusy)} + (IO Wait: ${data.cpuIowait.toFixed(1)}%)
`; const metrics = [ - { key: 'cpuBusy', label: 'CPU 使用率 (Busy / IO Wait)', value: cpuValueHtml }, - { key: 'sysLoad', label: '系统负载 (Load)', value: data.sysLoad.toFixed(1) + '%' }, - { key: 'memUsedPct', label: '内存使用率 (RAM)', value: formatPercent(data.memUsedPct) }, - { key: 'swapUsedPct', label: 'SWAP 使用率', value: formatPercent(data.swapUsedPct) }, - { key: 'rootFsUsedPct', label: '根分区使用率 (/)', value: formatPercent(data.rootFsUsedPct) }, - { key: 'netRx', label: '网络接收速率 (RX)', value: formatBandwidth(data.netRx) }, - { key: 'netTx', label: '网络发送速率 (TX)', value: formatBandwidth(data.netTx) }, - { key: 'sockstatTcp', label: 'TCP 链接数 (Sockstat)', value: data.sockstatTcp.toFixed(0) }, - { key: 'sockstatTcpMem', label: 'TCP 内存占用', value: formatBytes(data.sockstatTcpMem) } + { key: 'cpuBusy', label: 'CPU 使用率', value: cpuValueHtml }, + { key: 'sysLoad', label: '系统负载 (Load)', value: data.sysLoad.toFixed(1) + '%' }, + { key: 'memUsedPct', label: '内存使用率 (RAM)', value: formatPercent(data.memUsedPct) }, + { key: 'swapUsedPct', label: 'SWAP 使用率', value: formatPercent(data.swapUsedPct) }, + { key: 'rootFsUsedPct', label: '根分区使用率 (/)', value: formatPercent(data.rootFsUsedPct) }, + { key: 'netRx', label: '网络接收速率 (RX)', value: formatBandwidth(data.netRx) }, + { key: 'netTx', label: '网络发送速率 (TX)', value: formatBandwidth(data.netTx) }, + { key: 'sockstatTcp', label: 'TCP 链接数 (Sockstat)', value: data.sockstatTcp.toFixed(0) }, + { key: 'sockstatTcpMem', label: 'TCP 内存占用', value: formatBytes(data.sockstatTcpMem) } ]; dom.detailMetricsList.innerHTML = metrics.map(m => ` @@ -642,7 +640,7 @@ if (metricKey.includes('Pct') || metricKey === 'cpuBusy') unit = '%'; if (metricKey.startsWith('net')) unit = 'B/s'; if (metricKey === 'sockstatTcpMem') unit = 'B'; - + chart = new MetricChart(canvas, unit); currentServerDetail.charts[metricKey] = chart; } @@ -660,6 +658,14 @@ const res = await fetch(url); if (!res.ok) throw new Error('Query failed'); const data = await res.json(); + + if (metricKey === 'cpuBusy' && data.series) { + // Simplify: ONLY show total busy CPU usage (everything except idle) + // Since it's a percentage, 100 - idle is the total busy percentage + data.values = data.series.idle.map(idleVal => Math.max(0, 100 - idleVal)); + data.series = null; // This tells MetricChart to draw a single line instead of stacked area + } + chart.setData(data); } catch (err) { console.error(`Error loading history for ${metricKey}:`, err);