# 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` (or one a reverse proxy names, `--user-header`), 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 `) or put a TLS proxy in front ([`operations.md`](operations.md), Behind a reverse proxy). 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, all at one speed: over a longer link they arrive later. 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 `size` slider right after their legend makes all dots larger or smaller, and the `speed` slider after it makes them run faster or slower (0.1 to 0.9 map units per millisecond, 0.3 at first: a link of 200 in about 0.7 s); both are kept in the browser. A picture of the traffic, not a record of it; links between other members show none. `messages: on` after the sliders hides them (`messages: off` shows them again; the 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' 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**: open its dashboard, in this page, where the node publishes one (`--dashboard-url`, in the member list; e.g. behind a reverse proxy, [`operations.md`](operations.md)). The other dashboard asks for its own credentials where its proxy or node does. It opens as this one was left: the map's nodes where they were (on a dynamic map they then move to their places among the other node's links, smoothly; new ones are placed among them), the view, the pins, what is in focus, the legend shown or hidden, what is open in the figures and in the document, `links: live` or `every 30 s` (the other node's choice, for every page open on it), and this browser's settings for the page (sizes, collapsed boxes, the layout, the messages' size and speed, the console's filter), which are per host otherwise. They travel in the URL's fragment, which no request carries, and the page removes it at once. Links are drawn as the other node sees them. Where it publishes none, its halo flashes red and "no dashboard" rises from it, fading out. * **Middle-click a node**: pin it where it is (a dot in its middle): the layout leaves it there. Dragged with the middle button, it is pinned where it's dropped. Middle-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 (`#`, 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, which `following…` beside the title says. `clear` empties it; what comes from then on fills it again. 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. The map marks the message's two nodes and the link between them in the grey its line has in the console, each in its own shape: a circle around each node, and the link's lines as they bow. The message shown and the one pointed at are both marked, each in its line's grey; where they mark the same node or link, the shown one's wins. The other way round, the messages exchanged with a node, or over one of this node's links, are tinted in the colour of its halo on the map: lightly while it is pointed at there, stronger while it is in focus (clicked there or in the peers); where both tint a message, in focus wins. `filter selected`, beside the console's title, shows only the messages with the node or over the link in focus (clicked on the map or in the peers; pointing at one doesn't count), and all of them while nothing is. Where there are none to show (a link between two other nodes, a node this one exchanges nothing with), it says so. Whenever what it shows changes (turned on or off, another focus, or none), the console goes to the newest message and follows from there. The choice is kept in the browser. At the right of the console's title, `msg/s` gives the messages per second sent and received, as load averages do: over the last 1, 5 and 15 minutes (exponentially weighted), since the page was opened, from the message counts each update brings; below it, the bytes per second alike, in `B/s`, `KiB/s` or `MiB/s`, whichever the largest of the three needs. With the filter on, they are those with the node or over the link in focus; `–` where there are none. ## 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. A node that just joined through others shows as soon as one of them reports a link to it, hollow until its registration reaches this node and it is asked for its links (within 30 s). * **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. A member this node can't reach (on none of its transports: one on Tor alone, from a node on libp2p alone) is asked through a peer that can: an active member it reaches that, by the member list, shares a transport with the member, else one on a transport this node isn't on (the lowest pair score of several). The LINKS names the members to ask (`of`): the peer asks them on this node's behalf, itself or again through a peer of its own (three nodes at most), for as long as this node asked, and passes their links on in its replies (`relayed`, each with its age), several together: it holds what arrives a while. The asker's window (250 ms, at most 5 s) is for the whole way: each hop holds its share of what is left, the window divided among it and the hops that may follow (at least 10 ms), and passes the rest on, so links reach the asker within the window however long the chain. Such a subscription isn't ended by a message: every hop's runs out unless renewed, so it ends within 90 s of the page closing or the node going away. A hop renews its own as soon as it is renewed, for the same time, so the hops run in step, each a message's delay behind the one before (it never runs out first), and a renewal keeps its nonce, so what is on its way under the last one still counts. A renewal lost on the way leaves the hops behind it to run out at the old time: with 90 s renewed every 30 s, one lost costs nothing. 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 asks for each peer that has it relay at most 512 members at once and 32 a second on average (the rest of a request is dropped); 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/?shallow=true` for each map opened, and `/store/` 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/` (a text saved becomes a string; the root isn't written whole), a list's items each on its own (`PUT /store//`), a new key with `PUT /store//`, an inserted item with `POST /store//` (`-1` the end; an index past the end is refused), and `DELETE /store/`. The dashboard itself writes nothing to the document: only edits do.