diff options
| -rw-r--r-- | docs/dashboard.md | 8 | ||||
| -rw-r--r-- | src/api/dashboard/dashboard.html | 53 |
2 files changed, 56 insertions, 5 deletions
diff --git a/docs/dashboard.md b/docs/dashboard.md index 4067da5..3a92018 100644 --- a/docs/dashboard.md +++ b/docs/dashboard.md @@ -242,6 +242,12 @@ The figures of what is in focus, as a tree: this node's `/admin/stats`, or a pee `/peers` entry and links. Branches stay as opened across updates, and ages count on between them. What each figure means: [`measurements.md`](measurements.md). +Some values get a reading after them, muted, in parentheses: a node ID its alias +(where it has one; a key that is a node ID too), a time in Unix milliseconds in RFC 3339 +(a key ending in `At`, `Until` or `Since`, or `at`, `until`, `since`, `alive`), bytes +in KiB, MiB, GiB (a key ending in `Bytes`, or `bytes`; from 1 KiB), and bytes per +second likewise (`…BytesPerSec`, `bytesPerSec`). + ## Document The swarm's document, as this node holds it, its first level open. A branch is read @@ -262,6 +268,8 @@ to the whole swarm like any other: Protocol data (`_continuum`) is written only by users in `--protocol-writers`, and epoch metadata and the swarm ID by nobody ([`trust.md`](trust.md)). A key containing `/`, or an empty key, is listed but can't be opened. +In the protocol data, values and keys get the same readings as in the figures: the +aliases of node IDs (a member's entry, a vote, a coordinator), times and sizes. ## Console diff --git a/src/api/dashboard/dashboard.html b/src/api/dashboard/dashboard.html index ede191d..7be738c 100644 --- a/src/api/dashboard/dashboard.html +++ b/src/api/dashboard/dashboard.html @@ -254,6 +254,8 @@ .tree details[open] > summary::before { content: "▾"; } .tree details > .note { padding-left: 1.1em; } .key { color: var(--muted); } + /* What a value or key means, after it (`gloss`): an alias, a time, a size. */ + .gloss { color: var(--muted); } </style> </head> <body> @@ -584,6 +586,39 @@ if (handOver && Array.isArray(handOver.figures)) { open.clear(); for (const path of handOver.figures) if (typeof path === "string") open.add(path); } +// What a value means, shown muted in parentheses after it (`glossed`): a node ID's +// alias, a Unix-milliseconds time in RFC 3339 (a key that names a time: …At, …Until, +// …Since, at, since, alive), bytes in KiB, MiB, GiB (a key ending in …Bytes, or +// bytes), and bytes per second likewise (…BytesPerSec, bytesPerSec). Null if nothing to add. +const DATE_MS = [946684800000, 4102444800000]; +function aliasOf(id) { + if (!state || typeof id !== "string") return null; + const member = state.graph.members[id], peer = state.peers.peers[id]; + return (member && member.alias) || (peer && peer.alias) || null; +} +function bytesReadable(bytes) { + const units = ["B", "KiB", "MiB", "GiB", "TiB"]; + let unit = 0; + while (Math.abs(bytes) >= 1024 && unit < units.length - 1) { bytes /= 1024; unit++; } + return (unit === 0 ? String(bytes) : bytes.toFixed(1)) + " " + units[unit]; +} +function gloss(key, value) { + if (typeof value === "string") return aliasOf(value); + if (typeof value !== "number" || !Number.isFinite(value)) return null; + const name = String(key); + // By how the key ends: `savedBytesSecsAgo` is no size. + if (/(^b|B)ytesPerSec$/.test(name)) return value >= 1024 ? bytesReadable(value) + "/s" : null; + if (/(^b|B)ytes$/.test(name)) return value >= 1024 ? bytesReadable(value) : null; + if ((/(At|Until|Since)$/.test(name) || /^(at|until|since|alive)$/.test(name)) && + value >= DATE_MS[0] && value < DATE_MS[1]) { + return new Date(value).toISOString().replace(".000Z", "Z"); + } + return null; +} +// `gloss`'s, as an element to put after the value (or the key: an alias), if any. +function glossed(text) { + return text === null ? [] : [" ", el("span", { class: "gloss" }, "(" + text + ")")]; +} function tree(key, value, path) { // Empty: nothing to open. if (value !== null && typeof value === "object" && Object.keys(value).length === 0) { @@ -597,7 +632,7 @@ function tree(key, value, path) { if (details.open) open.add(path); else open.delete(path); }); const entries = Array.isArray(value) ? value.map((v, i) => [String(i), v]) : Object.entries(value); - const summary = el("summary", {}, el("span", { class: "key" }, key), + const summary = el("summary", {}, el("span", { class: "key" }, key), ...glossed(aliasOf(key)), Array.isArray(value) ? " [" + value.length + "]" : ""); // Opened or closed on press, as the peers' rows are focused: the tree is drawn anew // every second (ages), and a click whose press and release land on two drawings is @@ -614,8 +649,9 @@ function tree(key, value, path) { for (const [k, v] of entries) details.append(tree(k, v, path + "." + k)); return details; } - return el("div", { class: "leaf" }, el("span", { class: "key" }, key + ": "), - JSON.stringify(aged(key, value))); + return el("div", { class: "leaf" }, el("span", { class: "key" }, key, + ...glossed(aliasOf(key)), ": "), JSON.stringify(aged(key, value)), + ...glossed(gloss(key, value))); } function renderStats() { const title = document.getElementById("figures-title"); @@ -1483,8 +1519,14 @@ function addButton(kind, path, known, place) { return button; } +// In the protocol's data (`_continuum`): node IDs, times and sizes glossed (`gloss`). +const glossedPath = path => path[0] === "_continuum"; function editableLeaf(key, shown, value, path) { - const leaf = el("div", { class: "leaf" }, el("span", { class: "key" }, key + ": "), shown); + const glosses = glossedPath(path); + // The key one element (kept while editing), its alias in it. + const leaf = el("div", { class: "leaf" }, el("span", { class: "key" }, key, + ...(glosses ? glossed(aliasOf(key)) : []), ": "), shown, + ...(glosses ? glossed(gloss(key, value)) : [])); if (!editable(path)) return leaf; leaf.title = "Double-click to edit"; leaf.addEventListener("dblclick", event => { @@ -1532,7 +1574,8 @@ function documentEntry(key, summary, path) { } const details = el("details", {}); const described = document.createTextNode(describe(summary)); - const head = el("summary", {}, el("span", { class: "key" }, key), " ", described); + const head = el("summary", {}, el("span", { class: "key" }, key), + ...(glossedPath(path) ? glossed(aliasOf(key)) : []), " ", described); details.append(head); // A double click edits the whole of it: read, then shown in a field. if (editable(path)) { |
