// The shell: discover modules, mount each panel, poll its state. // A module is static/modules/.js exporting default {title, interval, // mount(el), update(state, el)}. A missing or broken panel is skipped. const grid = document.getElementById("grid"); const refreshers = []; async function mountModule(name) { try { const panel = (await import(`./modules/${name}.js`)).default; const el = document.createElement("section"); el.className = "panel"; el.dataset.module = name; const title = document.createElement("h2"); title.textContent = panel.title ?? name; el.append(title); grid.append(el); panel.mount?.(el); const poll = async () => { try { const r = await fetch(`/api/${name}`); if (r.ok) panel.update?.(await r.json(), el); } catch { // keep the last state; the next tick retries } }; refreshers.push(poll); await poll(); setInterval(poll, (panel.interval ?? 1) * 1000); } catch { // a broken panel must not take down the rest of the dashboard } } async function boot() { const r = await fetch("/api/modules"); if (!r.ok) return; for (const { name } of await r.json()) await mountModule(name); } document.addEventListener("visibilitychange", () => { if (document.visibilityState === "visible") refreshers.forEach((fn) => fn()); }); boot();