aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-10-30 19:22:39 +0100
committertv <tv@krebsco.de>2026-10-30 19:22:39 +0100
commit94c857eaf7197d1dc6caa3cb295c73a959095192 (patch)
tree519e04fa343d57b45b933e0b93e2586d6cf5ffc7 /src
parentc9f556a6d329117673adfc8a9237b6b81b9ade72 (diff)
dashboard: add status pulse
Diffstat (limited to 'src')
-rw-r--r--src/api/dashboard/dashboard.html25
1 files changed, 21 insertions, 4 deletions
diff --git a/src/api/dashboard/dashboard.html b/src/api/dashboard/dashboard.html
index dd0c3cc..f6f6693 100644
--- a/src/api/dashboard/dashboard.html
+++ b/src/api/dashboard/dashboard.html
@@ -27,6 +27,16 @@
#connection { margin-left: auto; color: var(--muted); }
#connection.live { color: var(--ok); }
#connection.down { color: var(--bad); }
+ /* A dot in the status's colour that breathes once with every event. */
+ #dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%;
+ background: currentColor; margin-right: 6px; vertical-align: 1px; opacity: .45; }
+ #dot.pulse { animation: breath .9s ease-in-out; }
+ @keyframes breath {
+ 0% { opacity: .45; transform: scale(1); }
+ 30% { opacity: 1; transform: scale(1.35); box-shadow: 0 0 6px currentColor; }
+ 100% { opacity: .45; transform: scale(1); }
+ }
+ @media (prefers-reduced-motion: reduce) { #dot.pulse { animation-duration: .01s; } }
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. */
@@ -95,7 +105,7 @@
<header>
<h1>continuum</h1>
<span id="node" class="id"></span>
- <span id="connection">connecting…</span>
+ <span id="connection"><span id="dot"></span><span id="connection-text">connecting…</span></span>
</header>
<main>
<section id="map">
@@ -613,8 +623,15 @@ graph.addEventListener("mouseleave", () => hover(null));
// --- Events ---
const connection = document.getElementById("connection");
+const dot = document.getElementById("dot");
+// One breath per event from the node.
+function pulse() {
+ dot.classList.remove("pulse");
+ void dot.offsetWidth; // restarts the animation
+ dot.classList.add("pulse");
+}
function status(text, live) {
- connection.textContent = text;
+ document.getElementById("connection-text").textContent = text;
connection.className = live ? "live" : "down";
document.body.classList.toggle("stale", !live && state !== null);
}
@@ -632,11 +649,11 @@ function connect() {
return;
}
state = snapshot; stateAt = Date.now(); live = true;
- status("live", true); render();
+ status("live", true); pulse(); render();
});
events.addEventListener("patch", event => {
if (!state) return;
- state = mergePatch(state, JSON.parse(event.data)); stateAt = Date.now(); render();
+ state = mergePatch(state, JSON.parse(event.data)); stateAt = Date.now(); pulse(); render();
});
// The node stops: it says so, and ends the stream. Back once it is.
events.addEventListener("offline", () => {