diff options
Diffstat (limited to 'src/api/dashboard/dashboard.html')
| -rw-r--r-- | src/api/dashboard/dashboard.html | 53 |
1 files changed, 48 insertions, 5 deletions
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)) { |
