aboutsummaryrefslogtreecommitdiffstats
path: root/src/api/dashboard
diff options
context:
space:
mode:
Diffstat (limited to 'src/api/dashboard')
-rw-r--r--src/api/dashboard/dashboard.html76
1 files changed, 71 insertions, 5 deletions
diff --git a/src/api/dashboard/dashboard.html b/src/api/dashboard/dashboard.html
index 6d32d55..d6f034b 100644
--- a/src/api/dashboard/dashboard.html
+++ b/src/api/dashboard/dashboard.html
@@ -655,6 +655,14 @@ let focused = handOver ? handOver.focused || null : (() => {
})();
const pairKey = (a, b) => a < b ? a + " " + b : b + " " + a;
const isPair = key => key !== null && key.includes(" ");
+// What subscribed nodes in focus say of themselves (`renderStats`): a node's figures
+// (`/admin/stats`) and peers (`/peers`), or a link's ends' peers, read from each every
+// 2 s while in focus, with the page's narrow token (`readFigures`). By node ID:
+// `stats`, `peers`, and `why` where they weren't read.
+const figuresOf = new Map();
+// The latest reading's number: an earlier one still on its way neither draws nor reads
+// again.
+let figuresTimer = null, figuresRound = 0;
function focus(id) {
focused = id === null || id === state.node.id ? null : id;
if (shownMessage !== null) {
@@ -670,6 +678,7 @@ function focus(id) {
render();
markPeerMessages();
loadTree();
+ readFigures();
}
// --- Peers ---
@@ -859,6 +868,36 @@ function tree(key, value, path) {
...glossed(aliasOf(key)), ": "), JSON.stringify(aged(key, value)),
...glossed(gloss(key, value)));
}
+// Read every 2 s while in focus (`figuresOf`, `figuresTimer` with `focused`).
+async function readFigures() {
+ clearTimeout(figuresTimer);
+ figuresTimer = null;
+ const round = ++figuresRound;
+ if (focused === null || !state) return;
+ const reading = focused;
+ const ids = (isPair(reading) ? reading.split(" ") : [reading])
+ .filter(id => id !== state.node.id && subscriptions.has(id));
+ await Promise.all(ids.map(async id => {
+ const get = async path => {
+ const response = await fetch(endpointOf(id, path), { cache: "no-store",
+ headers: { Authorization: "Bearer " + narrowToken.token, Accept: "application/json" } });
+ const answer = await response.json().catch(() => ({}));
+ if (!response.ok) throw new Error(response.status + " " + (answer.error || response.statusText));
+ return answer;
+ };
+ try {
+ if (!narrowToken) throw new Error(tokenWhy);
+ const [stats, peers] = await Promise.all([isPair(reading) ? null : get("stats"), get("../peers")]);
+ const known = figuresOf.get(id) || {};
+ figuresOf.set(id, { stats: stats || known.stats || null, peers: peers.peers || peers });
+ } catch (error) {
+ figuresOf.set(id, { ...(figuresOf.get(id) || {}), why: error.message || String(error) });
+ }
+ }));
+ if (round !== figuresRound || focused !== reading) return;
+ if (ids.length > 0) render();
+ figuresTimer = setTimeout(readFigures, 2000);
+}
function renderStats() {
const title = document.getElementById("figures-title");
const what = el("span", { class: "what" });
@@ -926,12 +965,16 @@ function renderStats() {
}
// Where this node is an end: the link itself, as `/peers` has the other end (where
// messages go, the paths, the addresses). The whole entry is the node's focus.
+ // Each end's paths to the other: this node's, and a subscribed node's as it says
+ // (`figuresOf`).
const self = state.node.id;
- const other = a === self ? b : b === self ? a : null;
- const peer = other !== null && state.peers.peers[other];
- if (peer) {
- shown.push(tree("this node's paths", {
- destination: peer.destination, addresses: peer.addresses }, "paths"));
+ for (const [x, y] of [[a, b], [b, a]]) {
+ const peers = x === self ? state.peers.peers : (figuresOf.get(x) || {}).peers;
+ const peer = peers && peers[y];
+ if (peer) {
+ shown.push(tree(x === self ? "this node's paths" : name(x) + "'s paths", {
+ destination: peer.destination, addresses: peer.addresses }, "paths." + (x === self ? "self" : x)));
+ }
}
title.replaceChildren("Link: ", what);
retitle(title, title.textContent);
@@ -955,6 +998,26 @@ function renderStats() {
what.replaceChildren(...(alias ? [alias, " "] : []), el("span", { class: "id" }, focused));
const peer = state.peers.peers[focused];
const report = state.graph.measured[focused];
+ // A subscribed node: its own figures at the top level, as this node's are, then this
+ // node's `/peers` entry for it and the links it reports, each a branch.
+ if (subscriptions.has(focused)) {
+ const known = figuresOf.get(focused) || {};
+ const figures = Object.entries(known.stats || {});
+ shown = figures.map(([k, v]) => tree(k, v, "stats." + k));
+ if (known.why) {
+ shown.push(el("div", { class: "note" }, "Its figures not read: " + known.why));
+ } else if (!known.stats) {
+ shown.push(el("div", { class: "note" }, "Reading its figures…"));
+ }
+ shown.push(peer ? tree("as this node sees it", peer, "seen")
+ : el("div", { class: "key leaf" }, "Not a peer of this node."));
+ if (report) shown.push(tree("links", report, "links"));
+ title.replaceChildren("Node: ", what, el("span", { class: "count" },
+ describe({ type: "map", size: figures.length + 1 + (report ? 1 : 0) })));
+ retitle(title, title.textContent);
+ document.getElementById("stats").replaceChildren(...shown);
+ return;
+ }
// Its `/peers` entry is the box, as this node's stats are: its keys at the top level,
// and after them the links the member reports.
const entries = Object.entries(peer || {});
@@ -2532,6 +2595,7 @@ function toggleSubscription(id) {
}
keepSubscriptions();
render();
+ readFigures();
}
// The subscriptions' reports on the map, over what this node has of them: fresher.
// What they cover of the node's state is kept aside (`overlaid`: by node, the node's
@@ -4089,9 +4153,11 @@ function connect() {
}
// Changes while not connected went unseen: what is open is read again.
if (state !== null) documentChanged({ all: true });
+ const first = state === null;
state = snapshot; stateAt = Date.now(); live = true; overlaid.clear();
status("live", true); pulse(); render(); trafficArrived(); epochsArrived();
remakesArrived();
+ if (first) readFigures();
if (treeShown && writerTree === null) loadTree();
if (token === null && tokenTimer === null) obtainTokens();
// Followed before the page knew which node it is: not any more.