aboutsummaryrefslogtreecommitdiffstats
path: root/src/api
diff options
context:
space:
mode:
Diffstat (limited to 'src/api')
-rw-r--r--src/api/dashboard/dashboard.html23
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.