diff options
| author | Danilo M. <danix@danix.xyz> | 2026-11-09 19:23:56 +0100 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-11-09 19:23:56 +0100 |
| commit | c04cfb4179068144d47c0fe64fc08344dd605457 (patch) | |
| tree | fbc7ac0ac7710329f58a2230a2dd79ffc52b602c /static/modules | |
| parent | 04bd03db72c7165debb79d1858f39121757ca358 (diff) | |
| download | deck-it-c04cfb4179068144d47c0fe64fc08344dd605457.tar.gz deck-it-c04cfb4179068144d47c0fe64fc08344dd605457.zip | |
feat: tablet-first dashboard tiles and match the Hyprland border speed
Diffstat (limited to 'static/modules')
| -rw-r--r-- | static/modules/llm.js | 78 | ||||
| -rw-r--r-- | static/modules/sbbuilder.js | 51 | ||||
| -rw-r--r-- | static/modules/stats.js | 65 | ||||
| -rw-r--r-- | static/modules/windows.js | 38 |
4 files changed, 118 insertions, 114 deletions
diff --git a/static/modules/llm.js b/static/modules/llm.js index a1b775b..0af8d7b 100644 --- a/static/modules/llm.js +++ b/static/modules/llm.js @@ -1,6 +1,5 @@ const num = (v) => (v == null ? null : Number(v)); -const pct = (v) => (num(v) == null ? "—" : `${Math.round(num(v))}%`); -const money = (v, cur) => (num(v) == null ? "—" : `${num(v).toFixed(2)}${cur ? " " + cur : ""}`); +const money = (v) => (num(v) == null ? "—" : `$${num(v).toFixed(2)}`); const tokens = (n) => { if (n == null) return "—"; if (n >= 1e9) return `${(n / 1e9).toFixed(1)}B`; @@ -8,28 +7,39 @@ const tokens = (n) => { if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`; return String(n); }; -const clock = (sec) => (sec ? new Date(sec * 1000).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : ""); -function line(label, value, percent) { - const row = document.createElement("div"); - row.className = "row"; - const name = document.createElement("span"); - name.textContent = label; - const val = document.createElement("span"); - val.className = "num"; - val.textContent = value; - row.append(name, val); - const wrap = document.createElement("div"); - wrap.append(row); - if (percent != null) { - const meter = document.createElement("div"); - meter.className = "meter"; - const fill = document.createElement("i"); - fill.style.width = `${Math.max(0, Math.min(100, Number(percent)))}%`; - meter.append(fill); - wrap.append(meter); - } - return wrap; +function gauge(label, value) { + const d = document.createElement("div"); + d.className = "gauge"; + const l = document.createElement("span"); + l.className = "glabel"; + l.textContent = label; + const v = document.createElement("span"); + v.className = "gvalue num"; + v.textContent = value == null ? "—" : `${Math.round(value)}%`; + const bar = document.createElement("div"); + bar.className = "gbar"; + const fill = document.createElement("i"); + fill.style.width = `${Math.max(0, Math.min(100, value || 0))}%`; + bar.append(fill); + d.append(l, v, bar); + return d; +} + +function big(label, value, sub) { + const d = document.createElement("div"); + d.className = "lbig"; + const l = document.createElement("span"); + l.className = "mlabel"; + l.textContent = label; + const v = document.createElement("span"); + v.className = "mvalue num"; + v.textContent = value; + const s = document.createElement("span"); + s.className = "msub"; + s.textContent = sub || ""; + d.append(l, v, s); + return d; } export default { @@ -37,9 +47,7 @@ export default { interval: 5, mount(el) { - const box = document.createElement("div"); - box.className = "llm"; - el.append(box); + el.innerHTML = `<div class="llm"></div>`; }, update(state, el) { @@ -51,16 +59,16 @@ export default { } const c = state.claude ?? {}; const o = state.opencode ?? {}; + const gauges = document.createElement("div"); + gauges.className = "gauges"; + gauges.append( + gauge("Context", c.context_pct), + gauge("5h", c.five_hour_pct), + gauge("7d", c.seven_day_pct), + ); + box.append(gauges); box.append( - line("Context", pct(c.context_pct), c.context_pct), - line(`5h window ${clock(c.five_hour_resets_at)}`, pct(c.five_hour_pct), c.five_hour_pct), - line(`7d window ${clock(c.seven_day_resets_at)}`, pct(c.seven_day_pct), c.seven_day_pct), - line("Month", money(c.extra_used, c.extra_currency)), - line("opencode spend", money(o.cost)), - line("opencode tokens", `${tokens(o.tokens)} · ${o.calls ?? 0} calls`), + big("opencode spend", money(o.cost), `${tokens(o.tokens)} · ${o.calls ?? 0} calls`), ); - for (const m of (o.models ?? []).slice(0, 4)) { - box.append(line(m.model || m.provider, money(m.cost))); - } }, }; diff --git a/static/modules/sbbuilder.js b/static/modules/sbbuilder.js index 02493af..751ffdd 100644 --- a/static/modules/sbbuilder.js +++ b/static/modules/sbbuilder.js @@ -1,11 +1,5 @@ const gib = (b) => (b == null ? "?" : (b / 1073741824).toFixed(1)); const pct = (n) => (n == null ? "?" : `${Math.round(n)}%`); -const up = (s) => { - if (s == null) return "?"; - const d = Math.floor(s / 86400); - const h = Math.floor((s % 86400) / 3600); - return d ? `${d}d ${h}h` : `${h}h`; -}; const ago = (ts) => { if (!ts) return "—"; const d = Math.max(0, Math.floor(Date.now() / 1000 - ts)); @@ -16,17 +10,20 @@ const ago = (ts) => { const ACTIVE = ["running", "busy", "starting"]; -function metric(label, value) { - const row = document.createElement("div"); - row.className = "metric"; - const k = document.createElement("span"); - k.className = "k"; - k.textContent = label; +function tile(label, value, sub) { + const d = document.createElement("div"); + d.className = "mtile"; + const l = document.createElement("span"); + l.className = "mlabel"; + l.textContent = label; const v = document.createElement("span"); - v.className = "v num"; + v.className = "mvalue num"; v.textContent = value; - row.append(k, v); - return row; + const s = document.createElement("span"); + s.className = "msub"; + s.textContent = sub || ""; + d.append(l, v, s); + return d; } export default { @@ -47,26 +44,28 @@ export default { const s = state.stats ?? {}; const p = s.packages ?? {}; const sr = s.slackrepo ?? {}; + const results = sr.results ?? {}; const jobs = state.jobs ?? []; - const running = jobs.find((j) => ACTIVE.includes(j.status)); + const now = document.createElement("div"); now.className = "sbb-now"; - now.textContent = running ? `● ${running.kind || "job"} running` : "idle"; + now.textContent = running ? `● ${running.kind || "job"}` : "idle"; now.classList.toggle("warn", Boolean(running)); box.append(now); const overlays = Object.entries(p).filter(([k]) => k !== "total" && k !== "shadowed"); - const results = Object.entries(sr.results ?? {}); box.append( - metric("CPU · load", `${pct(s.cpu)} · ${s.load ? s.load[0].toFixed(2) : "?"}`), - metric("Uptime", up(s.uptime)), - metric("Memory", `${gib((s.mem?.total ?? 0) - (s.mem?.available ?? 0))} / ${gib(s.mem?.total)} GiB`), - metric("Disk free", `${gib(s.disk?.free)} / ${gib(s.disk?.total)} GiB`), - metric("Packages", `${p.total ?? "?"}${overlays.length ? " · " + overlays.map(([k, v]) => `${k} ${v}`).join(" · ") : ""}`), - metric("Shadowed · orphans", `${p.shadowed ?? "?"} · ${s.orphans ?? "?"}`), - metric("Built · results", `${sr.built ?? "?"}${results.length ? " · " + results.map(([k, v]) => `${v} ${k}`).join(" · ") : ""}`), - metric("Overlay pulls", Object.entries(s.sync ?? {}).map(([k, v]) => `${k} ${ago(v)}`).join(" · ") || "—"), + tile("Packages", String(p.total ?? "?"), overlays.map(([k, v]) => `${k} ${v}`).join(" · ")), + tile("Failed", String(results.failed ?? 0), `${sr.built ?? "?"} built`), + tile("Disk free", `${gib(s.disk?.free)}`, `of ${gib(s.disk?.total)} GiB`), + tile("CPU", pct(s.cpu), `load ${s.load ? s.load[0].toFixed(2) : "?"}`), ); + + const foot = document.createElement("div"); + foot.className = "mfoot"; + foot.textContent = `orphans ${s.orphans ?? "?"} · ${ + Object.entries(s.sync ?? {}).map(([k, v]) => `${k} ${ago(v)}`).join(" · ") || "no pulls"}`; + box.append(foot); }, }; diff --git a/static/modules/stats.js b/static/modules/stats.js index a119c6c..16dffd2 100644 --- a/static/modules/stats.js +++ b/static/modules/stats.js @@ -1,36 +1,26 @@ -const gb = (kb) => (kb / 1048576).toFixed(1); -const rate = (b) => (b >= 1048576 ? `${(b / 1048576).toFixed(1)} MB/s` : `${Math.round(b / 1024)} KB/s`); +const gib = (kb) => (kb / 1048576).toFixed(1); +const bytes = (b) => (b == null ? "?" : (b / 1073741824).toFixed(1)); +const rate = (b) => (b >= 1048576 ? `${(b / 1048576).toFixed(1)}M` : `${Math.round(b / 1024)}k`); const uptime = (s) => { const d = Math.floor(s / 86400); const h = Math.floor((s % 86400) / 3600); - const m = Math.floor((s % 3600) / 60); - return d ? `${d}d ${h}h` : `${h}h ${m}m`; -}; -const gib = (b) => (b / 1073741824).toFixed(1); -const gpu = (g) => { - if (!g) return "—"; - if (g.asleep) return `${g.name} (asleep)`; - const parts = [g.name]; - if (g.util != null) parts.push(`${g.util}%`); - if (g.temp != null) parts.push(`${g.temp}°C`); - if (g.fan != null) parts.push(`${g.fan} rpm`); - if (g.memUsed != null) { - parts.push(g.memTotal ? `${gib(g.memUsed)} / ${gib(g.memTotal)} GiB` : `${gib(g.memUsed)} GiB`); - } - return parts.join(" · "); + return d ? `${d}d ${h}h` : `${h}h`; }; -function row(label, value) { - const div = document.createElement("div"); - div.className = "row"; - const name = document.createElement("span"); - name.className = "muted"; - name.textContent = label; - const val = document.createElement("span"); - val.className = "num"; - val.textContent = value; - div.append(name, val); - return div; +function tile(label, value, sub) { + const d = document.createElement("div"); + d.className = "mtile"; + const l = document.createElement("span"); + l.className = "mlabel"; + l.textContent = label; + const v = document.createElement("span"); + v.className = "mvalue num"; + v.textContent = value; + const s = document.createElement("span"); + s.className = "msub"; + s.textContent = sub || ""; + d.append(l, v, s); + return d; } export default { @@ -38,20 +28,23 @@ export default { interval: 1, mount(el) { - const box = document.createElement("div"); - box.className = "stats"; - el.append(box); + el.innerHTML = `<div class="stats"></div>`; }, update(st, el) { const box = el.querySelector(".stats"); box.textContent = ""; + const g = st.gpu; box.append( - row("CPU", `${st.cpu}%${st.cpuTemp != null ? ` · ${st.cpuTemp}°C` : ""}`), - row("Memory", `${gb(st.memUsed)} / ${gb(st.memTotal)} GiB`), - row("GPU", gpu(st.gpu)), - row("Network", `↓ ${rate(st.net.down)} ↑ ${rate(st.net.up)}`), - row("Uptime", uptime(st.uptime)), + tile("CPU", `${st.cpu}%`, st.cpuTemp != null ? `${st.cpuTemp}°C` : "—"), + tile("Memory", gib(st.memUsed), `/ ${gib(st.memTotal)} GiB`), + tile("GPU", g?.temp != null ? `${g.temp}°` : "—", + g ? `${bytes(g.memUsed)} / ${bytes(g.memTotal)} GiB` : "no GPU"), + tile("Network", `↓${rate(st.net.down)}`, `↑${rate(st.net.up)}`), ); + const foot = document.createElement("div"); + foot.className = "mfoot"; + foot.textContent = `up ${uptime(st.uptime)}`; + box.append(foot); }, }; diff --git a/static/modules/windows.js b/static/modules/windows.js index 7a2eeb8..a92d9e4 100644 --- a/static/modules/windows.js +++ b/static/modules/windows.js @@ -10,8 +10,9 @@ async function post(action, body = {}) { } } -function button(label, onclick) { +function tile(label, onclick) { const b = document.createElement("button"); + b.className = "wtile"; b.textContent = label; b.onclick = onclick; return b; @@ -22,44 +23,47 @@ export default { interval: 1, mount(el) { - const box = document.createElement("div"); - box.className = "windows"; - box.innerHTML = ` - <div class="row ws"></div> + el.innerHTML = `<div class="windows"> + <div class="ws"></div> <div class="winlist"></div> - <div class="row launches"></div>`; - el.append(box); + <div class="launches"></div> + </div>`; }, update(st, el) { - const box = el.querySelector(".windows"); - - const ws = box.querySelector(".ws"); + const ws = el.querySelector(".ws"); ws.textContent = ""; for (const id of st.workspaces ?? []) { - const b = button(String(id), () => post("workspace", { n: id })); + const b = tile(String(id), () => post("workspace", { n: id })); b.classList.toggle("on", (st.windows ?? []).some((w) => w.workspace === id)); ws.append(b); } - const list = box.querySelector(".winlist"); + const list = el.querySelector(".winlist"); list.textContent = ""; - for (const w of (st.windows ?? []).slice(0, 12)) { + for (const w of (st.windows ?? []).slice(0, 8)) { const row = document.createElement("div"); - row.className = "row"; + row.className = "wrow"; row.classList.toggle("on", Boolean(w.active)); const title = document.createElement("span"); title.className = "wt"; title.textContent = w.title || w.class; title.onclick = () => post("focus", { address: w.address }); - row.append(title, button("×", () => post("close", { address: w.address }))); + const close = document.createElement("button"); + close.className = "wclose"; + close.textContent = "×"; + close.setAttribute("aria-label", "Close window"); + close.onclick = () => post("close", { address: w.address }); + row.append(title, close); list.append(row); } - const launches = box.querySelector(".launches"); + const launches = el.querySelector(".launches"); launches.textContent = ""; for (const key of st.launches ?? []) { - launches.append(button(key, () => post("launch", { key }))); + const b = tile(key, () => post("launch", { key })); + b.classList.add("launch"); + launches.append(b); } }, }; |
