aboutsummaryrefslogtreecommitdiffstats
path: root/static/modules/sbbuilder.js
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-11-09 19:17:29 +0100
committerDanilo M. <danix@danix.xyz>2026-11-09 19:17:29 +0100
commit67e64e964a85562300b463378f0e260d6978867f (patch)
treecb706f2142823ca436d301d8b7a2af27e88231c1 /static/modules/sbbuilder.js
parent796407233383e7bf4825f671d84fbe71279488a6 (diff)
downloaddeck-it-67e64e964a85562300b463378f0e260d6978867f.tar.gz
deck-it-67e64e964a85562300b463378f0e260d6978867f.zip
feat: build VM card shows the real stats dashboard
Diffstat (limited to 'static/modules/sbbuilder.js')
-rw-r--r--static/modules/sbbuilder.js96
1 files changed, 55 insertions, 41 deletions
diff --git a/static/modules/sbbuilder.js b/static/modules/sbbuilder.js
index f1c574d..02493af 100644
--- a/static/modules/sbbuilder.js
+++ b/static/modules/sbbuilder.js
@@ -1,58 +1,72 @@
-function chipClass(status) {
- if (status === "ok") return "good";
- if (status === "failed" || status === "lost" || status === "killed") return "bad";
- if (status === "running" || status === "busy" || status === "starting") return "warn";
- return "muted";
-}
+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));
+ if (d < 3600) return `${Math.floor(d / 60)}m`;
+ if (d < 86400) return `${Math.floor(d / 3600)}h`;
+ return `${Math.floor(d / 86400)}d`;
+};
-function when(ts) {
- if (!ts) return "";
- return new Date(ts * 1000).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
-}
+const ACTIVE = ["running", "busy", "starting"];
-function summary(jobs) {
- const running = jobs.filter((j) => ["running", "busy", "starting"].includes(j.status)).length;
- const failed = jobs.filter((j) => ["failed", "lost", "killed"].includes(j.status)).length;
- const parts = [];
- if (running) parts.push(`${running} running`);
- if (failed) parts.push(`${failed} failed`);
- if (!parts.length) parts.push(jobs.length ? `${jobs.length} recent` : "idle");
- return parts.join(" · ");
+function metric(label, value) {
+ const row = document.createElement("div");
+ row.className = "metric";
+ const k = document.createElement("span");
+ k.className = "k";
+ k.textContent = label;
+ const v = document.createElement("span");
+ v.className = "v num";
+ v.textContent = value;
+ row.append(k, v);
+ return row;
}
export default {
title: "Build VM",
- interval: 5,
+ interval: 10,
mount(el) {
- el.innerHTML = `<div class="sbb"><div class="sbb-summary muted"></div><div class="sbb-list"></div></div>`;
+ el.innerHTML = `<div class="sbb"></div>`;
},
update(state, el) {
- const sum = el.querySelector(".sbb-summary");
- const list = el.querySelector(".sbb-list");
+ const box = el.querySelector(".sbb");
+ box.textContent = "";
if (state.offline) {
- sum.textContent = "";
- list.textContent = "build VM unreachable";
+ box.textContent = "build VM unreachable";
return;
}
+ const s = state.stats ?? {};
+ const p = s.packages ?? {};
+ const sr = s.slackrepo ?? {};
const jobs = state.jobs ?? [];
- sum.textContent = summary(jobs);
- list.textContent = "";
- for (const job of jobs.slice(0, 8)) {
- const row = document.createElement("div");
- row.className = "row job";
- const kind = document.createElement("span");
- kind.className = "kind";
- kind.textContent = job.kind || "job";
- const time = document.createElement("span");
- time.className = "muted num";
- time.textContent = when(job.created);
- const status = document.createElement("span");
- status.className = chipClass(job.status);
- status.textContent = job.status;
- row.append(kind, time, status);
- list.append(row);
- }
+
+ 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.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(" · ") || "—"),
+ );
},
};