diff --git a/public/views/cards/host_perf.js b/public/views/cards/host_perf.js index fce0371..fba6763 100644 --- a/public/views/cards/host_perf.js +++ b/public/views/cards/host_perf.js @@ -1,2 +1,33 @@ -// temporary stub — filled in Task 9 -export default { id: 'host-perf', title: 'Host Perf', size: 'm', mount() {}, start() {}, stop() {} }; +// public/views/cards/host_perf.js +import { el, mount } from '../../dom.js'; +import { api } from '../../api.js'; + +let body, timer, prev; +const GB = 1024 ** 3; +function bar(pct) { return el('div', { class: 'sv-bar' }, el('i', { style: { width: Math.min(100, pct) + '%' } })); } +async function load() { + if (!body) return; + try { + const h = await api.get('/api/host'); + let rate = ''; + if (prev) { + const dt = (h.at - prev.at) / 1000 || 1; + const dn = (b) => ((b) / dt / 1e6).toFixed(1); + rate = `↓${dn(h.net.rx_bytes - prev.net.rx_bytes)} ↑${dn(h.net.tx_bytes - prev.net.tx_bytes)} MB/s`; + } + prev = h; + mount(body, + el('div', { class: 'sv-row' }, el('span', { class: 'k' }, 'CPU'), el('span', {}, h.cpu_pct + '%')), bar(h.cpu_pct), + el('div', { class: 'sv-row' }, el('span', { class: 'k' }, 'RAM'), + el('span', {}, `${(h.mem.used / GB).toFixed(1)} / ${(h.mem.total / GB).toFixed(0)} GB`)), bar(h.mem.pct), + el('div', { class: 'sv-row' }, el('span', { class: 'k' }, 'DISK'), el('span', {}, h.disk.pct + '%')), bar(h.disk.pct), + el('div', { class: 'sv-row' }, el('span', { class: 'k' }, 'NET'), el('span', {}, rate || '—')) + ); + } catch { mount(body, el('span', { class: 'muted' }, 'Host unavailable')); } +} +export default { + id: 'host-perf', title: 'Host Perf · CT 311', size: 'm', + mount(el_) { body = el_; prev = null; load(); }, + start() { timer = setInterval(load, 30000); }, + stop() { clearInterval(timer); body = null; } +};