From 8d1950fcaa4e3d748a697c07e1df37c395f302c9 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 2 Jun 2026 22:51:22 +1000 Subject: [PATCH] feat(card): speedtest --- public/style.css | 1 + public/views/cards/speedtest.js | 27 +++++++++++++++++++++++++++ public/views/sacred_valley.js | 3 ++- 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 public/views/cards/speedtest.js diff --git a/public/style.css b/public/style.css index ec21587..6f8255c 100644 --- a/public/style.css +++ b/public/style.css @@ -211,3 +211,4 @@ ul.plain li:last-child { border-bottom: none; } .sv-bar > i { display: block; height: 100%; border-radius: 3px; background: linear-gradient(90deg, var(--accent-dim), var(--accent)); transition: box-shadow .35s; } .sv-card:hover .sv-bar > i { box-shadow: 0 0 9px rgba(255,79,46,.55); } .sv-search-input{width:100%;background:#0d0d13;border:1px solid var(--border);border-radius:6px;padding:8px 10px;color:var(--text);font-family:var(--font-mono);font-size:12px} +.sv-run{background:var(--accent-soft);border:1px solid var(--accent-dim);color:var(--accent);border-radius:5px;padding:3px 10px;font-family:var(--font-ui);font-size:11px;cursor:pointer} diff --git a/public/views/cards/speedtest.js b/public/views/cards/speedtest.js new file mode 100644 index 0000000..3ca8ef1 --- /dev/null +++ b/public/views/cards/speedtest.js @@ -0,0 +1,27 @@ +// public/views/cards/speedtest.js +import { el, mount } from '../../dom.js'; +import { api } from '../../api.js'; + +let body; +async function load() { + if (!body) return; + try { + const hist = await api.get('/api/speedtest/history'); + const latest = hist[0]; + const max = Math.max(1, ...hist.map(h => Number(h.down_mbps))); + const bars = el('div', { style: { display: 'flex', gap: '2px', alignItems: 'flex-end', height: '40px', marginTop: '8px' } }, + hist.slice(0, 30).reverse().map(h => + el('div', { style: { flex: '1', background: 'var(--accent-dim)', + height: (Number(h.down_mbps) / max * 100) + '%' } }))); + mount(body, + el('div', { class: 'sv-row', style: { fontSize: '20px' } }, + el('span', { style: { fontFamily: 'var(--font-mono)' } }, latest ? `${Number(latest.down_mbps).toFixed(0)}↓ ${Number(latest.up_mbps).toFixed(0)}↑` : '—'), + el('button', { class: 'sv-run', onclick: runNow }, 'Run')), + bars); + } catch { mount(body, el('span', { class: 'muted' }, 'No speedtest data')); } +} +async function runNow() { try { await api.post('/api/speedtest/run', {}); } catch {} setTimeout(load, 3000); } +export default { + id: 'speedtest', title: 'Speedtest', size: 'm', + mount(el_) { body = el_; load(); }, start() {}, stop() { body = null; } +}; diff --git a/public/views/sacred_valley.js b/public/views/sacred_valley.js index 4d937ec..65b86bc 100644 --- a/public/views/sacred_valley.js +++ b/public/views/sacred_valley.js @@ -9,8 +9,9 @@ import hostPerf from './cards/host_perf.js'; import jobs from './cards/jobs.js'; import inbox from './cards/inbox.js'; import search from './cards/search.js'; +import speedtest from './cards/speedtest.js'; -const CARD_MODULES = [clock, weather, hostPerf, jobs, inbox, search]; // grows in later tasks +const CARD_MODULES = [clock, weather, hostPerf, jobs, inbox, search, speedtest]; // grows in later tasks let active = []; // mounted cards needing stop() export async function render(main) {