aboutsummaryrefslogtreecommitdiffstats
path: root/static/app.js
blob: ed19c60310ba99630a33fed8ebbc469791f4af39 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// The shell: discover modules, mount each panel, poll its state.
// A module is static/modules/<name>.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();