lakehouse/mcp-server/dashboard.ts
root 66a3460c92 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>
2026-04-17 12:51:08 -05:00

231 lines
7.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const GW = window.location.origin;
let simData: any = null;
let currentDay = 0;
async function api(path: string, body?: any) {
const opts: RequestInit = body
? { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) }
: {};
const r = await fetch(GW + path, opts);
return r.json();
}
function addLog(msg: string) {
const el = document.getElementById("log")!;
const div = document.createElement("div");
div.textContent = `${new Date().toLocaleTimeString()} ${msg}`;
el.prepend(div);
while (el.children.length > 50) el.lastChild?.remove();
}
function renderContracts(day: any) {
const el = document.getElementById("contracts")!;
el.replaceChildren();
if (!day?.contracts?.length) {
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");
const isFilled = c.filled >= c.headcount;
div.className = `contract ${isFilled ? "filled" : c.priority === "urgent" ? "urgent" : c.priority === "high" ? "high" : ""}`;
const top = document.createElement("div");
top.className = "top";
const title = document.createElement("span");
title.className = "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} × ${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";
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);
}
el.appendChild(div);
}
}
function renderDayNav() {
const nav = document.getElementById("day-nav")!;
nav.replaceChildren();
if (!simData?.days) return;
simData.days.forEach((d: any, i: number) => {
const btn = document.createElement("button");
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);
});
}
function renderWeekStats() {
const el = document.getElementById("week-stats")!;
el.replaceChildren();
if (!simData?.summary) return;
const s = simData.summary;
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", 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 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);
}
}
async function loadAlerts() {
const el = document.getElementById("alerts")!;
el.replaceChildren();
try {
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 ${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 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";
div.textContent = "Could not load alerts";
el.appendChild(div);
}
}
async function loadPlaybooks() {
const el = document.getElementById("playbooks")!;
el.replaceChildren();
try {
const r = await api("/playbooks", {});
const pbs = r.playbooks || [];
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, 6)) {
const div = document.createElement("div");
div.className = "playbook";
const op = document.createElement("span");
op.className = "op";
op.textContent = (p.operation || "").slice(0, 50);
div.appendChild(op);
div.appendChild(document.createElement("br"));
div.appendChild(document.createTextNode((p.result || "").slice(0, 80)));
el.appendChild(div);
}
} catch {
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 g";
} catch {
document.getElementById("svc-gw")!.className = "dot r";
}
try {
const r = await api("/vram");
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 {}
}
(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}`);
return;
}
renderDayNav();
currentDay = 0;
renderContracts(simData.days[0]);
renderWeekStats();
addLog(`Week done: ${simData.summary.total_filled}/${simData.summary.total_needed} filled (${simData.summary.fill_pct}%)`);
} catch (e) {
addLog(`Failed: ${e}`);
}
};
(window as any).refresh = async function () {
addLog("Refreshing...");
await checkServices();
await loadAlerts();
await loadPlaybooks();
};
// Init
checkServices();
loadAlerts();
loadPlaybooks();
addLog("Dashboard loaded");
setInterval(checkServices, 30000);