diff --git a/mcp-server/dashboard.html b/mcp-server/dashboard.html index f87df4b..927b224 100644 --- a/mcp-server/dashboard.html +++ b/mcp-server/dashboard.html @@ -3,98 +3,346 @@ -Lakehouse — Staffing Co-Pilot +Lakehouse — Staffing Search
-

Staffing Co-Pilot

-
- Gateway - Lakehouse - VRAM: — - - -
+

Search 500,000 Workers

+

Type what you need in plain English — the AI understands meaning, not just keywords

-
-
-
-

Contracts

-
-
Click Run Week Sim to simulate a staffing week
-
-
-

Week Summary

-
No simulation data yet
-
+ - + + diff --git a/mcp-server/dashboard.ts b/mcp-server/dashboard.ts index cc8d005..6bf611b 100644 --- a/mcp-server/dashboard.ts +++ b/mcp-server/dashboard.ts @@ -1,232 +1 @@ -// Detect if we're behind nginx (/lakehouse/ prefix) or direct (:3700) -const base = window.location.pathname.startsWith("/lakehouse") ? "/lakehouse" : ""; -const GW = window.location.origin + base; -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); +// Replaced by inline script in dashboard.html