# 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, subscriptions, 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`, for a user a reverse proxy names (`--user-header`) or one logged in on the page `/login`, which a browser opening the dashboard without one is sent to. There, a user logs in with its password (`--htpasswd`, the same file the API's basic authentication takes, which the dashboard never asks for itself, nor takes from the browser: no browser password prompt, the node never challenges a browser), or with a token: from `continuum-signed login` (by key, [`access.md`](access.md), Tokens), or any other. The page keeps a session for the browser, a cookie no script reads and no other site's page sends, renewed while the dashboard is open (half way through the token's life, looked at again when the page shows, so after the computer slept too) until the login ends (`--session-lifetime`), then back to the login page; it ends with the token's lifetime once not renewed, or with "Log out of this browser" on the login page, or "log out" in the user's menu on the dashboard (its name, top right), which ends this browser's session only. Where the page got in by what it can't end, the proxy's, "log out" is greyed, and says how instead. The menu's "credentials…" shows the user's keys, each by its name and as much of the key as fits the dialog, cut in the middle where it doesn't (its end and comment always shown, the whole in its tooltip), and `edit` at the right: its name and `.pub` line in fields, to save, or to delete with the red `Delete` (on a second click from half a second to 3 s after the first, as in the document box; the last key only where the user has a password on this node, which it logs in by then); or one added below; what fails says why where it was asked, to be set right and tried again. Under a strict policy, it says how to sign them anew (`continuum-signed key-add`); and where the password is: in each node's `--htpasswd`. The page and the login page run their own script only (a content security policy naming it by its hash), and no other page may frame them. 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); 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. ## The map ### Nodes Every member of the swarm, by alias (or a shortened node ID): * **This node**: filled blue, slightly larger. * **Filled**: the page has the member's links: this node's own, and those of the nodes it subscribes to (below). * **Hollow**: it has none of its links: the page doesn't subscribe to it, or doesn't reach it. * **Black centre, ring of this node's blue**: subscribed: the page reads its links from it, directly (below); the ring grey while the browser doesn't reach it (why, in its tooltip). * **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 page compares the clocks the reports on the map carry: this node's peers', and the peers' of the nodes you subscribe to, so subscribing reaches members this node doesn't; it needs three members compared. * **Amber**: it coordinates a remake of this node's epoch (then also with a golden halo, below), or voted for one, while that remake is open: voting, or committed until the member switched. A small amber breath as a remake is proposed (its coordinator) and as each vote comes; a short pop once it's decided, green if committed, red if aborted (or expired), and the nodes turn back. While votes are collected (`settle`), and once committed, this node's wait to switch (`switchDelay`), the coordinator has a golden halo (within the one of a node in focus, over it) that wanes: whole at first, its end goes back counterclockwise to the top of the screen, however the map is turned; the switch's whole again once committed, gone as this node switches; whole while no wait is known yet (just proposed, or committed before this node saw it). A wait mostly over before the page saw it is shown whole, only faster. Settled but not decided yet, the coordinator is hollow, with a golden question mark in it. Of the remakes in this node's document: every member's vote shows, as it reaches this node. * **A golden glow**, growing from it and fading, once: it just entered a new epoch (a remake, [`remakes.md`](remakes.md)). For this node's peers and the nodes subscribed to (their links say their epoch), as they change while the page is open: so after a remake, a wave across the map as the members switch. ### 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; a link to a node whose links the page doesn't read (not subscribed, drawn hollow) is drawn as the other end reports it, which its tooltip says. Then: * **Solid**: direct. * **Dashed**: through a relay. * **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. Each follows the line it went over: a PING or PONG of the paths layer, which names the address it went to, the line of that address's transport (curved, beside the one in use); any other, the line in use. They come from the node's message log as it goes: the node sends what it logged a tenth of a second after the first of them. Each node's stream comes its own way, a subscribed node's over the network to it, so they arrive later than this node's, each by its path. So messages run not as they arrive but when they went, on one timeline: each at the time its node logged it, taken back by how far that node's clock is ahead of this node's (as the map chains the clocks), a while behind real time. How far behind (`behind: …` after the sliders) is the most any stream lagged within the last 30 s and a tenth of a second more; its tooltip shows how it came about (which node's stream lagged the most, by how much, and the margin) and the three slowest streams and this node's own, each with the most and the least it lagged, and of that how long its batches waited in their node (from logging the first message to sending the batch: the node's own clock, exact) and took on the way (from sending to arriving), so a slow node or a slow path shows. Lags are measured by the browser's clock against this node's: one that is off adds to all of them alike. Messages of different nodes then run as far apart as they went, give or take how well their clocks are known (within the bounds the clock chain has). A message the page knows only from its receiver (its sender's messages aren't read: not subscribed) arrives at the time its receiver logged it, starting a crossing before. Of more than 6 at once on a link and way, 6 run, shared out by message kind, spread over the moment; each kind in its colour (named in the legend; the console colours them alike). Messages lagging more than 10 s (after a gap: the computer slept, a stream stalled) don't run. A dot is as large as its message (by the size's logarithm: a ping small, a large sync message large; shared out, as its kind's messages on average); 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. A subscribed node's messages with its peers run along its links too, from its message log as it goes (read from it with the page's token, as its links are). Each message runs once, as it left, from its sender where the page reads the sender (this node's, or a subscribed node's): so it runs the way it went, also where it never arrived. Only a message from a node the page doesn't read runs as it arrived, from its receiver's. Links between other members show none. Where the page reads both ends of a link, what one sent the other should count received: a message sent and not received within 5 s makes the link breathe red once, its tooltip saying how many didn't arrive. Every kind counts but the paths and identity layers' (PINGs, WHO), which this node counts under addresses, not nodes. This node's side is its counts, which come with the state every 2 s, hence the 5 s; a subscribed node's messages that came short (more than 500 at once) start the count afresh, so it never takes what it didn't see as lost. `messages: dots` after the sliders switches to `messages: flash`: each message crosses its link as fast as it did (half the link's round trip, as the map has it; 50 ms where none is measured), a streak widening to its head, which then draws back into where it went as it fades (0.7 s); the `speed` slider is greyed then. Another click, `messages: off`, hides them (the legend and the sliders greyed), and the next shows dots again; 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). The map's controls are at its top right, over the map as the legend is (below). 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`, 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 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. The `pull` slider at the end of the links' legend sets how hard the links pull their ends together against the nodes pushing each other apart, from a third to three times as hard (the middle, at first), in either layout: to the right harder, the links shorter (the length the map's size and its number of members give them, divided by how hard); the map moves to the new balance smoothly. It is kept in the browser. `tree: off`, next, switched to `tree: on`, shows a writer's tree ([`peers.md`](peers.md)): of the node in focus, else of this node. A chevron on the line from each member's parent to it, at its middle and in the colour of the line in use, points the way the writer's changes go; a member without one gets them by announcement. The node computes the tree as every node does its own part of it: from the member list, and whom it and its neighbours reach and their round trips, as they told it; other members count as reaching their neighbours by the member list. Asked for when the writer in focus changes and every 10 s (`GET /admin/dashboard/tree`). 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. * **Double-click a node**: subscribe to its links, or no longer. The page then reads the node's own links from it, live, as it measures them (its `/admin/dashboard/links/own/events`, at the dashboard it publishes, `--dashboard-url`), with a token from this node that lets it read the member list only ([`access.md`](access.md), Tokens); its links are drawn as it reports them, over what this node knows of them. The browser must reach that dashboard: not one behind NAT, on Tor alone, or plain `http` from an `https` page; the node allows this page's origin as a member's dashboard (`--cors-origins` for others). Subscriptions are kept in the browser, and handed over with the rest; going to another node's dashboard, the node left is subscribed to there (this node itself never is: its links and messages are the page's own). * **Right-click a node**: its menu: subscribing to its links (or not), and `go to its dashboard`, greyed where the node publishes none, and on this node's own. * **Go to a node's dashboard** (its menu): in this page, where the node publishes one (`--dashboard-url`, in the member list; e.g. behind a reverse proxy, [`operations.md`](operations.md)). A token from this node goes along, reading the member list only, for five minutes, enough for the map: where the browser has no session at the other dashboard, its login page begins one with it (log in there to see more), unless its proxy asks for credentials of its own. Whoever publishes the dashboard (a member writes its own URL) gets that token, no more. It goes in the address's fragment, which the other page takes out at once; where that page never loads (an error, a proxy's own login), it stays in the address bar and the history, for its five minutes. 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, 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: on a dynamic map it moves to its place among the others; on a static one it stays where it is. The map is laid out when the page opens. Resized (the window, or the boxes), it stays as it is, scaled about its middle as the box's area is (resized back, it's as before). 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. A node the page subscribes to: its own figures first, as this node's are (its `/admin/stats`, read from it every 2 s while in focus, with the page's token), then this node's entry for it (`as this node sees it`) and its links, each a branch. 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 or a node the page subscribes to is an end, the link as that end has it in its `/peers` (a subscribed node's read from it, as its figures): 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; those that aren't its neighbours muted (it only answers them: no HELLOs, keepalives or syncing of its own; their tooltip says so), 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); `refused`, above all, where this node refuses it as a whole, its own change having broken the swarm's rules (the change and why in the tooltip; [`access.md`](access.md)). | | seen | How long ago this node last heard from it. | | via | The address this node sends to it at, and whether `direct` or `relayed`: as much as the box's width leaves, cut short with an ellipsis; all of it in the tooltip. | | rtt | The round trip on that path. | | hop | How long its sync and blob messages take to come, by its clock and this node's, its clock offset taken off (`hopDelay` in [`measurements.md`](measurements.md)): this minute's mean, then the last 15 minutes' means as bars, oldest first (a minute without messages blank). Good to half the round trip, but the trend exactly: bars growing mean the link, or a queue on it, gets slower. All figures in the tooltip. | | 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). | | reaches | How many members besides this node it reaches, as it said ([`peers.md`](peers.md)); `(+n)`: of those, how many this node doesn't reach itself, which it is a way to. By name in the tooltip, those marked `+`. Blank until it said so in this session. | | epoch | The epoch it last said it is on, by its generation ([`remakes.md`](remakes.md)); orange where it isn't this node's (they don't sync then); its ID in the tooltip; 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, its keys at the top level and how many beside the title: this node's `/admin/stats`; a subscribed node's own (`Node:`), with this node's entry for it and its links after; or a peer's `/peers` entry and, after it, the links it reports (`links`), where the page has them. Branches stay as opened across updates, and ages count on between them. What each figure means: [`measurements.md`](measurements.md). Some values get a reading after them, muted, in parentheses: a node ID its alias (where it has one; a key that is a node ID too), a time in Unix milliseconds in RFC 3339 (a key ending in `At`, `Until` or `Since`, or `at`, `until`, `since`, `alive`), bytes in KiB, MiB, GiB (a key ending in `Bytes`, or `bytes`; from 1 KiB), and bytes per second likewise (`…BytesPerSec`, `bytesPerSec`). ## Document The swarm's document, as this node holds it: its keys listed at the top level, how many beside the title. 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: Right-click an entry (or hold a finger on it) for its menu; the title's menu is the root's: * **`Edit value`**: 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. * **`Rename key`** (a map's key): the value is read, written under the new key, and the old key deleted: two writes, the value as JSON, as the store reads it; a key that exists already isn't overwritten. * **`Add a key`** (a map; the title's: a key at the top level): a key and its value. * **`Append an item`**, **`Insert an item at…`** (a list): an item at the end, or at the index asked for (`-1`: at the end); the items from there on move up. Each user sees and edits what its capabilities let it ([`access.md`](access.md)): of a part it may only partly read, the keys on the way to what it may. Protocol data (`_continuum`) is written only by users given it (in a swarm without a user registry, those 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. In the protocol data, values and keys get the same readings as in the figures: the aliases of node IDs (a member's entry, a vote, a coordinator), times and sizes. **`remake…`** at the right of the title proposes a remake ([`remakes.md`](remakes.md)), this node coordinating it: `compact` (the same content, without its history), `replace` (the new store root as JSON, typed or from a file, which fills the text to look at first) or `import` (an Automerge document, a backup, uploaded as it is); for the last two, rules for the writes made meanwhile (a path prefix moved to another, or dropped; what no rule covers is quarantined on the node that made it), and for any, how long votes are collected and how long nodes wait after a successful vote before switching (the switch delay, `switchDelay`; below a keepalive interval only with "even below a keepalive interval" ticked, the API's `?force=switchDelay`: slow nodes may then miss the commit before the others switch, and rejoin), and the replay window and the archive retention (`replayWindow`, `archiveRetention`: epochs, an age, or both; both empty, the defaults). The answer shows below: the remake's ID, or why it was refused (and the API's override, if any). The map shows the votes as they come. ## 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`, `PING`, …, in its kind's colour, as on the map), the size, and the transport it went by (`mycelium`, `libp2p`, …, in its colour on the map; the address in its tooltip and in the figures box). 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 as they come (a tenth of a second after the first of a moment's), and are listed in the order they went, as the map runs them: each by when its node logged it, on this node's clock (a subscribed node's taken back by how far its clock is ahead, as the map chains the clocks; its own clock's time in the tooltip of the time), so one a slower stream brought late goes in among those of its moment, not at the bottom. 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 subscribed node's messages with its peers are listed too, as they come from it: named by both ends, the receiver first (`b ← a`), each once, as on the map (those with this node only from this node, between two subscribed nodes from the sender); its tooltip names the node that logged it. Shown in the figures box, it says when, both ends, its type, size and address, and the message written out, read from the node that logged it as it is shown (`/admin/dashboard/messages/own/{seq}` there, with the page's token), while that node still keeps it (its latest 1000). 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 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. Nor do tooltips change while open (on the map, in the peers table, the figures' title): an update that changes one waits until the pointer leaves it, so it doesn't close and open again. ## 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**: the page has none of its links: subscribe to it (double-click). If the ring stays grey, the browser doesn't reach its dashboard (why, in its tooltip). A node that just joined through this one shows at once, hollow; through others, once its registration reaches this node (the graph is computed again 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**: the page has one end's links only (subscribe to the other to see its side). * **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, every 30 s. The measured one is this node's links, in the state, and those of the nodes the page subscribes to, which it reads from each node itself: `GET /admin/dashboard/links/own/events` there, a `links` event with its links at once and again whenever they change (a peer, a transport, relayed, dormant, or a round trip moved by a fifth; it looks every 2 s) or it enters a new epoch, with the epoch it is in. The page asks with a token from this node that reads the member list only (`Authorization: Bearer`, [`access.md`](access.md), Tokens), refreshed half way through its life; the other node allows the page's origin by CORS where it is a member's dashboard (`--cors-origins` for others). A stream that fails is opened again, waiting longer each time (up to a minute). 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`). **Clocks.** The clocks in the links on the map (this node's and the subscribed nodes') chain every member's clock to this node's, along the narrowest bounds, and the page 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 and in the console, come from a log the node keeps only while someone looks (`traffic::MessageLog`, the latest 1000), sent as a `messages` event as they come: a tenth of a second after the first of those since the last, the latest 500 of them, with when the node sent the event (`{"sent": …, "messages": […]}`). The per-peer message counts in the state (`/peers`, `traffic`) give the rates and what was delivered. **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.