// deck-it - sbbuilder panel // Copyright (C) 2026 Danilo M. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2 as // published by the Free Software Foundation. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License along // with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. import { offline } from "../offline.js"; const gib = (b) => (b == null ? "?" : (b / 1073741824).toFixed(1)); const pct = (n) => (n == null ? "?" : `${Math.round(n)}%`); 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`; }; const ACTIVE = ["running", "busy", "starting"]; 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 { title: "Build VM", interval: 10, mount(el) { el.innerHTML = `
`; }, update(state, el) { const box = el.querySelector(".sbb"); if (state.offline) { offline(box, { icon: "/icons/virt-manager.svg", title: "Build VM is off", detail: "Start the VM and its jobs and package counts appear here.", }); return; } box.textContent = ""; 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"}` : "idle"; now.classList.toggle("warn", Boolean(running)); box.append(now); const overlays = Object.entries(p).filter(([k]) => k !== "total" && k !== "shadowed"); box.append( 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); }, };