修复地理位置漂移的BUG
This commit is contained in:
@@ -877,6 +877,52 @@ input:checked+.slider:before {
|
|||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
border-bottom: 1px solid var(--border-color);
|
border-bottom: 1px solid var(--border-color);
|
||||||
background: rgba(0, 0, 0, 0.2);
|
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 {
|
.server-table td {
|
||||||
|
|||||||
@@ -288,14 +288,14 @@
|
|||||||
<table class="server-table" id="serverTable">
|
<table class="server-table" id="serverTable">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>状态</th>
|
<th class="sortable active" data-sort="up">状态 <span class="sort-icon"></span></th>
|
||||||
<th>Job / 实例</th>
|
<th class="sortable" data-sort="job">Job / 实例 <span class="sort-icon"></span></th>
|
||||||
<th>数据源</th>
|
<th class="sortable" data-sort="source">数据源 <span class="sort-icon"></span></th>
|
||||||
<th>CPU</th>
|
<th class="sortable" data-sort="cpu">CPU <span class="sort-icon"></span></th>
|
||||||
<th>内存</th>
|
<th class="sortable" data-sort="mem">内存 <span class="sort-icon"></span></th>
|
||||||
<th>磁盘</th>
|
<th class="sortable" data-sort="disk">磁盘 <span class="sort-icon"></span></th>
|
||||||
<th>网络 ↓</th>
|
<th class="sortable" data-sort="netRx">网络 ↓ <span class="sort-icon"></span></th>
|
||||||
<th>网络 ↑</th>
|
<th class="sortable" data-sort="netTx">网络 ↑ <span class="sort-icon"></span></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="serverTableBody">
|
<tbody id="serverTableBody">
|
||||||
|
|||||||
104
public/js/app.js
104
public/js/app.js
@@ -94,6 +94,7 @@
|
|||||||
let currentSourceFilter = 'all';
|
let currentSourceFilter = 'all';
|
||||||
let currentPage = 1;
|
let currentPage = 1;
|
||||||
let pageSize = 20;
|
let pageSize = 20;
|
||||||
|
let currentSort = { column: 'up', direction: 'desc' };
|
||||||
let myMap2D = null;
|
let myMap2D = null;
|
||||||
|
|
||||||
// ---- Initialize ----
|
// ---- Initialize ----
|
||||||
@@ -161,6 +162,7 @@
|
|||||||
|
|
||||||
// Server table row click delegator
|
// Server table row click delegator
|
||||||
dom.serverTableBody.addEventListener('click', (e) => {
|
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');
|
const row = e.target.closest('tr');
|
||||||
if (row && !row.classList.contains('empty-row')) {
|
if (row && !row.classList.contains('empty-row')) {
|
||||||
const instance = row.getAttribute('data-instance');
|
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
|
// P95 Toggle
|
||||||
if (dom.legendP95) {
|
if (dom.legendP95) {
|
||||||
dom.legendP95.addEventListener('click', () => {
|
dom.legendP95.addEventListener('click', () => {
|
||||||
@@ -428,6 +442,7 @@
|
|||||||
series: [{
|
series: [{
|
||||||
type: 'effectScatter',
|
type: 'effectScatter',
|
||||||
coordinateSystem: 'geo',
|
coordinateSystem: 'geo',
|
||||||
|
geoIndex: 0,
|
||||||
showEffectOn: 'render',
|
showEffectOn: 'render',
|
||||||
rippleEffect: { brushType: 'stroke', scale: 4, period: 4 },
|
rippleEffect: { brushType: 'stroke', scale: 4, period: 4 },
|
||||||
symbolSize: 6,
|
symbolSize: 6,
|
||||||
@@ -485,7 +500,11 @@
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
myMap2D.setOption({
|
myMap2D.setOption({
|
||||||
series: [{ data: geoData }]
|
series: [{
|
||||||
|
coordinateSystem: 'geo',
|
||||||
|
geoIndex: 0,
|
||||||
|
data: geoData
|
||||||
|
}]
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update footer stats
|
// Update footer stats
|
||||||
@@ -549,12 +568,60 @@
|
|||||||
filtered = allServersData.filter(s => s.source === currentSourceFilter);
|
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) => {
|
filtered.sort((a, b) => {
|
||||||
if (a.up !== b.up) return a.up ? -1 : 1;
|
// Primary sort: Always put online servers first unless sorting by 'up' explicitly
|
||||||
const nameA = a.job || '';
|
if (currentSort.column !== 'up') {
|
||||||
const nameB = b.job || '';
|
if (a.up !== b.up) return a.up ? -1 : 1;
|
||||||
return nameA.localeCompare(nameB);
|
} 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;
|
const totalFiltered = filtered.length;
|
||||||
@@ -601,6 +668,31 @@
|
|||||||
renderFilteredServers();
|
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 ----
|
// ---- Server Table ----
|
||||||
function updateServerTable(servers) {
|
function updateServerTable(servers) {
|
||||||
if (!servers || servers.length === 0) {
|
if (!servers || servers.length === 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user