// Coven Mirror frontend — vanilla JS, no build step needed. // Talks to the REST + WebSocket API in pkg/web/server.go. async function fetchJSON(url) { const r = await fetch(url); if (!r.ok) throw new Error(`${url}: ${r.status}`); return r.json(); } function renderGrid(elId, items, render) { const el = document.getElementById(elId); if (!items || items.length === 0) { el.innerHTML = '
No data
'; return; } el.innerHTML = items.map(render).join(""); } async function loadGrimoire() { try { const data = await fetchJSON("/api/v1/spells"); renderGrid("spell-grid", data.spells || [], (s) => `
Spell
${s}
` ); } catch (e) { document.getElementById("spell-grid").innerHTML = `
${e.message}
`; } } async function loadNodes() { try { const nodes = await fetchJSON("/api/v1/cluster/nodes"); renderGrid("pulse-grid", nodes, (n) => `
${n.Role || "node"}
${n.ID}
Arch
${n.Arch}
` ); renderGrid("sanctum-map", nodes, (n) => `
Sanctum
${n.ID}
○ Warding strong
` ); } catch (e) { console.error(e); } } function openLogStream(taskId) { const ws = new WebSocket(`ws://${location.host}/api/v1/stream/${taskId}`); ws.onmessage = (ev) => { const bar = document.getElementById("progress"); bar.textContent = "⚡ " + ev.data; }; } document.getElementById("generate-sbom").addEventListener("click", async () => { const out = document.getElementById("sbom-output"); out.textContent = "Forging SBOM..."; try { const r = await fetch("/api/v1/spells"); const data = await r.json(); out.textContent = JSON.stringify(data, null, 2); } catch (e) { out.textContent = "Error: " + e.message; } }); // Initial load loadGrimoire(); loadNodes(); // Heartbeat setInterval(() => { document.getElementById("status-text").textContent = "Coven online · Ley-Lines stable · " + new Date().toLocaleTimeString(); }, 1000);