aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-03 01:16:41 +0100
committertv <tv@krebsco.de>2026-11-03 01:16:41 +0100
commit72d05e54de4a051148578d8445ad4527550052ff (patch)
tree3e357016e3e81b50983c737cf0cef6e15033a360
parentd2c8d34cb086fd49cfece13d60c22967064ea34a (diff)
dashboard: neater graph title & legend
-rw-r--r--docs/dashboard.md17
-rw-r--r--src/api/dashboard/dashboard.html54
2 files changed, 46 insertions, 25 deletions
diff --git a/docs/dashboard.md b/docs/dashboard.md
index 25bcfe8..a9e4567 100644
--- a/docs/dashboard.md
+++ b/docs/dashboard.md
@@ -51,11 +51,11 @@ the whole width; drag the space right of the map back to bring it again. A
double-click there sets that space back to its default. The sizes are kept in the
browser, for the next time the page is opened.
-The `▾` before a box's title collapses it to its title (`▸` opens it again). A
-collapsed box isn't drawn at all, so it costs the browser nothing while the node
-updates; opened, it shows what's current. The open boxes take the room the collapsed
-ones leave: in the right column they share it as before (the peers take all of it
-when they're the only one open); the map collapsed, the right column takes the width.
+The `▾` before a box's title collapses it to its title (`▸` opens it again); the map
+always stays open. A collapsed box isn't drawn at all, so it costs the browser nothing
+while the node updates; opened, it shows what's current. The open boxes take the room
+the collapsed ones leave: in the right column they share it as before (the peers take
+all of it when they're the only one open).
The right column all collapsed keeps its width: its titles stay at the top, with a
hint that dragging the space beside them to the right hides the column. Which boxes
are collapsed is kept in the browser too.
@@ -117,7 +117,8 @@ legend and the sliders greyed meanwhile); the choice is kept in the browser.
A label on the map that changes, a round trip or a node's name, breathes once: it
glows around its new text and fades (not where the system asks for reduced motion).
-`links: live` beside the box's title says the members tell this node of their links'
+The map's controls are at its top right, over the map as the legend is (below):
+`links: live` says the members tell this node of their links'
changes as they happen. Clicked, it's `links: every 30 s`: the members are only asked
every 30 s, which costs the swarm fewer messages and leaves the map up to 30 s behind
(this node's own links stay current). It's the node's choice, for every page open on
@@ -125,8 +126,8 @@ it, until clicked again or the node restarts.
The legend, over the top of the map, names the transports that appear; its contents
are on an underlay of their own shape, as the map's labels, so they read over what is
-under them, and the map is dragged through it as anywhere else. `hide legend` beside
-the box's title hides it (`show legend` brings it back); the map stays as it is.
+under them, and the map is dragged through it as anywhere else. `hide legend`, among the
+map's controls, hides it (`show legend` brings it back); the map stays as it is.
`layout: static`, beside it, is the default: nodes move only when the map is laid out
(below). A click switches to `layout: dynamic`: each measured link is as
diff --git a/src/api/dashboard/dashboard.html b/src/api/dashboard/dashboard.html
index 25837c8..9132275 100644
--- a/src/api/dashboard/dashboard.html
+++ b/src/api/dashboard/dashboard.html
@@ -172,16 +172,24 @@
/* 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 map and, over its top, the legend: shown or hidden, it takes no room from the
- map, which stays as it is. What is under it shows through; its contents are on an
- underlay of their own shape (as the map's labels), and only its controls take the
- pointer: the map under the rest is dragged as anywhere else. */
+ /* 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
+ shape (as the map's labels), and only buttons and sliders take the pointer: the map
+ under the rest is dragged as anywhere else. */
.map-area { position: relative; flex: 1; min-height: 0; display: flex; }
- #legends { position: absolute; top: 0; left: 0; right: 0; pointer-events: none; }
- #legends .legend > * { filter: drop-shadow(2px 0 0 var(--panel))
+ .map-overlay { position: absolute; top: 0; left: 0; right: 0; pointer-events: none; }
+ /* The controls at the top right, on the legend's first row, which leaves them room
+ (`--controls-width`, as wide as they are); the rows below take the whole width.
+ As tall as a row of the legend. */
+ .map-controls { position: absolute; top: 0; right: 0; display: flex; gap: 6px; }
+ #legends .legend:first-child { padding-right: calc(var(--controls-width, 0px) + 1em); }
+ .map-controls button.toggle { float: none; margin-left: 0; box-sizing: border-box;
+ height: 1.4em; line-height: calc(1.4em - 2px); padding-block: 0; }
+ .map-overlay .legend > *, .map-controls > * { filter: drop-shadow(2px 0 0 var(--panel))
drop-shadow(-2px 0 0 var(--panel)) drop-shadow(0 2px 0 var(--panel))
drop-shadow(0 -2px 0 var(--panel)); }
- #legends button, #legends input, #legends label { pointer-events: auto; }
+ .map-overlay button, .map-overlay input, .map-overlay label { pointer-events: auto; }
/* Hides the legend, and shows it again. */
button.toggle { font: inherit; font-size: 12px; text-transform: none; letter-spacing: 0;
float: right; margin-left: 6px; padding: 0 6px; color: var(--muted); background: none;
@@ -250,10 +258,13 @@
</header>
<main>
<section id="map">
- <h2>Swarm <button id="legend-toggle" class="toggle">hide legend</button>
- <button id="rtt-toggle" class="toggle">layout: static</button>
- <button id="links-toggle" class="toggle">links: live</button></h2>
<div class="map-area">
+ <div class="map-overlay">
+ <div class="map-controls">
+ <button id="links-toggle" class="toggle">links: live</button>
+ <button id="rtt-toggle" class="toggle">layout: static</button>
+ <button id="legend-toggle" class="toggle">hide legend</button>
+ </div>
<div id="legends">
<div class="legend">
<span class="node self">this node</span>
@@ -272,6 +283,7 @@
double-click it for its dashboard</span>
</div>
</div>
+ </div>
<svg id="graph"><g id="view"><g id="flows" pointer-events="none"></g><g id="flashes" pointer-events="none"></g></g></svg>
</div>
</section>
@@ -1501,6 +1513,15 @@ function release() {
}
document.addEventListener("selectionchange", release);
+// The legend's first row leaves the controls room, however wide they are (their texts
+// change).
+{
+ const controls = document.querySelector(".map-controls");
+ const overlay = document.querySelector(".map-overlay");
+ const room = () => overlay.style.setProperty("--controls-width", controls.offsetWidth + "px");
+ room();
+ if (window.ResizeObserver) new ResizeObserver(room).observe(controls);
+}
// The legend, hidden and shown again, over the map: the map stays as it is.
const legendToggle = document.getElementById("legend-toggle");
function showLegend(shown) {
@@ -2360,12 +2381,9 @@ function applySizes() {
const wasHidden = main.classList.contains("right-hidden");
main.classList.toggle("right-hidden", sizes.columns === 1);
main.classList.toggle("right-shut", sizes.columns !== 1 && right.every(shut));
- // What's collapsed gives its room to what isn't: the map collapsed, the right column
- // takes the width but the map's title. The right column all collapsed keeps its
- // width, resized as ever (dragged to the side, it goes).
- const mapShut = shut("map"), rightShut = right.every(shut);
+ // The right column all collapsed keeps its width, resized as ever (dragged to the
+ // side, it goes); the map is never collapsed.
if (sizes.columns === 1) style.setProperty("--columns", "minmax(0, 1fr) 0");
- else if (mapShut && !rightShut) style.setProperty("--columns", "max-content minmax(0, 1fr)");
else if (sizes.columns === null) style.removeProperty("--columns");
else style.setProperty("--columns", "minmax(0, " + sizes.columns + "fr) minmax(0, " +
(1 - sizes.columns) + "fr)");
@@ -2479,7 +2497,10 @@ try {
collapsed.add(id);
}
} catch (_) {}
-for (const section of document.querySelectorAll("main > section")) {
+// The map isn't collapsed: it is what the page is about (a choice kept from before isn't
+// taken).
+collapsed.delete("map");
+for (const section of document.querySelectorAll("main > section:not(#map)")) {
const button = el("button", { class: "collapse" });
section.querySelector("h2").prepend(button);
const show = () => {
@@ -2497,7 +2518,6 @@ for (const section of document.querySelectorAll("main > section")) {
applySizes();
if (collapsed.has(section.id)) return;
if (section.id === "console-box") showConsole();
- if (section.id === "map") layoutAgain();
release();
render();
});