diff --git a/hbd/server/templates/plugins.html b/hbd/server/templates/plugins.html index 6f164d0..12165c5 100644 --- a/hbd/server/templates/plugins.html +++ b/hbd/server/templates/plugins.html @@ -873,7 +873,7 @@ let html = ''; switch (pluginName) { case 'os_info': html = renderOsInfoTable(cached.data); break; - case 'cpu_monitor': html = renderCpuTable(cached.data); break; + case 'cpu_monitor': html = renderCpuTable(hostname, cached.data); break; case 'memory_monitor': html = renderMemoryTable(cached.data); break; case 'disk_monitor': html = renderDiskTables(cached.data); break; case 'network_monitor':html = renderNetworkTables(cached.data); break; @@ -885,6 +885,10 @@ html += `
Last updated: ${new Date(cached.timestamp * 1000).toLocaleString()}
`; body.innerHTML = html; + + if (pluginName === 'cpu_monitor') { + fetchCpuHistory(hostname).then(samples => renderCpuChart(hostname, samples)).catch(() => {}); + } } // ── Per-plugin renderers ──────────────────────────────────────────────── @@ -907,7 +911,78 @@ return html; } - function renderCpuTable(d) { + async function fetchCpuHistory(hostname) { + const r = await fetch(`/api/0/hosts/${encodeURIComponent(hostname)}/plugins/cpu_monitor?limit=100`); + if (!r.ok) return []; + const json = await r.json(); + return json.samples || []; + } + + function renderCpuChart(hostname, samples) { + const el = document.getElementById(`cpu-chart-${hostname}`); + if (!el || !samples.length) return; + + const pts = samples + .filter(s => s.data.cpu_percent != null) + .map(s => ({ t: s.timestamp, v: s.data.cpu_percent })); + if (pts.length < 2) { el.style.display = 'none'; return; } + + const W = 600, H = 80, PAD = { top: 6, right: 8, bottom: 18, left: 28 }; + const cW = W - PAD.left - PAD.right; + const cH = H - PAD.top - PAD.bottom; + + const tMin = pts[0].t, tMax = pts[pts.length - 1].t; + const tRange = tMax - tMin || 1; + const x = t => PAD.left + ((t - tMin) / tRange) * cW; + const y = v => PAD.top + cH - (Math.min(v, 100) / 100) * cH; + + // Build polyline points and filled area path + const linePoints = pts.map(p => `${x(p.t).toFixed(1)},${y(p.v).toFixed(1)}`).join(' '); + const areaPath = `M${x(pts[0].t).toFixed(1)},${(PAD.top + cH).toFixed(1)} ` + + pts.map(p => `L${x(p.t).toFixed(1)},${y(p.v).toFixed(1)}`).join(' ') + + ` L${x(pts[pts.length-1].t).toFixed(1)},${(PAD.top + cH).toFixed(1)} Z`; + + // Color based on latest value + const latest = pts[pts.length - 1].v; + const strokeColor = latest > 90 ? '#e53935' : latest > 70 ? '#fb8c00' : '#43a047'; + const fillColor = latest > 90 ? '#ffcdd2' : latest > 70 ? '#ffe0b2' : '#c8e6c9'; + + // Y-axis grid lines at 25, 50, 75, 100 + let gridLines = ''; + for (const pct of [25, 50, 75, 100]) { + const yy = y(pct).toFixed(1); + gridLines += ``; + gridLines += `${pct}`; + } + + // X-axis time labels + const fmt = ts => { + const d = new Date(ts * 1000); + return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); + }; + const xLabels = ` + ${fmt(pts[0].t)} + ${fmt(pts[pts.length-1].t)}`; + + el.innerHTML = ` + + + + + + ${gridLines} + + + + + + + ${xLabels} + `; + } + + function renderCpuTable(hostname, d) { const KEYS = [ ['cpu_percent', 'CPU Usage', 'bar'], ['load_1min', 'Load (1 min)', 'num'], @@ -925,7 +1000,8 @@ ]; const handled = new Set(KEYS.map(r => r[0])); - let html = ''; + let html = `
`; + html += '
MetricValue
'; for (const [k, label, fmt] of KEYS) { if (!(k in d)) continue; const v = d[k];
MetricValue