diff options
| author | tv <tv@krebsco.de> | 2026-10-31 06:55:20 +0100 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-10-31 06:55:20 +0100 |
| commit | 210e40178082f485d46bdcdaaf433356f7c9919a (patch) | |
| tree | df3440b066edb512b0e272399be718eac6594c62 /src/api/dashboard | |
| parent | c5f743ef0f91e4afd0661824d4ec7df1483dcbad (diff) | |
dashboard: add clocks
Diffstat (limited to 'src/api/dashboard')
| -rw-r--r-- | src/api/dashboard/dashboard.html | 23 |
1 files changed, 21 insertions, 2 deletions
diff --git a/src/api/dashboard/dashboard.html b/src/api/dashboard/dashboard.html index 03fa177..153d7cb 100644 --- a/src/api/dashboard/dashboard.html +++ b/src/api/dashboard/dashboard.html @@ -25,6 +25,7 @@ border-bottom: 1px solid var(--line); flex-wrap: wrap; } header h1 { font-size: 16px; margin: 0; } #connection { margin-left: auto; color: var(--muted); } + #clock-warning, #peers .clock-off { color: var(--warn); } #connection.live { color: var(--ok); } #connection.down { color: var(--bad); } /* A dot in the status's colour that breathes once with every event. */ @@ -137,6 +138,7 @@ <header> <h1>continuum</h1> <span id="node" class="id"></span> + <span id="clock-warning"></span> <span id="connection"><span id="dot"></span><span id="connection-text">connecting…</span></span> </header> <main> @@ -220,6 +222,13 @@ function name(id) { const member = state.graph.members[id]; return (member && member.alias) || (state.peers.peers[id] || {}).alias || short(id); } +// Milliseconds as signed seconds: "+5.0 s". +const seconds = ms => (ms < 0 ? "−" : "+") + (Math.abs(ms) / 1000).toFixed(1) + " s"; +// Whether `clock` (a peer's, `/peers`) differs from this node's beyond the limit for sure. +function clockOff(clock) { + const limit = (state.stats.clock || {}).limitMs || 0; + return !!clock && limit > 0 && Math.abs(clock.offsetMs) > clock.withinMs + limit; +} function age(secs) { if (secs === null || secs === undefined) return ""; if (secs < 120) return secs + " s"; @@ -248,7 +257,8 @@ function focus(id) { // --- Peers --- function renderPeers() { const table = document.getElementById("peers"); - const rows = [el("tr", {}, ...["peer", "status", "seen", "via", "rtt", "neighbour", "same epoch"] + const rows = [el("tr", {}, ...["peer", "status", "seen", "via", "rtt", "clock", "neighbour", + "same epoch"] .map(h => el("th", {}, h)))]; for (const [id, peer] of Object.entries(state.peers.peers)) { const destination = peer.destination; @@ -265,6 +275,10 @@ function renderPeers() { el("td", {}, age(aged("lastSeenSecsAgo", peer.lastSeenSecsAgo))), el("td", { class: "id" }, via), el("td", {}, rtt), + // Its clock against this node's, flagged beyond the limit (`--clock-skew-warn`). + el("td", { class: clockOff(peer.clock) ? "clock-off" : "", title: peer.clock + ? "ahead of this node's by " + peer.clock.offsetMs + " ms, give or take " + + peer.clock.withinMs + " ms" : "" }, peer.clock ? seconds(peer.clock.offsetMs) : ""), el("td", {}, peer.neighbour ? "yes" : ""), el("td", {}, peer.sameEpoch === null || peer.sameEpoch === undefined ? "" : peer.sameEpoch ? "yes" : "no")); @@ -641,8 +655,10 @@ function renderGraph() { fill: member.self ? "var(--self)" : reported ? "var(--fg)" : "var(--panel)", stroke: member.self ? "var(--self)" : dormant ? "var(--bad)" : "var(--fg)", "stroke-width": dormant ? 2.5 : 1.5 }, pressed); + const clock = (state.peers.peers[id] || {}).clock; tooltip(circle, id + (reported ? "" : " (no links reported)") + - (dormant ? " (dormant)" : "")); + (dormant ? " (dormant)" : "") + + (clockOff(clock) ? " (clock " + seconds(clock.offsetMs) + " off this node's)" : "")); const label = drawing("name " + id, "text", upright({ x: p.x + 10, y: p.y + 4, "font-size": 12, fill: "var(--fg)", cursor: "pointer" }), pressed); text(label, name(id)); @@ -994,6 +1010,9 @@ function render(ages = false) { if (!state) return; document.getElementById("node").textContent = (state.node.alias ? state.node.alias + " · " : "") + state.node.id; + const skew = state.stats.clock; + document.getElementById("clock-warning").textContent = skew && skew.off + ? "clock " + seconds(skew.offsetMs) + " off its peers'" : ""; held = false; const boxes = [["peers-box", renderPeers], ["figures-box", renderStats]]; // Its ages are in tooltips only: drawn on events, not every second. |
