diff options
| author | tv <tv@krebsco.de> | 2026-10-30 18:12:31 +0100 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-10-30 18:12:31 +0100 |
| commit | 0ef6785b09d74878f66449f18cf45fe54f8b2f5a (patch) | |
| tree | 36340b8b96ea780e266611159711a4d357cab4bf /src/api/dashboard/dashboard.html | |
| parent | 46a7d204c81b002f960a9bca97941df7a98c43d4 (diff) | |
dasboard cannot block shutdown
Diffstat (limited to 'src/api/dashboard/dashboard.html')
| -rw-r--r-- | src/api/dashboard/dashboard.html | 55 |
1 files changed, 39 insertions, 16 deletions
diff --git a/src/api/dashboard/dashboard.html b/src/api/dashboard/dashboard.html index a8b697e..9a17d6b 100644 --- a/src/api/dashboard/dashboard.html +++ b/src/api/dashboard/dashboard.html @@ -27,6 +27,7 @@ #connection { margin-left: auto; color: var(--muted); } #connection.live { color: var(--ok); } #connection.down { color: var(--bad); } + body.stale main { opacity: .55; } /* Swarm on the left, the full height; peers (as tall as their rows, up to 8) above the figures (the rest) on the right. */ main { flex: 1; min-height: 0; display: grid; gap: 16px; padding: 16px; @@ -428,23 +429,45 @@ document.getElementById("graph").addEventListener("click", () => focus(null)); // --- Events --- const connection = document.getElementById("connection"); -const events = new EventSource("dashboard/events"); -events.addEventListener("open", () => { - connection.textContent = "live"; connection.className = "live"; -}); -events.addEventListener("error", () => { - // EventSource reconnects by itself; the node sends a snapshot first again. - connection.textContent = "reconnecting…"; connection.className = "down"; -}); -events.addEventListener("snapshot", event => { - state = JSON.parse(event.data); stateAt = Date.now(); render(); -}); -events.addEventListener("patch", event => { - if (!state) return; - state = mergePatch(state, JSON.parse(event.data)); stateAt = Date.now(); render(); -}); +function status(text, live) { + connection.textContent = text; + connection.className = live ? "live" : "down"; + document.body.classList.toggle("stale", !live && state !== null); +} +// While not live, what is shown stays as it was last (ages don't count on). +let live = false; +let events = null; +function connect() { + events = new EventSource("dashboard/events"); + events.addEventListener("snapshot", event => { + state = JSON.parse(event.data); stateAt = Date.now(); live = true; + status("live", true); render(); + }); + events.addEventListener("patch", event => { + if (!state) return; + state = mergePatch(state, JSON.parse(event.data)); stateAt = Date.now(); render(); + }); + // The node stops: it says so, and ends the stream. Back once it is. + events.addEventListener("offline", () => { + events.close(); live = false; + status("node stopped; reconnecting…", false); + setTimeout(connect, 3000); + }); + events.addEventListener("error", () => { + live = false; + if (events.readyState === EventSource.CLOSED) { + // Given up (an HTTP error, e.g. the node is up but not serving the page yet). + status("disconnected; retrying…", false); + setTimeout(connect, 5000); + } else { + // EventSource reconnects by itself; the node sends a snapshot first again. + status("reconnecting…", false); + } + }); +} +connect(); // Ages count on between events; nothing else changes without one. -setInterval(render, 1000); +setInterval(() => { if (live) render(); }, 1000); window.addEventListener("resize", () => { lastShape = ""; render(); }); </script> </body> |
