aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/api/dashboard/dashboard.html111
1 files changed, 84 insertions, 27 deletions
diff --git a/src/api/dashboard/dashboard.html b/src/api/dashboard/dashboard.html
index e5ed7a7..3eda30a 100644
--- a/src/api/dashboard/dashboard.html
+++ b/src/api/dashboard/dashboard.html
@@ -177,6 +177,11 @@
/* After the sliders, as tall; the kinds and the sliders greyed while the dots are off. */
.legend button.toggle { float: none; margin-left: 0; line-height: 1em; }
.legend.off .message, .legend.off .slider { opacity: .45; }
+ /* The LINKS kind, a toggle: shown (in the console, as dots, in the rates) or not. */
+ .legend button.message::before { content: ""; display: inline-block; vertical-align: middle;
+ margin-right: 4px; }
+ .legend button.message.hidden-kind { text-decoration: line-through; }
+ #console.no-links > div.links-kind { display: none; }
/* The map and, over its top, its controls (links, layout, legend) and the legend:
they take no room from the map, which stays as it is, the legend shown or hidden.
What is under them shows through; their contents are on an underlay of their own
@@ -312,7 +317,7 @@
<div id="column-hint" class="note">Drag the space left of these boxes to the right to
hide the column.</div>
<section id="console-box">
- <h2>Console <label class="choice" title="Only the messages with the node or over the link in focus (clicked on the map or in the peers); all while nothing is"><input type="checkbox" id="console-filter">filter selected</label><span id="console-following" class="following" title="Scrolled to the bottom: new messages are scrolled to as they come; scroll up to stop">following…</span><button id="console-clear" class="toggle inline" title="Empty this page's console: what comes from now on fills it again">clear</button><span id="console-rates" class="rates" title="Messages and bytes per second sent and received, as load averages are: over the last 1, 5 and 15 minutes (exponentially weighted), since the page was opened; with the filter, those with the node or over the link in focus"></span></h2>
+ <h2>Console <label class="choice" title="Only the messages with the node or over the link in focus (clicked on the map or in the peers); all while nothing is"><input type="checkbox" id="console-filter">filter selected</label><span id="console-following" class="following" title="Scrolled to the bottom: new messages are scrolled to as they come; scroll up to stop">following…</span><button id="console-clear" class="toggle inline" title="Empty this page's console: what comes from now on fills it again">clear</button><span id="console-rates" class="rates" title="Messages and bytes per second sent and received, as load averages are: over the last 1, 5 and 15 minutes (exponentially weighted), since the page was opened; with the filter, those with the node or over the link in focus; without LINKS while their kind is off in the map's legend (they go only while a dashboard asks)"></span></h2>
<div class="scroll" id="console-scroll"><div id="console" class="console"></div>
<div id="console-none" class="note" hidden>No information available</div></div>
</section>
@@ -1860,7 +1865,8 @@ if (handOver) {
moveView();
}
// The fragment that opens another node's dashboard (`id`'s) as this page is now
-// (`handOver`). What is in focus stays, but `id` itself: its page is its own.
+// (`handOver`). What is in focus stays, but `id` itself: its page is its own. Also this
+// node's own page, reloaded for a new version (`id` null).
function handOff(id) {
const settings = {};
try {
@@ -1877,7 +1883,7 @@ function handOff(id) {
focused: focused === id ? null : focused,
settings,
legend: !document.getElementById("legends").hidden,
- links: state.graph.live !== false,
+ links: state ? state.graph.live !== false : undefined,
figures: [...open],
document: [...documentOpen],
}));
@@ -2324,6 +2330,39 @@ let flowSpeed = .3;
try { flowSpeed = Number(localStorage.getItem("continuum-dot-speed")) || .3; } catch (_) {}
flowSpeed = Math.min(.9, Math.max(.1, flowSpeed));
let dotSlider = null, speedSlider = null;
+// LINKS and their replies (`traffic`'s `links`), and whether they are shown
+// (`linksToggleInLegend`).
+const isLinks = kind => kind === "links";
+let linksShown = true;
+try { linksShown = localStorage.getItem("continuum-links-shown") !== "off"; } catch (_) {}
+// The LINKS kind in the legend, a toggle: whether LINKS and their replies, which go only
+// while a dashboard asks for the members' links, are shown: in the console, as dots on
+// the map, and counted in the console's rates. Off, what is shown is the swarm's own
+// traffic (kept in this browser).
+function linksToggleInLegend(colour) {
+ const button = el("button", { class: "toggle message", style: "--colour: " + colour }, "links");
+ const show = () => {
+ button.classList.toggle("hidden-kind", !linksShown);
+ document.getElementById("console").classList.toggle("no-links", !linksShown);
+ button.title = linksShown
+ ? "LINKS go only while a dashboard asks for the members' links. Click to leave them out: of the console, the dots and the rates"
+ : "Left out of the console, the dots and the rates. Click to show them again";
+ };
+ button.addEventListener("click", () => {
+ linksShown = !linksShown;
+ try { localStorage.setItem("continuum-links-shown", linksShown ? "on" : "off"); } catch (_) {}
+ if (!linksShown) {
+ for (let i = flows.length - 1; i >= 0; i--) if (isLinks(flows[i].kind)) flows.splice(i, 1);
+ }
+ show();
+ showRates();
+ filterConsole();
+ const scroll = document.getElementById("console-scroll");
+ scroll.scrollTop = scroll.scrollHeight;
+ });
+ show();
+ return button;
+}
function showMessagesLegend() {
const legend = document.getElementById("messages-legend");
legend.classList.toggle("off", !flowsShown);
@@ -2343,8 +2382,9 @@ function showMessagesLegend() {
try { localStorage.setItem("continuum-dot-speed", String(flowSpeed)); } catch (_) {}
});
// Before the toggle, which ends the line.
- legend.prepend(...KIND_COLOURS.map(([kind, colour]) =>
- el("span", { class: "message", style: "--colour: " + colour },
+ legend.prepend(...KIND_COLOURS.map(([kind, colour]) => kind === "links"
+ ? linksToggleInLegend(colour)
+ : el("span", { class: "message", style: "--colour: " + colour },
kind === "paths" ? "paths (pings)" : kind)),
el("label", { class: "slider" }, "size", slider),
el("label", { class: "slider" }, "speed", speed));
@@ -2400,7 +2440,7 @@ function trafficArrived() {
for (const [kind, count] of Object.entries(counts[way])) {
const was = before[way][kind] || { messages: 0, bytes: 0 };
const grown = Math.max(0, count.messages - was.messages);
- if (grown > 0) {
+ if (grown > 0 && (linksShown || !isLinks(kind))) {
kinds[kind] = { messages: grown, bytes: Math.max(0, count.bytes - was.bytes) };
total += grown;
}
@@ -2425,36 +2465,44 @@ function trafficArrived() {
// weighs in by how long ago it was, `e^(-age/window)`), from the counts each update
// brings. The first sample is taken as it is, so they needn't climb from 0 first. ---
const RATE_WINDOWS_S = [60, 300, 900];
-// By peer, and in all (`null`): the averages (`messages`, `bytes`: per window); and when
-// the counts were last taken.
+// By peer, and in all (`null`), with LINKS (`all`) and without (`withoutLinks`): the
+// averages (`messages`, `bytes`: per window); and when the counts were last taken.
const rates = new Map();
let ratesAt = null;
function measureRates(current) {
const now = performance.now();
if (lastTraffic !== null && ratesAt !== null && now > ratesAt) {
const secs = (now - ratesAt) / 1000;
- // Of `what` (`messages`, `bytes`), sent and received, by how much each grew.
- const grown = (counts, before, what) => ["sent", "received"].reduce((sum, way) => {
- const total = of => Object.values(of[way]).reduce((sum, count) => sum + count[what], 0);
+ // Of `what` (`messages`, `bytes`), sent and received, by how much each grew; LINKS
+ // left out if `withoutLinks`.
+ const grown = (counts, before, what, withoutLinks) => ["sent", "received"].reduce((sum, way) => {
+ const total = of => Object.entries(of[way]).reduce((sum, [kind, count]) =>
+ withoutLinks && isLinks(kind) ? sum : sum + count[what], 0);
return sum + Math.max(0, total(counts) - total(before));
}, 0);
const average = (known, rate) => known ? known.map((average, i) =>
rate + (average - rate) * Math.exp(-secs / RATE_WINDOWS_S[i])) : RATE_WINDOWS_S.map(() => rate);
- const sample = (key, messages, bytes) => {
- const known = rates.get(key);
- rates.set(key, { messages: average(known && known.messages, messages / secs),
- bytes: average(known && known.bytes, bytes / secs) });
+ const sample = (key, variant, messages, bytes) => {
+ const known = rates.get(key) || {};
+ const was = known[variant];
+ known[variant] = { messages: average(was && was.messages, messages / secs),
+ bytes: average(was && was.bytes, bytes / secs) };
+ rates.set(key, known);
};
- let messagesInAll = 0, bytesInAll = 0;
- for (const [id, counts] of current) {
- const before = lastTraffic.get(id);
- if (!before) continue;
- const messages = grown(counts, before, "messages"), bytes = grown(counts, before, "bytes");
- sample(id, messages, bytes);
- messagesInAll += messages;
- bytesInAll += bytes;
+ for (const variant of ["all", "withoutLinks"]) {
+ const withoutLinks = variant === "withoutLinks";
+ let messagesInAll = 0, bytesInAll = 0;
+ for (const [id, counts] of current) {
+ const before = lastTraffic.get(id);
+ if (!before) continue;
+ const messages = grown(counts, before, "messages", withoutLinks);
+ const bytes = grown(counts, before, "bytes", withoutLinks);
+ sample(id, variant, messages, bytes);
+ messagesInAll += messages;
+ bytesInAll += bytes;
+ }
+ sample(null, variant, messagesInAll, bytesInAll);
}
- sample(null, messagesInAll, bytesInAll);
// Peers gone from the list: their averages too.
for (const key of [...rates.keys()]) if (key !== null && !current.has(key)) rates.delete(key);
}
@@ -2462,11 +2510,13 @@ function measureRates(current) {
showRates();
}
// The averages shown: in all, or with the filter, of the peer in focus; none (–) for a
-// link between other nodes, a node that is no peer, or before two updates came.
+// link between other nodes, a node that is no peer, or before two updates came. With
+// LINKS or without (`linksShown`).
function showRates() {
const filtering = consoleFiltered && focused !== null;
const peer = filtering ? peerOf(focused) : null;
- const shown = filtering && peer === null ? undefined : rates.get(peer);
+ const both = filtering && peer === null ? undefined : rates.get(peer);
+ const shown = both && both[linksShown ? "all" : "withoutLinks"];
const format = rate => rate < 100 ? rate.toFixed(2) : String(Math.round(rate));
// Bytes in the unit the largest of the three needs, all three alike.
const largest = shown ? Math.max(...shown.bytes) : 0;
@@ -2563,7 +2613,8 @@ function filterConsole() {
const filtering = consoleFiltered && focused !== null;
log.classList.toggle("filtering", filtering);
document.getElementById("console-none").hidden =
- !filtering || log.querySelector(":scope > div.peer-focused") !== null;
+ !filtering || log.querySelector(":scope > div.peer-focused" +
+ (linksShown ? "" : ":not(.links-kind)")) !== null;
const view = filtering ? focused : null;
if (view !== consoleView) {
consoleView = view;
@@ -2577,6 +2628,7 @@ function consoleLine(message) {
"data-related": message.related || "",
"data-peer": message.peer,
class: (shownMessage && shownMessage.seq === message.seq ? "focused" : "") +
+ (isLinks(message.counted) ? " links-kind" : "") +
(relatedMarked !== null && message.related === relatedMarked ? " related" : "") +
peerClasses(message.peer),
title: message.way === "refused" ? "refused: the transport wouldn't send it" : "" },
@@ -2706,6 +2758,11 @@ function connect() {
if (snapshot.page !== PAGE_VERSION) {
events.close();
status("new version; reloading…", false);
+ // As this page is now (`handOff`): the map, what's open, the focus; reloaded with
+ // the URL's fragment, which the new page takes (`handOver`).
+ try {
+ history.replaceState(null, "", location.pathname + location.search + handOff(null));
+ } catch (_) { /* not where history works: reloaded as it is */ }
location.reload();
return;
}