From b2c5096744c99453856f7281251b3f0cb4b9baae Mon Sep 17 00:00:00 2001 From: tv Date: Mon, 9 Nov 2026 05:22:14 +0000 Subject: dashboard: a subscribed node's message written out, read from it when shown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A subscribed node's messages come without being written out (their stream leaves the text out: up to 8 KiB each, 500 at a time), so the figures box said only when, both ends, type and size of one. Now the node answers for one message by its number, `GET /admin/dashboard/messages/own/{seq}` (`{"text": …}`, `404` once it isn't kept), which pages of other origins may call as they do its streams (`cors::open`), and the page reads it when the message is shown, with its narrow token, and keeps it on the message. Where it can't be read, the box says why. --- src/api/dashboard/dashboard.html | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) (limited to 'src/api/dashboard/dashboard.html') diff --git a/src/api/dashboard/dashboard.html b/src/api/dashboard/dashboard.html index 9cfb384..6d32d55 100644 --- a/src/api/dashboard/dashboard.html +++ b/src/api/dashboard/dashboard.html @@ -876,8 +876,9 @@ function renderStats() { el("span", { class: "key" }, key + ": "), value); title.replaceChildren("Message: ", what); retitle(title, title.textContent); - // A subscribed node's: logged there, the message itself not read from it. + // A subscribed node's: logged there, written out as read from it. if (message.node) { + const text = el("pre", { class: "message" }, "reading it from " + name(message.node) + "…"); document.getElementById("stats").replaceChildren( field("at", clockTime(new Date(message.at))), field("from", message.from), @@ -886,7 +887,12 @@ function renderStats() { ...(message.address ? [field("address", message.address)] : []), field("type", message.type), field("bytes", String(message.bytes)), - el("div", { class: "note" }, "Written out only on the node that logged it (its console).")); + text); + writtenOut(message).then(written => { text.textContent = written; }, + error => { + text.replaceWith(el("div", { class: "note" }, "Not read from " + name(message.node) + + ": " + (error.message || String(error)))); + }); return; } document.getElementById("stats").replaceChildren( @@ -2842,9 +2848,7 @@ function follow(id, delay = 2000) { const member = state && state.graph.members[id]; if (!member || !member.dashboard) return fail("it publishes no dashboard (--dashboard-url)"); if (!narrowToken) return fail(tokenWhy); - // Its endpoints, beside its page (`…/admin/dashboard` → `…/admin/dashboard/…`). - const base = member.dashboard.split("#")[0].replace(/\/+$/, ""); - const at = path => new URL(path, base).href; + const at = path => endpointOf(id, path); let url; try { url = at("dashboard/links/own/events"); @@ -2881,6 +2885,25 @@ function follow(id, delay = 2000) { }; followMessages(); } +// A member's endpoint, beside the page it publishes (`…/admin/dashboard` → +// `…/admin/dashboard/`); throws where its address isn't one. +function endpointOf(id, path) { + const member = state && state.graph.members[id]; + const base = ((member && member.dashboard) || "").split("#")[0].replace(/\/+$/, ""); + return new URL(path, base).href; +} +// A subscribed node's message written out, read from it (its console's +// `messages/own/{seq}`) with this page's narrow token, kept on the message once read. +async function writtenOut(message) { + if (typeof message.text === "string") return message.text; + if (!narrowToken) throw new Error(tokenWhy); + const response = await fetch(endpointOf(message.node, "dashboard/messages/own/" + message.ownSeq), + { cache: "no-store", headers: { Authorization: "Bearer " + narrowToken.token } }); + const answer = await response.json().catch(() => ({})); + if (!response.ok) throw new Error(response.status + " " + (answer.error || response.statusText)); + message.text = String(answer.text); + return message.text; +} // Server-sent events from `url`, read with this page's narrow token (`EventSource` // can't send one): `opened` once answered, `event(kind, data)` for each; rejects when it // fails or ends (`offline`: the node stopped). @@ -3581,7 +3604,7 @@ function remoteMessagesArrived(id, entry, list, sent) { for (const message of list) { if (message.way === "refused") continue; const [from, to] = message.way === "sent" ? [id, message.peer] : [message.peer, id]; - const made = { ...message, seq: id + ":" + message.seq, node: id, from, to, + const made = { ...message, seq: id + ":" + message.seq, ownSeq: message.seq, node: id, from, to, related: message.related ? id + ":" + message.related : null }; if (message.way === "sent") noteDelivery(from, to, message.counted, "sent"); else noteDelivery(from, to, message.counted, "got"); -- cgit v1.3.1