Dashboard rebuilt: matches proof page design, mobile-ready

Clean dark theme matching /proof page. Priority badges on contracts
(urgent=red, high=yellow, medium=blue, low=green). Worker matches
shown inline. Day tabs show fill counts. Alerts with icons. Playbook
entries styled. All styles inline — no separate CSS file.

Mobile responsive: single column layout, scrollable tabs.
Links to /proof at bottom.

https://devop.live/lakehouse/ — the dashboard
https://devop.live/lakehouse/proof — the proof page

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
root
2026-04-17 12:51:08 -05:00
co-authored by Claude Opus 4.6
parent 5aaa3c5c08
commit 66a3460c92
3 changed files with 166 additions and 93 deletions
+80 -52
View File
@@ -1,4 +1,3 @@
// Use the same host the browser loaded the page from — works on LAN + localhost
const GW = window.location.origin;
let simData: any = null;
let currentDay = 0;
@@ -11,53 +10,64 @@ async function api(path: string, body?: any) {
return r.json();
}
function addLog(msg: string, cls = "") {
function addLog(msg: string) {
const el = document.getElementById("log")!;
const div = document.createElement("div");
const t = new Date().toLocaleTimeString();
div.textContent = `${t} ${msg}`;
if (cls) div.className = cls;
div.textContent = `${new Date().toLocaleTimeString()} ${msg}`;
el.prepend(div);
while (el.children.length > 50) el.lastChild?.remove();
}
function setText(id: string, text: string) {
const el = document.getElementById(id);
if (el) el.textContent = text;
}
function renderContracts(day: any) {
const el = document.getElementById("contracts")!;
el.replaceChildren();
if (!day?.contracts?.length) {
el.textContent = "No contracts for this day";
const empty = document.createElement("div");
empty.className = "empty";
empty.textContent = "No contracts for this day";
el.appendChild(empty);
return;
}
for (const c of day.contracts) {
const div = document.createElement("div");
div.className = `contract ${c.filled >= c.headcount ? "filled" : c.priority === "urgent" ? "urgent" : c.priority === "high" ? "high" : ""}`;
const isFilled = c.filled >= c.headcount;
div.className = `contract ${isFilled ? "filled" : c.priority === "urgent" ? "urgent" : c.priority === "high" ? "high" : ""}`;
const icon = c.priority === "urgent" ? "!!" : c.priority === "high" ? "!" : "";
const title = document.createElement("div");
const top = document.createElement("div");
top.className = "top";
const title = document.createElement("span");
title.className = "title";
title.textContent = `${icon} ${c.id} - ${c.client}`;
div.appendChild(title);
title.textContent = `${c.id} — ${c.client}`;
top.appendChild(title);
const badge = document.createElement("span");
badge.className = `badge ${c.priority}`;
badge.textContent = c.priority.toUpperCase();
top.appendChild(badge);
div.appendChild(top);
const meta = document.createElement("div");
meta.className = "meta";
meta.textContent = `${c.role} x${c.headcount} | ${c.city || c.state} | ${c.filled}/${c.headcount} filled | ${c.priority}`;
meta.textContent = `${c.role} × ${c.headcount} · ${c.city || c.state} · Start: ${c.start} · ${c.filled}/${c.headcount} filled`;
div.appendChild(meta);
if (c.matches?.length) {
const workers = document.createElement("div");
workers.className = "workers";
workers.textContent = c.matches.map((m: any) => `${m.name || m.doc_id} (${m.score?.toFixed(2) || "?"})`).join(", ");
const names = c.matches.map((m: any) => m.name || m.doc_id).join(", ");
const b = document.createElement("b");
b.textContent = `${c.matches.length} matched: `;
workers.appendChild(b);
workers.appendChild(document.createTextNode(names));
div.appendChild(workers);
}
if (c.notes) {
const notes = document.createElement("div");
notes.className = "meta";
notes.style.marginTop = "4px";
notes.style.fontStyle = "italic";
notes.textContent = c.notes;
div.appendChild(notes);
}
@@ -72,8 +82,10 @@ function renderDayNav() {
if (!simData?.days) return;
simData.days.forEach((d: any, i: number) => {
const btn = document.createElement("button");
btn.className = `day-btn ${i === currentDay ? "active" : ""}`;
btn.textContent = d.label;
btn.className = `tab ${i === currentDay ? "active" : ""}`;
const filled = d.contracts.reduce((s: number, c: any) => s + c.filled, 0);
const needed = d.contracts.reduce((s: number, c: any) => s + c.headcount, 0);
btn.textContent = `${d.label} (${filled}/${needed})`;
btn.onclick = () => { currentDay = i; renderDayNav(); renderContracts(simData.days[i]); };
nav.appendChild(btn);
});
@@ -84,25 +96,25 @@ function renderWeekStats() {
el.replaceChildren();
if (!simData?.summary) return;
const s = simData.summary;
const rows = [
["Total contracts", s.total_contracts],
["Total positions", s.total_needed],
["Filled", s.total_filled],
const rows: [string, string][] = [
["Total contracts", String(s.total_contracts)],
["Total positions", String(s.total_needed)],
["Filled", String(s.total_filled)],
["Fill rate", `${s.fill_pct}%`],
["Emergencies", s.emergencies],
["Handoffs", s.handoffs],
["Playbooks", s.playbook_entries],
["Emergencies", String(s.emergencies)],
["Handoffs", String(s.handoffs)],
["Playbooks", String(s.playbook_entries)],
];
for (const [label, val] of rows) {
const row = document.createElement("div");
row.className = "stat-row";
const nameSpan = document.createElement("span");
nameSpan.textContent = String(label);
const valSpan = document.createElement("span");
valSpan.className = "val";
valSpan.textContent = String(val);
row.appendChild(nameSpan);
row.appendChild(valSpan);
const l = document.createElement("span");
l.textContent = label;
const v = document.createElement("span");
v.className = "val";
v.textContent = val;
row.appendChild(l);
row.appendChild(v);
el.appendChild(row);
}
}
@@ -114,14 +126,22 @@ async function loadAlerts() {
const r = await api("/sql", { sql: "SELECT archetype, COUNT(*) cnt FROM ethereal_workers WHERE archetype IN ('erratic','silent') GROUP BY archetype" });
for (const row of r.rows || []) {
const div = document.createElement("div");
div.className = "alert warn";
div.textContent = `${row.archetype}: ${row.cnt} workers flagged`;
div.className = `alert ${row.archetype === "erratic" ? "warn" : "info"}`;
const icon = document.createElement("span");
icon.className = "icon";
icon.textContent = row.archetype === "erratic" ? "⚠" : "📵";
div.appendChild(icon);
div.appendChild(document.createTextNode(`${row.cnt} ${row.archetype} workers flagged`));
el.appendChild(div);
}
const info = document.createElement("div");
info.className = "alert info";
info.textContent = "System: all services running";
el.appendChild(info);
const good = document.createElement("div");
good.className = "alert good";
const gi = document.createElement("span");
gi.className = "icon";
gi.textContent = "✓";
good.appendChild(gi);
good.appendChild(document.createTextNode("All services running"));
el.appendChild(good);
} catch {
const div = document.createElement("div");
div.className = "alert warn";
@@ -136,11 +156,14 @@ async function loadPlaybooks() {
try {
const r = await api("/playbooks", {});
const pbs = r.playbooks || [];
if (pbs.length === 0) {
el.textContent = "No playbooks yet - run the simulation";
if (!pbs.length) {
const empty = document.createElement("div");
empty.className = "empty";
empty.textContent = "Run the simulation to build playbooks";
el.appendChild(empty);
return;
}
for (const p of pbs.slice(0, 8)) {
for (const p of pbs.slice(0, 6)) {
const div = document.createElement("div");
div.className = "playbook";
const op = document.createElement("span");
@@ -152,39 +175,43 @@ async function loadPlaybooks() {
el.appendChild(div);
}
} catch {
el.textContent = "Could not load playbooks";
const el2 = document.getElementById("playbooks")!;
el2.textContent = "Could not load playbooks";
}
}
async function checkServices() {
try {
await fetch(GW + "/health");
document.getElementById("svc-gw")!.className = "dot green";
document.getElementById("svc-gw")!.className = "dot g";
} catch {
document.getElementById("svc-gw")!.className = "dot red";
document.getElementById("svc-gw")!.className = "dot r";
}
try {
const r = await api("/vram");
setText("vram-display", `VRAM: ${r.gpu?.used_mib || "?"}/${r.gpu?.total_mib || "?"} MiB`);
const used = r.gpu?.used_mib || "?";
const total = r.gpu?.total_mib || "?";
const models = (r.ollama_loaded || []).map((m: any) => m.name).join(", ");
document.getElementById("vram-display")!.textContent = `GPU: ${used}/${total} MiB ${models ? "· " + models : ""}`;
} catch {}
}
// Week simulation runner
(window as any).runWeek = async function () {
addLog("Starting week simulation...");
try {
const r = await fetch(GW + "/simulation/run", { method: "POST" });
simData = await r.json();
if (simData.error) {
addLog(`Error: ${simData.error}`, "fail");
addLog(`Error: ${simData.error}`);
return;
}
renderDayNav();
currentDay = 0;
renderContracts(simData.days[0]);
renderWeekStats();
addLog(`Week complete: ${simData.summary?.total_filled}/${simData.summary?.total_needed} filled`, "ok");
addLog(`Week done: ${simData.summary.total_filled}/${simData.summary.total_needed} filled (${simData.summary.fill_pct}%)`);
} catch (e) {
addLog(`Simulation failed: ${e}`, "fail");
addLog(`Failed: ${e}`);
}
};
@@ -195,8 +222,9 @@ async function checkServices() {
await loadPlaybooks();
};
// Initial load
// Init
checkServices();
loadAlerts();
loadPlaybooks();
addLog("Dashboard loaded");
setInterval(checkServices, 30000);