aboutsummaryrefslogtreecommitdiffstats
path: root/static/modules
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-11-09 19:23:56 +0100
committerDanilo M. <danix@danix.xyz>2026-11-09 19:23:56 +0100
commitc04cfb4179068144d47c0fe64fc08344dd605457 (patch)
treefbc7ac0ac7710329f58a2230a2dd79ffc52b602c /static/modules
parent04bd03db72c7165debb79d1858f39121757ca358 (diff)
downloaddeck-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.js78
-rw-r--r--static/modules/sbbuilder.js51
-rw-r--r--static/modules/stats.js65
-rw-r--r--static/modules/windows.js38
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);
}
},
};