Files

204 lines
8.0 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
window.DashboardModule = {
ws: null,
reconnectTimer: null,
reconnectDelay: 1000,
charts: {},
_animateNumber: function(el, newVal, decimals, suffix) {
try {
var oldVal = parseFloat(el.getAttribute('data-val'));
if (isNaN(oldVal)) oldVal = newVal;
if (Math.abs(oldVal - newVal) < 0.05) { el.textContent = newVal.toFixed(decimals) + suffix; el.setAttribute('data-val', newVal); return; }
el.setAttribute('data-val', newVal);
var steps = 20, i = 0;
var stepMs = 50; // 20 steps × 50ms = 1000ms
var timer = setInterval(function(){
i++;
var p = Math.min(i / steps, 1);
var eased = 1 - Math.pow(1 - p, 3);
el.textContent = (oldVal + (newVal - oldVal) * eased).toFixed(decimals) + suffix;
if (i >= steps) clearInterval(timer);
}, stepMs);
} catch(e) { el.textContent = newVal.toFixed(decimals) + suffix; }
},
init: function() {
var self = this;
// 初始化图表
self.charts = {
mem: new MiniChart('chart-mem', '#9ece6a'),
cpu: new MiniChart('chart-cpu', '#7aa2f7'),
net: new MiniChart('chart-net', '#e0af68'),
speed: new DualLineChart('chart-speed', '#bb9af7', '#c0a8f0'),
proc_mem: new MiniChart('chart-proc-mem', '#f7768e')
};
// 静态信息 (HTTP 一次)
fetchSystemStaticInfo();
// WebSocket 实时推送
self._connect();
},
_connect: function() {
var self = this;
if (self.ws) {
self.ws.onclose = null;
self.ws.close();
self.ws = null;
}
var base = window.location.pathname.split("/").slice(0, 2).join("/");
var proto = location.protocol === 'https:' ? 'wss://' : 'ws://';
var ws = new WebSocket(proto + location.host + base + "/api/system/ws");
self.ws = ws;
ws.onopen = function() {
self.reconnectDelay = 1000;
};
ws.onmessage = function(e) {
try {
var d = JSON.parse(e.data);
if (d.type === "sys") {
self._updateUI(d.system, d.framework);
}
} catch(ex) {}
};
ws.onclose = function() {
self.ws = null;
self.reconnectTimer = setTimeout(function() {
self.reconnectDelay = Math.min(self.reconnectDelay * 1.5, 15000);
self._connect();
}, self.reconnectDelay);
};
ws.onerror = function() { ws.close(); };
},
_updateUI: function(sys, fw) {
// ── 框架卡片 ──
if (fw) {
var el;
el = document.getElementById('d-uptime'); if (el) el.textContent = formatUptime(fw.uptime || 0);
el = document.getElementById('d-plugins'); if (el) el.textContent = fw.plugins || 0;
el = document.getElementById('d-ver-badge'); if (el) el.textContent = fw.version || '?';
}
if (!sys) return;
var self = this;
// ── 内存 ──
if (sys.memory) {
var m = sys.memory.percent || 0;
var el = document.getElementById('d-mem'); if (el) self._animateNumber(el, m, 1, '%');
el = document.getElementById('d-mem-detail');
if (el) el.textContent = (sys.memory.used_gb || 0).toFixed(1) + ' / ' + (sys.memory.total_gb || 0).toFixed(1) + ' GB';
self.charts.mem.update(m);
}
// ── CPU ──
if (sys.cpu) {
var cpuVal = sys.cpu.percent;
if (cpuVal === null || cpuVal === undefined) {
var load = (sys.cpu.load_avg && sys.cpu.load_avg[0]) ? sys.cpu.load_avg[0] : 0;
cpuVal = Math.min(100, (load / (sys.cpu.cores || 1)) * 100);
}
var el = document.getElementById('d-cpu'); if (el) self._animateNumber(el, cpuVal, 0, '%');
self.charts.cpu.update(cpuVal);
if (sys.cpu.load_avg) {
el = document.getElementById('info-load'); if (el) el.textContent = sys.cpu.load_avg[2].toFixed(2);
}
}
// ── 网络累计流量 (RX + TX) ──
if (sys.network) {
var el;
el = document.getElementById('d-net-rx'); if (el) el.textContent = formatTraffic(sys.network.rx_mb);
el = document.getElementById('d-net-tx'); if (el) el.textContent = formatTraffic(sys.network.tx_mb);
// Chart shows RX trend
self.charts.net.update(sys.network.rx_mb || 0);
}
// ── 实时网速 ──
if (sys.net_speed) {
var el;
el = document.getElementById('d-speed-rx');
if (el) el.textContent = formatSpeed(sys.net_speed.rx_bytes_sec);
el = document.getElementById('d-speed-tx');
if (el) el.textContent = formatSpeed(sys.net_speed.tx_bytes_sec);
// Chart: RX solid line, TX dashed line
self.charts.speed.update(sys.net_speed.rx_bytes_sec || 0, sys.net_speed.tx_bytes_sec || 0);
}
// ── 进程内存 ──
if (sys.process && sys.process.memory_mb !== undefined) {
var pm = sys.process.memory_mb || 0;
var el = document.getElementById('d-proc-mem'); if (el) self._animateNumber(el, pm, 1, ' MB');
self.charts.proc_mem.update(pm);
}
if (sys.process && sys.process.pid !== undefined) {
var el = document.getElementById('info-pid'); if (el) el.textContent = sys.process.pid;
}
},
destroy: function() {
if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); this.reconnectTimer = null; }
if (this.ws) { this.ws.onclose = null; this.ws.close(); this.ws = null; }
this.charts = {};
}
};
/* ── 静态信息 ── */
async function fetchSystemStaticInfo() {
try {
var sys = await fetch('./api/system', {credentials:'include'}).then(function(r){return r.json();});
var el;
if (sys.platform) {
el = document.getElementById('info-os'); if (el) el.textContent = sys.platform.system || '--';
el = document.getElementById('info-arch'); if (el) el.textContent = sys.platform.machine || '--';
el = document.getElementById('info-env'); if (el) el.textContent = sys.platform.env || 'Standard';
}
if (sys.cpu) {
var c = sys.cpu.cores || 0;
el = document.getElementById('info-cores'); if (el) el.textContent = c + ' / ' + c;
}
if (sys.memory) {
el = document.getElementById('info-mem-total'); if (el) el.textContent = sys.memory.total_gb + ' GB';
}
// 框架信息 (部分来自 API)
try {
var fw = await fetch('./api/framework', {credentials:'include'}).then(function(r){return r.json();});
el = document.getElementById('info-fw-ver'); if (el) el.textContent = fw.version || '?';
} catch(e) {}
var host = window.location.hostname + (window.location.port ? ':' + window.location.port : '');
el = document.getElementById('info-addr'); if (el) el.textContent = host;
} catch(e) {}
}
/* ── 格式化 ── */
function formatUptime(seconds) {
var h = Math.floor(seconds / 3600);
var m = Math.floor((seconds % 3600) / 60);
var s = Math.floor(seconds % 60);
if (h > 0) return h + 'h ' + m + 'm';
if (m > 0) return m + 'm ' + s + 's';
return s + 's';
}
function formatTraffic(mb) {
if (mb === undefined || mb === null) return '--';
if (mb >= 1024) return (mb / 1024).toFixed(1) + ' GB';
if (mb >= 1) return mb.toFixed(1) + ' MB';
return (mb * 1024).toFixed(0) + ' KB';
}
function formatSpeed(bytesPerSec) {
if (bytesPerSec === undefined || bytesPerSec === null || bytesPerSec < 0) return '0 B/s';
if (bytesPerSec >= 1048576) return (bytesPerSec / 1048576).toFixed(1) + ' MB/s';
if (bytesPerSec >= 1024) return (bytesPerSec / 1024).toFixed(0) + ' KB/s';
return bytesPerSec.toFixed(0) + ' B/s';
}