aboutsummaryrefslogtreecommitdiffstats
path: root/src/api/dashboard/dashboard.html
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-10-30 18:12:31 +0100
committertv <tv@krebsco.de>2026-10-30 18:12:31 +0100
commit0ef6785b09d74878f66449f18cf45fe54f8b2f5a (patch)
tree36340b8b96ea780e266611159711a4d357cab4bf /src/api/dashboard/dashboard.html
parent46a7d204c81b002f960a9bca97941df7a98c43d4 (diff)
dasboard cannot block shutdown
Diffstat (limited to 'src/api/dashboard/dashboard.html')
-rw-r--r--src/api/dashboard/dashboard.html55
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>