From d595397f088c2d0f866f4a4150d1b9cc4a16a65a Mon Sep 17 00:00:00 2001 From: CN-JS-HuiBai Date: Sun, 5 Apr 2026 17:12:48 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/css/style.css | 28 +++++++++++ public/index.html | 10 +++- public/js/app.js | 113 ++++++++++++++++++++++++++++++++++--------- 3 files changed, 128 insertions(+), 23 deletions(-) diff --git a/public/css/style.css b/public/css/style.css index 9538b26..51752d9 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -646,6 +646,34 @@ input:checked+.slider:before { background: rgba(99, 102, 241, 0.05); } +.btn-icon-sm { + background: var(--bg-input); + border: 1px solid var(--border-color); + border-radius: var(--radius-sm); + width: 32px; + height: 32px; + padding: 0; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-muted); + cursor: pointer; + transition: all 0.2s ease; + flex-shrink: 0; +} + +.btn-icon-sm:hover { + border-color: var(--border-hover); + color: var(--accent-indigo); + background: rgba(99, 102, 241, 0.05); + transform: rotate(-15deg); +} + +.btn-icon-sm svg { + width: 16px; + height: 16px; +} + .chart-title { display: flex; align-items: center; diff --git a/public/index.html b/public/index.html index 10ecfff..1006168 100644 --- a/public/index.html +++ b/public/index.html @@ -242,7 +242,8 @@

- + 全球服务器分布

@@ -279,6 +280,13 @@ 服务器详情
+ diff --git a/public/js/app.js b/public/js/app.js index c6a34a8..402620a 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -82,7 +82,8 @@ globeContainer: document.getElementById('globeContainer'), globeTotalNodes: document.getElementById('globeTotalNodes'), globeTotalRegions: document.getElementById('globeTotalRegions'), - sourceFilter: document.getElementById('sourceFilter') + sourceFilter: document.getElementById('sourceFilter'), + btnResetSort: document.getElementById('btnResetSort') }; // ---- State ---- @@ -94,7 +95,18 @@ let currentSourceFilter = 'all'; let currentPage = 1; let pageSize = 20; + + // Load sort state from localStorage or use default let currentSort = { column: 'up', direction: 'desc' }; + try { + const savedSort = localStorage.getItem('serverListSort'); + if (savedSort) { + currentSort = JSON.parse(savedSort); + } + } catch (e) { + console.warn('Failed to load sort state', e); + } + let myMap2D = null; // ---- Initialize ---- @@ -177,6 +189,19 @@ // Server table header sorting const tableHeader = document.querySelector('.server-table thead'); if (tableHeader) { + // Sync UI with initial state + const initialHeaders = tableHeader.querySelectorAll('th.sortable'); + initialHeaders.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'); + } + }); + tableHeader.addEventListener('click', (e) => { const th = e.target.closest('th.sortable'); if (th) { @@ -213,6 +238,11 @@ }); } + // Reset sort listener + if (dom.btnResetSort) { + dom.btnResetSort.addEventListener('click', resetSort); + } + // Check auth status checkAuthStatus(); @@ -576,27 +606,34 @@ } 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; + const val = a.up ? -1 : 1; + return currentSort.direction === 'asc' ? -val : val; } } // Secondary sort based on user choice let valA, valB; - switch (currentSort.column) { + const col = currentSort.column; + const dir = currentSort.direction; + + switch (col) { case 'up': - return 0; // Already handled above - case 'job': + // If we reached here, status is the same (both online or both offline) + // Fall back to job name sorting valA = a.job || ''; valB = b.job || ''; - return currentSort.direction === 'asc' ? valA.localeCompare(valB) : valB.localeCompare(valA); + return valA.localeCompare(valB); + case 'job': + valA = (a.job || '').toLowerCase(); + valB = (b.job || '').toLowerCase(); + return dir === '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); + valA = (a.source || '').toLowerCase(); + valB = (b.source || '').toLowerCase(); + return dir === 'asc' ? valA.localeCompare(valB) : valB.localeCompare(valA); case 'cpu': - valA = a.cpuPercent || 0; - valB = b.cpuPercent || 0; + valA = a.cpuPercent ?? 0; + valB = b.cpuPercent ?? 0; break; case 'mem': valA = a.memTotal > 0 ? (a.memUsed / a.memTotal) : 0; @@ -607,21 +644,25 @@ valB = b.diskTotal > 0 ? (b.diskUsed / b.diskTotal) : 0; break; case 'netRx': - valA = a.netRx || 0; - valB = b.netRx || 0; + valA = a.netRx ?? 0; + valB = b.netRx ?? 0; break; case 'netTx': - valA = a.netTx || 0; - valB = b.netTx || 0; + valA = a.netTx ?? 0; + valB = b.netTx ?? 0; break; default: - valA = a.job || ''; - valB = b.job || ''; + valA = (a.job || '').toLowerCase(); + valB = (b.job || '').toLowerCase(); return valA.localeCompare(valB); } - if (currentSort.direction === 'asc') return valA - valB; - return valB - valA; + // Numeric comparison + if (valA === valB) { + // If values are same, secondary fallback to job name for stable sort + return (a.job || '').localeCompare(b.job || ''); + } + return dir === 'asc' ? valA - valB : valB - valA; }); const totalFiltered = filtered.length; @@ -668,15 +709,43 @@ renderFilteredServers(); }; + function resetSort() { + currentSort = { column: 'up', direction: 'desc' }; + localStorage.removeItem('serverListSort'); + + // 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(); + } + function handleHeaderSort(column) { if (currentSort.column === column) { - // Toggle direction - currentSort.direction = currentSort.direction === 'asc' ? 'desc' : 'asc'; + if (currentSort.direction === 'desc') { + currentSort.direction = 'asc'; + } else { + // Cycle back to default (Status desc) + resetSort(); + return; + } } else { currentSort.column = column; currentSort.direction = 'desc'; // Default to desc for most metrics } + // Persist sort state + localStorage.setItem('serverListSort', JSON.stringify(currentSort)); + // Update UI headers const headers = document.querySelectorAll('.server-table th.sortable'); headers.forEach(th => {