diff --git a/plugins/sentinel/dashboard.html b/plugins/sentinel/dashboard.html
index f3d4c9a..825b7ed 100644
--- a/plugins/sentinel/dashboard.html
+++ b/plugins/sentinel/dashboard.html
@@ -88,14 +88,13 @@
var pollTimer = null;
var _chartInstances = {};
var editingId = null;
- var _layoutMode = 'grid'; // 'grid' | 'row'
+ var _layoutMode = 'grid'; // 'grid' | 'row'
function toggleLayout() {
_layoutMode = _layoutMode === 'grid' ? 'row' : 'grid';
var btn = document.getElementById("btn-layout");
var grid = document.getElementById("sentinel-grid");
// 销毁旧图表
- Object.keys(_chartInstances).forEach(function(k){ if(_chartInstances[k]) _chartInstances[k].forEach(function(c){ if(c.destroy)c.destroy(); }); });
_chartInstances = {};
_cardCache = {};
_firstRender = true;
@@ -123,8 +122,7 @@
}
// ═══ 仪表盘 ═══
- Object.keys(_chartInstances).forEach(function(k){ if(_chartInstances[k]) _chartInstances[k].forEach(function(c){ if(c.destroy)c.destroy(); }); });
- _chartInstances = {};
+
var _cardCache = {};
function animateValue(el, oldVal, newVal, decimals, suffix) {
@@ -148,15 +146,14 @@
var grid = document.getElementById("sentinel-grid");
if (!nodes.length) {
grid.innerHTML = '
暂无节点 — 切换到节点管理添加
';
- Object.keys(_chartInstances).forEach(function(k){ if(_chartInstances[k]) _chartInstances[k].forEach(function(c){ if(c.destroy)c.destroy(); }); });
- _chartInstances = {};
_cardCache = {};
_firstRender = true;
return;
}
- // 首次渲染清除占位
if (_firstRender) { grid.innerHTML = ''; _firstRender = false; }
- // 清理已删除的节点
+ var isRow = _layoutMode === 'row';
+
+ // 清理删除的节点
var activeIds = {};
nodes.forEach(function(n){ activeIds[n.id||n.name] = true; });
Object.keys(_cardCache).forEach(function(id){
@@ -165,132 +162,148 @@
nodes.forEach(function(n){
var sys = n.system || {};
- var cpu = sys.cpu || {};
- var mem = sys.memory || {};
+ var cpu = sys.cpu || {}; var cpuVal = cpu.percent || 0;
+ var mem = sys.memory || {}; var memVal = mem.percent || 0;
var net = sys.net_speed || {};
var id = n.id || n.name;
- var cpuVal = cpu.percent || 0;
- var memVal = mem.percent || 0;
- var cpuBarCls = bar(cpuVal);
- var memBarCls = bar(memVal);
var card = _cardCache[id];
if (!card) {
- // 首次创建卡片
card = document.createElement('div');
card.className = 'node-card ' + (n.online ? 'online' : 'offline');
card.setAttribute('data-id', id);
var plat = n.platform || {};
var sysInfo = (plat.system||'') + (plat.release ? ' ' + plat.release : '') + (plat.machine ? ' | ' + plat.machine : '');
- var isRow = _layoutMode === 'row';
- card.innerHTML =
- (isRow ? '' : '') +
- '
'+(n.online?'🟢':'🔴')+' '+esc(n.name)+'
'+
- (sysInfo.trim() ? '
'+esc(sysInfo)+'
' : '')+
- '
'+esc(n.url||'')+'
'+
- (n.notes ? '
📝 '+esc(n.notes)+'
' : '') +
- (isRow ? '
' : '') +
- '
'+
- (isRow ? '
' : '');
+ if (isRow) {
+ card.innerHTML =
+ ''+
+ '
'+(n.online?'🟢':'🔴')+' '+esc(n.name)+'
'+
+ (sysInfo.trim() ? '
'+esc(sysInfo)+'
' : '')+
+ '
'+esc(n.url||'')+'
'+
+ (n.notes ? '
📝 '+esc(n.notes)+'
' : '')+
+ '
'+
+ ''+
+ '
'+
+ '
'+
+ '
'+
+ '
CPU 0%
'+
+ '
MEM 0%
'+
+ '
'+
+ '
'+
+ '
'+
+ '
'+
+ '';
+ if (typeof MiniChart !== 'undefined') {
+ var body = card.querySelector('.metrics-body');
+ _chartInstances[id] = [
+ new MiniChart(body.querySelector('.chart-cpu'), '#7aa2f7'),
+ new MiniChart(body.querySelector('.chart-mem'), '#9ece6a'),
+ new MiniChart(body.querySelector('.chart-net'), '#e0af68'),
+ ];
+ }
+ } else {
+ card.innerHTML =
+ ''+(n.online?'🟢':'🔴')+' '+esc(n.name)+'
'+
+ (sysInfo.trim() ? ''+esc(sysInfo)+'
' : '')+
+ ''+esc(n.url||'')+'
'+
+ (n.notes ? '📝 '+esc(n.notes)+'
' : '')+
+ ''+
+ '';
+ }
grid.appendChild(card);
_cardCache[id] = card;
}
- // 更新状态样式
+ // 更新通用信息
card.className = 'node-card ' + (n.online ? 'online' : 'offline');
- card.querySelector('.node-name').innerHTML = (n.online?'🟢':'🔴')+' '+esc(n.name);
+ var nameEl = card.querySelector('.node-name');
+ if (nameEl) nameEl.innerHTML = (n.online?'🟢':'🔴')+' '+esc(n.name);
+ var urlEl = card.querySelector('.node-url');
+ if (urlEl) urlEl.textContent = n.url||'';
var sysEl = card.querySelector('.node-sys');
- var plat = n.platform || {};
- var sysInfo = (plat.system||'') + (plat.release ? ' ' + plat.release : '') + (plat.machine ? ' | ' + plat.machine : '');
- if (sysInfo.trim()) {
- if (!sysEl) { sysEl = document.createElement('div'); sysEl.className = 'node-sys'; card.insertBefore(sysEl, card.querySelector('.node-url')); }
- sysEl.textContent = sysInfo;
+ var plat2 = n.platform || {};
+ var si2 = (plat2.system||'') + (plat2.release ? ' ' + plat2.release : '') + (plat2.machine ? ' | ' + plat2.machine : '');
+ if (si2.trim()) {
+ if (!sysEl) { sysEl = document.createElement('div'); sysEl.className = 'node-sys'; card.insertBefore(sysEl, card.querySelector('.metrics-body')||card.querySelector('.card-mid')); }
+ sysEl.textContent = si2;
} else if (sysEl) { sysEl.remove(); }
- card.querySelector('.node-url').textContent = n.url||'';
- var notesEl = card.querySelector('.node-notes');
- if (n.notes) {
- if (!notesEl) {
- notesEl = document.createElement('div'); notesEl.className = 'node-notes';
- card.insertBefore(notesEl, card.querySelector('.metrics-body'));
- }
- notesEl.innerHTML = '📝 '+esc(n.notes);
- } else if (notesEl) { notesEl.remove(); }
var body = card.querySelector('.metrics-body');
- if (n.online) {
- var oldCpu = parseFloat((body.getAttribute('data-cpu')||cpuVal));
- var oldMem = parseFloat((body.getAttribute('data-mem')||memVal));
+ if (!body) return;
+
+ if (isRow) {
+ // 更新图表
+ var charts = _chartInstances[id];
+ if (charts && charts[0]) {
+ charts[0].update(cpuVal);
+ charts[1].update(memVal);
+ charts[2].update(net.rx_bytes_sec||0);
+ }
+ // 更新数值
+ var ce = body.querySelector('.cpu-val');
+ var me = body.querySelector('.mem-val');
+ var md = body.querySelector('.mem-detail-val');
+ if (ce) ce.textContent = cpuVal.toFixed(1) + '%';
+ if (me) me.textContent = memVal.toFixed(1) + '%';
+ if (md) md.textContent = 'NET ↓'+fmtSpeed(net.rx_bytes_sec||0)+' ↑'+fmtSpeed(net.tx_bytes_sec||0);
+ // 右边数值
+ var right = card.querySelector('.card-right');
+ if (right) right.innerHTML =
+ 'CPU '+(cpuVal).toFixed(1)+'%
'+
+ 'MEM '+(memVal).toFixed(1)+'%
'+
+ ''+(mem.used_gb||0).toFixed(1)+'/'+(mem.total_gb||0).toFixed(1)+' GB
'+
+ '↓ '+fmtSpeed(net.rx_bytes_sec||0)+'
'+
+ '↑ '+fmtSpeed(net.tx_bytes_sec||0)+'
';
+ // 时间
+ var timeEl = card.querySelector('.node-time');
+ if (timeEl) timeEl.innerHTML = ''+(n.last_seen ? new Date(n.last_seen*1000).toLocaleTimeString() : '—')+'
';
+ } else {
+ // 网格模式
+ var oldCpu = parseFloat(body.getAttribute('data-cpu')||cpuVal);
+ var oldMem = parseFloat(body.getAttribute('data-mem')||memVal);
body.setAttribute('data-cpu', cpuVal);
body.setAttribute('data-mem', memVal);
var isNew = !body.querySelector('.cpu-val');
- var isRow = _layoutMode === 'row';
if (isNew) {
- if (isRow) {
- body.innerHTML =
- '';
- if (typeof MiniChart !== "undefined") {
- _chartInstances[id] = [
- new MiniChart(body.querySelector(".chart-cpu"), "#7aa2f7"),
- new MiniChart(body.querySelector(".chart-mem"), "#9ece6a"),
- new MiniChart(body.querySelector(".chart-net"), "#e0af68"),
- ];
- }
- } else {
- body.innerHTML =
- ''+
- '';
- }
+ body.innerHTML =
+ ''+
+ '';
}
-
- if (isRow) {
- var charts = _chartInstances[id];
- if (charts && charts[0]) {
- charts[0].update(cpuVal);
- charts[1].update(memVal);
- charts[2].update(net.rx_bytes_sec||0);
- }
- var right = card.querySelector('.card-right');
- if (right) right.innerHTML =
- 'CPU '+(cpuVal).toFixed(1)+'%
'+
- 'MEM '+(memVal).toFixed(1)+'%
'+
- ''+(mem.used_gb||0).toFixed(1)+'/'+(mem.total_gb||0).toFixed(1)+' GB
'+
- '↓ '+fmtSpeed(net.rx_bytes_sec||0)+'
'+
- '↑ '+fmtSpeed(net.tx_bytes_sec||0)+'
'+
- ''+(n.last_seen ? new Date(n.last_seen*1000).toLocaleTimeString() : '—')+'
';
- } else {
- body.querySelector('.net-down').textContent = fmtSpeed(net.rx_bytes_sec||0);
- body.querySelector('.net-up').textContent = fmtSpeed(net.tx_bytes_sec||0);
- var md = body.querySelector('.mem-detail-val');
- if (md) md.textContent = (mem.used_gb||0).toFixed(1) + '/' + (mem.total_gb||0).toFixed(1) + ' GB';
-
- var barCpu = body.querySelectorAll('.metric-bar-fill')[0];
- var barMem = body.querySelectorAll('.metric-bar-fill')[1];
+ body.querySelector('.net-down').textContent = fmtSpeed(net.rx_bytes_sec||0);
+ body.querySelector('.net-up').textContent = fmtSpeed(net.tx_bytes_sec||0);
+ var md = body.querySelector('.mem-detail-val');
+ if (md) md.textContent = (mem.used_gb||0).toFixed(1) + '/' + (mem.total_gb||0).toFixed(1) + ' GB';
+ var barCpu = body.querySelectorAll('.metric-bar-fill')[0];
+ var barMem = body.querySelectorAll('.metric-bar-fill')[1];
+ if (barCpu && barMem) {
+ var cpuBarCls = bar(cpuVal), memBarCls = bar(memVal);
barCpu.style.transition = 'none'; barMem.style.transition = 'none';
barCpu.style.width = oldCpu+'%'; barMem.style.width = oldMem+'%';
barCpu.className = 'metric-bar-fill '+cpuBarCls; barMem.className = 'metric-bar-fill '+memBarCls;
barCpu.offsetWidth; barMem.offsetWidth;
barCpu.style.transition = ''; barMem.style.transition = '';
barCpu.style.width = cpuVal+'%'; barMem.style.width = memVal+'%';
-
- animateValue(body.querySelector('.cpu-val'), oldCpu, cpuVal, 1, '%');
- animateValue(body.querySelector('.mem-val'), oldMem, memVal, 1, '%');
}
- } // close if (n.online)
-card.querySelector('.node-time').innerHTML = ''+(n.last_seen ? new Date(n.last_seen*1000).toLocaleTimeString() : '—')+'
';
+ animateValue(body.querySelector('.cpu-val'), oldCpu, cpuVal, 1, '%');
+ animateValue(body.querySelector('.mem-val'), oldMem, memVal, 1, '%');
+ card.querySelector('.node-time').innerHTML = ''+(n.last_seen ? new Date(n.last_seen*1000).toLocaleTimeString() : '—')+'
';
+ }
});
}
- function bar(v) { return v < 60 ? 'bar-green' : (v < 85 ? 'bar-yellow' : 'bar-red'); }
+function bar(v) { return v < 60 ? 'bar-green' : (v < 85 ? 'bar-yellow' : 'bar-red'); }
function fmtSpeed(bps) { return bps<1024?bps.toFixed(0)+'B/s':(bps<1048576?(bps/1024).toFixed(0)+'K/s':(bps/1048576).toFixed(1)+'M/s'); }
function esc(s) { return String(s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); }