From dc865c6d9d245f7d7a7fe67418062eb79728749b Mon Sep 17 00:00:00 2001 From: CN-JS-HuiBai Date: Sun, 5 Apr 2026 17:05:15 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=9C=B0=E7=90=86=E4=BD=8D?= =?UTF-8?q?=E7=BD=AE=E6=BC=82=E7=A7=BB=E7=9A=84BUG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/css/style.css | 46 +++++++++++++++++++ public/index.html | 16 +++---- public/js/app.js | 104 ++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 152 insertions(+), 14 deletions(-) diff --git a/public/css/style.css b/public/css/style.css index c7b2fb6..9538b26 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -877,6 +877,52 @@ input:checked+.slider:before { color: var(--text-muted); border-bottom: 1px solid var(--border-color); background: rgba(0, 0, 0, 0.2); + transition: all 0.2s ease; +} + +.server-table th.sortable { + cursor: pointer; + position: relative; + user-select: none; +} + +.server-table th.sortable:hover { + background: rgba(99, 102, 241, 0.08); + color: var(--text-primary); +} + +.server-table th.sortable.active { + color: var(--accent-indigo); + background: rgba(99, 102, 241, 0.05); + border-bottom-color: var(--accent-indigo); +} + +.sort-icon { + display: inline-block; + width: 12px; + height: 12px; + vertical-align: middle; + margin-left: 4px; + opacity: 0.3; + transition: all 0.2s ease; + position: relative; +} + +.server-table th.sortable.active .sort-icon { + opacity: 1; +} + +.sort-icon::after { + content: '↕'; + font-size: 0.8rem; +} + +.server-table th.sortable.active[data-dir="asc"] .sort-icon::after { + content: '↑'; +} + +.server-table th.sortable.active[data-dir="desc"] .sort-icon::after { + content: '↓'; } .server-table td { diff --git a/public/index.html b/public/index.html index e1a56c1..10ecfff 100644 --- a/public/index.html +++ b/public/index.html @@ -288,14 +288,14 @@ - - - - - - - - + + + + + + + + diff --git a/public/js/app.js b/public/js/app.js index 87f483a..c6a34a8 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -94,6 +94,7 @@ let currentSourceFilter = 'all'; let currentPage = 1; let pageSize = 20; + let currentSort = { column: 'up', direction: 'desc' }; let myMap2D = null; // ---- Initialize ---- @@ -161,6 +162,7 @@ // Server table row click delegator dom.serverTableBody.addEventListener('click', (e) => { + // Don't trigger detail if clicking a button or something interactive inside (none currently) const row = e.target.closest('tr'); if (row && !row.classList.contains('empty-row')) { const instance = row.getAttribute('data-instance'); @@ -172,6 +174,18 @@ } }); + // Server table header sorting + const tableHeader = document.querySelector('.server-table thead'); + if (tableHeader) { + tableHeader.addEventListener('click', (e) => { + const th = e.target.closest('th.sortable'); + if (th) { + const column = th.getAttribute('data-sort'); + handleHeaderSort(column); + } + }); + } + // P95 Toggle if (dom.legendP95) { dom.legendP95.addEventListener('click', () => { @@ -428,6 +442,7 @@ series: [{ type: 'effectScatter', coordinateSystem: 'geo', + geoIndex: 0, showEffectOn: 'render', rippleEffect: { brushType: 'stroke', scale: 4, period: 4 }, symbolSize: 6, @@ -485,7 +500,11 @@ })); myMap2D.setOption({ - series: [{ data: geoData }] + series: [{ + coordinateSystem: 'geo', + geoIndex: 0, + data: geoData + }] }); // Update footer stats @@ -549,12 +568,60 @@ filtered = allServersData.filter(s => s.source === currentSourceFilter); } - // Sort servers: online first, then alphabetically by name (job) + // Sort servers: online first, then by currentSort filtered.sort((a, b) => { - if (a.up !== b.up) return a.up ? -1 : 1; - const nameA = a.job || ''; - const nameB = b.job || ''; - return nameA.localeCompare(nameB); + // Primary sort: Always put online servers first unless sorting by 'up' explicitly + if (currentSort.column !== 'up') { + if (a.up !== b.up) return a.up ? -1 : 1; + } else { + // Specifically sorting by status: Online vs Offline + if (a.up !== b.up) { + const val = a.up ? -1 : 1; + return currentSort.direction === 'asc' ? -val : val; + } + } + + // Secondary sort based on user choice + let valA, valB; + switch (currentSort.column) { + case 'up': + return 0; // Already handled above + case 'job': + valA = a.job || ''; + valB = b.job || ''; + return currentSort.direction === 'asc' ? valA.localeCompare(valB) : valB.localeCompare(valA); + case 'source': + valA = a.source || ''; + valB = b.source || ''; + return currentSort.direction === 'asc' ? valA.localeCompare(valB) : valB.localeCompare(valA); + case 'cpu': + valA = a.cpuPercent || 0; + valB = b.cpuPercent || 0; + break; + case 'mem': + valA = a.memTotal > 0 ? (a.memUsed / a.memTotal) : 0; + valB = b.memTotal > 0 ? (b.memUsed / b.memTotal) : 0; + break; + case 'disk': + valA = a.diskTotal > 0 ? (a.diskUsed / a.diskTotal) : 0; + valB = b.diskTotal > 0 ? (b.diskUsed / b.diskTotal) : 0; + break; + case 'netRx': + valA = a.netRx || 0; + valB = b.netRx || 0; + break; + case 'netTx': + valA = a.netTx || 0; + valB = b.netTx || 0; + break; + default: + valA = a.job || ''; + valB = b.job || ''; + return valA.localeCompare(valB); + } + + if (currentSort.direction === 'asc') return valA - valB; + return valB - valA; }); const totalFiltered = filtered.length; @@ -601,6 +668,31 @@ renderFilteredServers(); }; + function handleHeaderSort(column) { + if (currentSort.column === column) { + // Toggle direction + currentSort.direction = currentSort.direction === 'asc' ? 'desc' : 'asc'; + } else { + currentSort.column = column; + currentSort.direction = 'desc'; // Default to desc for most metrics + } + + // Update UI headers + const headers = document.querySelectorAll('.server-table th.sortable'); + headers.forEach(th => { + const col = th.getAttribute('data-sort'); + if (col === currentSort.column) { + th.classList.add('active'); + th.setAttribute('data-dir', currentSort.direction); + } else { + th.classList.remove('active'); + th.removeAttribute('data-dir'); + } + }); + + renderFilteredServers(); + } + // ---- Server Table ---- function updateServerTable(servers) { if (!servers || servers.length === 0) {
状态Job / 实例数据源CPU内存磁盘网络 ↓网络 ↑状态 Job / 实例 数据源 CPU 内存 磁盘 网络 ↓ 网络 ↑