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 = - '
'+ - '
CPU
0%
'+ - '
MEM
0%
'+ - '
'+ - '
'+ - '
NET ↓
'+ - '
NET ↑
'+ - '
'; - } + body.innerHTML = + '
'+ + '
CPU
0%
'+ + '
MEM
0%
'+ + '
'+ + '
'+ + '
NET ↓
'+ + '
NET ↑
'+ + '
'; } - - 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,'"'); }