aboutsummaryrefslogtreecommitdiffstats
path: root/docs/dashboard.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/dashboard.md')
-rw-r--r--docs/dashboard.md324
1 files changed, 324 insertions, 0 deletions
diff --git a/docs/dashboard.md b/docs/dashboard.md
new file mode 100644
index 0000000..b7e3069
--- /dev/null
+++ b/docs/dashboard.md
@@ -0,0 +1,324 @@
+# The dashboard
+
+A page showing one node's view of the swarm, live: a map of the members and the links
+between them, the node's peers, its figures, and the document, which it can also edit.
+This page is about using it; how it works underneath (events, LINKS, what is sent
+when) is at the end, [Underneath](#underneath).
+
+## Opening it
+
+The dashboard is in builds with the `dashboard` cargo feature:
+
+```sh
+cargo build --release --features mycelium,dashboard
+```
+
+It is at `/admin/dashboard` on the node's HTTP API, by default
+`http://127.0.0.1:3000/admin/dashboard`, and asks for a user from `--htpasswd`, as the
+rest of the API does. The API listens on localhost unless `--bind-addr` says otherwise;
+to look at a node on another machine, forward the port (`ssh -L 3000:localhost:3000
+<machine>`) or put a TLS proxy in front ([`trust.md`](trust.md)).
+
+Every node has its own dashboard, and shows the swarm as that node sees it: what it
+knows of the others, what they report to it, and its own figures.
+
+## The page
+
+At the top, the node's alias and node ID; in orange, when this node's clock is off
+from its peers' ([`measurements.md`](measurements.md), Clocks), by how much; and the connection's status:
+
+| Status | Meaning |
+|---|---|
+| `connecting…` | Opening the page's event stream. |
+| `live` | Up to date. The dot beside it breathes with every update from the node. |
+| `reconnecting…` | The stream broke; the browser reconnects by itself. |
+| `disconnected; retrying…` | The node answered with an error (it may be up but not serving the page yet); again in 5 s. |
+| `node stopped; reconnecting…` | The node said it is stopping; again in 3 s, until it is back. |
+| `paused while hidden` | The page was hidden (another tab, a minimised window) for 30 s: it closed its stream, so the node does no work for it. Shown again, it reconnects. |
+| `new version; reloading…` | The node now serves another version of the page; it is loaded. |
+
+A click on the status refreshes everything at once while live, and reconnects at once
+otherwise. While not live, the page is dimmed and shows what it had last: ages stop
+counting.
+
+Below, five boxes: the swarm's map on the left; the peers, the figures, the document
+and the console on the right. In a narrow window they are stacked.
+
+Dragging the space between two boxes resizes them: between the map and the right
+column, below the peers, between the figures and the document, and above the
+console. Dragged all the way to the right, the right column goes, and the map takes
+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 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.
+
+## The map
+
+### Nodes
+
+Every member of the swarm, by alias (or a shortened node ID):
+
+* **This node**: filled blue, slightly larger.
+* **Filled**: the member reports its links to this node.
+* **Hollow**: it reports none: it doesn't answer this node's LINKS, or its last answer
+ is more than 90 s old (it is unreachable from here, stopped, or doesn't yet take this
+ node for a member).
+* **Red outline**: this node holds it dormant (it has been silent, or said farewell).
+* **Dashed orange ring**: its clock is off the swarm's: it differs from the median of
+ the members' clocks by more than `--clock-skew-warn`, beyond what the measurements
+ allow. The members report their peers' clocks with their links, so this works for
+ members this node doesn't reach itself; it needs three members compared.
+
+### Links
+
+Two kinds, drawn over each other:
+
+* **Intended** (dashed grey): the neighbours the member list gives each member, as this
+ node computes them. Shown only where no measured link backs it.
+* **Measured** (coloured): the links members report. A link is drawn if either end
+ reports it.
+
+A measured link is coloured by transport (blue Mycelium, purple libp2p, teal Tor). The
+transport in use is the thick line in the middle, labelled with its round trip in
+milliseconds; the other transports the peers can reach each other on are thin lines
+beside it, from node to node, bowing out a little (furthest at the middle), with their
+round trips smaller. The round trips are drawn near the end that
+measured them: of the two ends' reports, the one drawn is the one with a round trip,
+else the fresher. Then:
+
+* **Solid**: direct.
+* **Dashed**: through a relay.
+* **Dotted**: one end reports no links: what is drawn may be out of date.
+* **Narrow, over a red band**: either end holds the other dormant, or the path in use
+ is down (no path to the peer answers; `/peers` shows its connection as `null`).
+
+Messages this node exchanges run along its links as dots, from sender to receiver.
+They come from the per-peer message counts, which arrive every 2 s: what was sent and
+received since is spread over the next 2 s, at most 6 dots per link and direction,
+shared out by message kind, each kind in its colour (named in the legend; the console
+colours them alike). A dot is as large as its kind's messages were on average
+(by the size's logarithm: a ping small, a large sync message large); the slider right
+after their legend makes all dots larger or smaller, kept in the browser. A picture
+of the traffic, not a record of it; links between other members show none.
+`messages: on` after the slider hides them (`messages: off` shows them again; the
+legend and the slider 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'
+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
+it, until clicked again or the node restarts.
+
+The legend above the map names the transports that appear; `hide legend` beside the
+box's title gives its room to the map (`show legend` brings it back).
+
+`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
+long as its round trip is slow, against the median link's, by the square root, from
+0.4 to 2.5 times the usual length; links without a round trip keep the usual length.
+In either layout, a link only intended (dashed grey) gives way to the measured ones: it
+pulls its ends together only a little when stretched, and doesn't push them apart. As
+round trips change, the map moves to the new lengths smoothly. Clicked again, it's
+static, the nodes staying where they are. The choice is kept in the browser.
+
+Hovering over a node or a link highlights it and shows a tooltip: for a link, each of
+its lines with transport, direct or relayed, round trip, and how long ago it was
+measured.
+
+### Moving around
+
+* **Wheel**, or pinching on a touch screen: zoom, around the pointer (or between the
+ fingers, the map moving with them).
+* **Drag beside the nodes, or along a link**: pan.
+* **Two fingers turning**, on a touch screen: the map turns with them; labels stay
+ upright.
+* **Drag a node**: move it. On a dynamic map, the others follow at once, and once
+ let go of, it moves back among them; on a static map it stays where it is put until
+ the map is laid out again.
+* **Double-click a node**: pin it where it is (a dot in its middle): the layout leaves
+ it there. A double-click whose second press drags the node pins it where it's
+ dropped. Double-clicked again, it's let go, and moves to its place among the others
+ (on a static map, only it moves).
+
+The map is laid out when the page opens, and again when the window or the box is
+resized. A member that comes later is placed among the others, which stay where they
+are, as they do when links appear or disappear; pinned nodes always stay put. The view and pins last as long as
+the page is open.
+
+## Focus
+
+What the Figures box shows is what is in focus:
+
+* **Nothing** (the default): this node's figures.
+* **A node** (click it on the map, or its row in Peers): this node's entry for that
+ peer (as `/peers` has it), and the links that member reports. Ringed on the map,
+ highlighted in Peers. A second click on its row, a click beside the nodes or a click
+ on this node goes back.
+* **A link** (click it): whether the member list intends it, each end's report of it
+ (transport, relayed, round trip, dormant, its other transports, and how old the
+ report is), and, where this node is an end, the link as this node has it in `/peers`:
+ where messages go, its paths (each with its connection and round trip), and the
+ addresses known of the other end. Both ends are ringed.
+
+The focus is in the page's address (`#<node ID>`, or the two IDs of a link), so a
+reload keeps it and a link to the page can name it.
+
+## Peers
+
+Every peer this node knows, with:
+
+| Column | Meaning |
+|---|---|
+| peer | Alias, or a shortened node ID (the full one in the tooltip). |
+| status | `active`, `dormant` (for how long: in the tooltip) or `unknown` (not heard from since this node started). |
+| seen | How long ago this node last heard from it. |
+| via | The address this node sends to it at, and whether `direct` or `relayed`. |
+| rtt | The round trip on that path. |
+| clock | How far its clock is ahead of this node's (behind: `−`), give or take (in the tooltip); in orange beyond `--clock-skew-warn`, for sure ([`measurements.md`](measurements.md), Clocks). |
+| neighbour | Whether this node keeps contact with it (HELLOs, keepalives, sync); others it only answers. |
+| same epoch | Whether it is on this node's epoch ([`remakes.md`](remakes.md)); blank while unknown. |
+
+The box is as tall as its rows, up to eight; then it scrolls.
+
+## Figures
+
+The figures of what is in focus, as a tree: this node's `/admin/stats`, or a peer's
+`/peers` entry and links. Branches stay as opened across updates, and ages count on
+between them. What each figure means: [`measurements.md`](measurements.md).
+
+## Document
+
+The swarm's document, as this node holds it, its first level open. A branch is read
+when opened, and read again when the document changes there; what changed or came
+breathes once, as the map's labels do: its row glows and fades.
+
+Editing writes to the document, as the user the page was opened as, and the write goes
+to the whole swarm like any other:
+
+* **Double-click an entry** to edit it: a string as text, anything else as JSON, a map
+ or list whole. Saved, it is shown open. The red `Delete` beside `Save` deletes the
+ entry instead, on a second click (`Delete: sure?`) from half a second to 3 s later: a
+ double-click doesn't delete.
+* **`+` on a map's row** adds a key and its value.
+* **`+` on a list's row** inserts an item at an index (`-1`: at the end); the items
+ from there on move up.
+
+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.
+
+## Console
+
+The messages this node exchanged with its peers, one by one: when (on the 24-hour
+clock, to the millisecond), which way (`→` sent, `←` received, `↛` refused: the
+transport wouldn't send it), with whom, the type (`HELLO`, `SYNC`, `STATUS_REPLY`,
+`LINKS`, …, in its kind's colour, as on the map) and the size. The latest 500, the
+newest at the bottom; scrolled along while at the bottom. They arrive with every update
+(every 2 s). The PINGs the paths layer sends on its own, and the PONGs it answers
+PINGs with, are listed too: under the node at the address they went to, where it is
+known, else under the address. Not listed: WHO and ME, which stay in the layers
+below the node (they are counted, [`measurements.md`](measurements.md), Traffic).
+
+A click on a message shows it in the figures box: when, with whom, its type and size,
+and the message written out (its bytes as sizes). A click on it again, or on anything on
+the map or in the peers, goes back.
+
+Pointing at a message marks the others of its exchange (a lighter grey, and a bar on
+their left); while one is shown, its exchange stays marked. An exchange is a PING and
+its PONG (by their nonce, wherever they were listed), a HELLO and its replies (by the
+run of the node that sent the HELLO), a LINKS and its replies (by their nonce), a blob's
+requests and answers, from whichever peers (by the blob), and a delta offered in a SYNC
+with the blob it is fetched as. A STATUS and its reply carry nothing that ties them, nor
+does an ordinary SYNC.
+
+## While you read
+
+Updates don't pull things from under you: while text is selected in a box, that box
+waits, and while an entry is edited, the branch it is in waits; both catch up once you
+are done. The map isn't redrawn under the pointer: what you point at stays put.
+
+## When something looks odd
+
+* **The status stays at `connecting…`, or the page doesn't load**: the node isn't
+ built with `dashboard` (404), or the user isn't in `--htpasswd` (401).
+* **A hollow node**: no links report from it. If it is also dormant here, it is
+ probably down or unreachable; if not, it may not take this node for a member yet
+ (it hasn't registered it), or its answers don't arrive.
+* **Only dashed grey between two nodes**: the member list wants them as neighbours, but
+ neither reports a link to the other: they haven't reached each other (yet).
+* **A dotted link**: one end has stopped reporting; the link is as it was last
+ reported.
+* **A red band**: one end holds the other dormant. If the other end still reports the
+ link, the trouble is likely in one direction only.
+
+## Underneath
+
+The page is `GET /admin/dashboard`, authenticated as the rest of `/admin`. It is kept up
+to date through Server-Sent Events (`/admin/dashboard/events`);
+`/admin/dashboard/state` has the latest state at once.
+
+**Events.** One producer per node serves every client, and runs only while one is
+connected (or for a minute after a `/state` request); a page hidden for 30 s
+disconnects until it is shown again. Every 2 s it builds the state; when anything but
+ages changed, it sends every client a JSON merge patch onto the state before. Nothing
+changed, nothing is sent. Ages are as of the event, and the page counts them on. A click
+on the status refreshes at once (`POST /admin/dashboard/refresh`). When the node stops,
+every client gets an `offline` event and its stream ends, so stopping doesn't wait for
+them; the page reconnects once the node is back, and reloads if the node now serves
+another version of it (the state's `page`).
+
+**The map's graphs.** The intended one is the neighbours the member list gives every
+member (`swarm::topology`), as this node computes them. The measured one is every
+member's links as it reports them in reply to a LINKS (sent to every member, at most
+512, while the producer runs). The LINKS subscribes: the member replies again whenever
+its links change (a peer, a transport, relayed, dormant, or a round trip moved by a
+fifth; it looks every 2 s), for 90 s, renewed every 30 s (each renewal answered), and
+ended when the producer stops; a node that is gone stops it by not renewing. A reply
+older than 90 s is left out. A member keeps 64 subscribers at most. With `links: every
+30 s` (`POST /admin/dashboard/links?live=false`; `true` back), the subscriptions end
+and the members are only asked every 30 s, for every page open on this node, until
+asked otherwise or the node restarts.
+
+The links a member reports: its neighbours and the peers it holds dormant (a neighbour
+that goes dormant is one no more, but stays listed until it is heard from again), by
+which transport, whether only through a relay, the round trip (as the paths layer
+measures it), whether it holds the peer dormant or the path is down, the peer's other
+transports with a path up (each with its fastest path's round trip, and whether it goes
+only through a relay), and the peer's clock against the member's ([`measurements.md`](measurements.md),
+Per peer, `clock`). A member answers a LINKS from members only, at most every 5 s, and
+every build answers, with or without the feature (`swarm::links`).
+
+**Clocks.** The clocks in the links chain every member's clock to this node's, along
+the narrowest bounds, and the map takes each against their median: a member more than
+`--clock-skew-warn` off it, beyond the chained bound, is ringed. With fewer than three
+members compared, none is.
+
+**Messages on the map** come from the per-peer message counts in the state (`/peers`,
+`traffic`), not from messages of their own. **The console's** come from a log the node
+keeps only while the producer runs (`traffic::MessageLog`, the latest 1000), sent as a
+`messages` event every 2 s with those since the last.
+
+**The document** is read through the store API, a level at a time as it is opened
+(`GET /store?shallow=true`, then `/store/<path>?shallow=true` for each map opened, and
+`/store/<path>` for a long value). Where it changes, the node says so (a `document`
+event with the paths changed, at most every 2 s), and the page reads again what is open
+there. A key with `/` in it (or an empty key) can't be opened: store paths are split at
+`/`.
+
+Edits go through the store API too, as the user the page was opened as: `PUT
+/store/<path>` (a text saved becomes a string; the root isn't written whole), a list's
+items each on its own (`PUT /store/<path>/<index>`), a new key with `PUT
+/store/<path>/<key>`, an inserted item with `POST /store/<path>/<index>` (`-1` the end;
+an index past the end is refused), and `DELETE /store/<path>`. The dashboard itself
+writes nothing to the document: only edits do.