aboutsummaryrefslogtreecommitdiffstats
Commit message (Collapse)AuthorAgeFilesLines
* docs: how long a broken path takes to see, with PINGs taking turnstv41 hours1-1/+5
| | | | | | | Review 4, 1258's note: the glossary says that a path whose way out broke while the peer's PINGs still come is found by this node's own unanswered PINGs, which with the turns come every one to two intervals, up to twice as long as before, and what still finds it at once.
* docs: what the request log carriestv41 hours1-1/+4
| | | | | | | Review 4, 1257's note: measurements.md now says that no endpoint takes a secret in its query, but every path read or written is in the request log, and paths are data here, so the lines go wherever the node's log goes; and that a user who may set the node's log filter may turn them on.
* remake: a backup's epoch as its snapshot names ittv41 hours1-16/+27
| | | | | | | | | | | | Review 4, appendix: a document loaded from a backup was taken to be on the largest generation `_continuum/epochs` held as it stood, verified at its only root. In a swarm without a policy a member's own change could plant `_continuum/epochs/99`, and a backup then failed to load until it was removed. The generation is now read at the document's only change without dependencies (its snapshot writes its own entry and carries the earlier ones), as the rest of the protocol reads epoch metadata; the genesis change writes none, so a first-epoch backup is genesis whatever was planted. The test plants one.
* access: a key's comment 256 bytes at mosttv41 hours2-3/+18
| | | | | | | Review 4, 1250's note: a key's comment was kept as given, unbounded but by the request limit (2 MiB), in protocol data every node holds and the dashboard shows. It says whose and where the key is: at most 256 bytes now (`sshsig::MAX_COMMENT`), a longer one refused with the key.
* docs: how signatures travel, as they do nowtv41 hours1-2/+10
| | | | | | | | | | Review 4, appendix: api.md's Signed changes said a node passes on each node's latest signature (true of deltas, not of messages carrying changes) and that every change arrives vouched for. It now says what a message carries (its changes' signatures, each run's latest past 4096 and for a delta), that a node signs each of its runs' latest unvouched change at start and when its document is swapped (1277), and the two cases that still arrive unvouched (a change vouched for only by a refused one, postponed; more than 4096 runs writing in an epoch).
* signatures: a listed signed path takes `*`, as everything else doestv41 hours1-3/+16
| | | | | | | | | | Review 4, appendix: whether a write is signed because `_continuum/access/signed` lists its path compared the listed paths by prefix (`signatures::related`), so a `*` there was a literal key, while api.md says the list holds paths as capabilities name them, which take `*` since 1259, and signed values already read the same list as patterns (`envelope::reaches_signed`). A listed `metrics/*/alerts` signed nothing. It compares by `access::agree` now: within or above the pattern, `*` for any one key.
* screen: that a policy was held kept across a swap of the documenttv41 hours2-4/+8
| | | | | | | | | | | Review 4, F12: `Screening::of` started screening over when the document was swapped (a drop, a switch, a rejoin), the root pin alone carried on, so whether the document had held a policy (`policed`, 1211) was forgotten there too, not only across a restart as documented: after a drop with the policy removed meanwhile, late changes written under it went in unjudged again. It is carried on now, as the root pin is; what belongs to the old document (changes waiting, refusals, drops held) still starts over. access.md says so.
* api: a stream opened by password or a proxy's header ends with its usertv41 hours3-44/+85
| | | | | | | | | | | | Review 4, F11: only streams answered to a token ended with it (1273); one opened by password (basic authentication) or a proxy's header went on after its user was disabled or removed. `streams_end` now wraps those too, by the user `basic_auth` leaves on the answer: such a stream ends once the document no longer has its user, enabled (`token::user_taken`, the part of a token's check about its user, which `still_taken` uses too), looked at as the stream begins and as protocol data changes. No expiry: a password has none.
* sync: a write between signing and making a message signed tootv41 hours2-9/+25
| | | | | | | | | | | Review 4, F10: a sync message signed the node's latest change under the document's write lock (`sign_sent`), then was made under its read lock: a write committed in between went in the message unsigned, and under a policy requiring signatures waited at the peer until the next signed change came with it, or a resend. Now the message is made under a read lock taken after signing, and if a write came in between (`AppState::sign_due` again under that lock), signed again: a few times at most, past them it goes as before, its signature with the next message.
* api: a stream listens for its token's end from before it is answeredtv41 hours1-17/+33
| | | | | | | | | | | | | Review 4, F9: a stream answered to a token subscribed to the document's changes only when its body was first polled, and looked at the token only after a change came: one revoking it that came between the request's check and the subscription (a node applying what it was behind on) went unseen until the next change of protocol data, or the token's expiry. Now `streams_end` subscribes before the answer is made, and the token is looked at once as the stream begins (`refused_now`, the document and this node's ended sessions), then with every change of protocol data as before. A request that isn't answered with a stream drops its subscription with the answer.
* roots: only the seeds' roots end the wait for themtv41 hours3-6/+28
| | | | | | | | | | | | | | Review 4, R2 partly (F8): a joining node waited for "each of its seeds" to name the swarm's root as a count, of every admitted peer that named one. Node identities are self-made and admission is the swarm key: a member answering a joiner under as many identities as it has seeds, naming its own root within the wait, made the joiner pin it before the real seeds answered, and refuse them after as another history. Now the seeds are the nodes proven at the seeds' addresses (`roots::seed_found`, as `bootstrap::resolve_seeds` finds them), and only their roots count toward the wait and start its clock. Another peer naming a root still disputes it (no root while they differ), so such a member can keep a joiner from joining, as any member can, but not hand it another root. access.md says so.
* access: an ended session recorded until the login's endtv41 hours2-2/+14
| | | | | | | | | | | | | Review 4, F7: `DELETE /auth/session` recorded the session as ended until the `session_exp` of the token it came with. A token handed to another member's dashboard has its `session_exp` cut to its own end (five minutes), and so has the session that dashboard keeps with it: logging out there recorded an end minutes away, the next remake's snapshot left it out, and the original browser's tokens of the same session, refreshable for days, were taken again. A token now keeps the login's end as it logged in (`Claims::login_exp`), which no refresh changes, and an ended session is recorded until then (or the token's `session_exp`, if later, as for a token from before the field).
* remake: earlier epochs' metadata carried as built, not as it standstv41 hours1-5/+23
| | | | | | | | | | | | Review 4, F6: a snapshot judged which earlier epochs' metadata to leave out as the base epoch's snapshot built it (1254), but copied `_continuum/epochs` from the base as it stood. In a swarm without a policy, a member's rewrite of an entry (its window, kind, clock, translation) went into the next epoch, and was "as built" there; an entry it added was carried and never left out, one it removed not carried. Now `_continuum/epochs` is copied from the base epoch's snapshot change too (`as_built`), then pruned; the first epoch's base carries none. The test adds a generation and narrows a window after the snapshot: neither reaches the next one.
* access: a shallow read shows only the way through where the user reads partlytv41 hours2-7/+39
| | | | | | | | | | | | | Review 4, F4: `?shallow=true` kept every entry the user may at least pass through, and a shallow read inlines short scalars' values with their types and sizes. A scalar at a level a `*` passes through (a capability on `data/*/public`, an entry `data/count: 42`) showed its value though no capability covers it; before `*`, only a scalar where a capability expected a map did. Now an entry the user may read only partly is shown only as the way through it is: a map, with no more than its type (not its size, the number of its keys); anything else there (a scalar, a list, which a deep read leaves out too) isn't shown. Entries it may read whole are as they were.
* signatures: up to 4096 in a message, each actor's latest past themtv41 hours3-6/+9
| | | | | | | | | | | | | | | | Review 4, F5: a delta, and a message whose changes held more than 256 signatures (a whole document pulled, of any real history), carried each actor's latest signature, the 256 that became latest last. An actor is a node's run, so past 256 runs writing in an epoch, some actors' changes arrived unvouched at the puller under a policy requiring signatures, waited, and every resend carried the same 256: a joiner never finished. A message now carries up to 4096 signatures (`MAX_PER_MESSAGE`, as many as wait for their changes, `MAX_PENDING`: 384 KiB at most), all of its changes' up to there, each actor's latest past it and for a delta, and a receiver takes as many (wire version 47). An epoch with more than 4096 runs writing would still leave some unvouched in such a transfer, which the constant says; sending each actor of the transfer its own latest would need the actors of a delta's changes, which only rebuilding their metadata gives.
* signatures: the latest unsigned change of each own actor signed, at start ↵tv41 hours3-39/+58
| | | | | | | | | | | | | | | | | | | | | and after a swap Review 4, F2 (the honest case) and claim 2: a node signed at start its own changes at the heads that nothing vouched for (1216), and after a swap only its current actor's latest. An own change left unsigned can sit below the heads: a drop (`drop_taken`) takes away a signed change and keeps an earlier unsigned one, and the node writes on under a fresh actor, building on it; or a node's change is built on by another's concurrently with the signed one the drop took. Nothing signed it again, and under a policy requiring signatures every node pulling it (a joiner) waited for good, and for everything built on it. Now the latest change of each of the node's actors (`RemakeState::own_actors`, the current one among them) that no signature vouches for is signed (`Signatures::sign_own_actors`), at start and whenever the document is swapped (a drop, a switch, a rejoin), the old `sign_own_heads` and `sign_own_latest` going. One signature per actor vouches for all its changes before it. The case of a member whose signed change the rules refuse, vouching once for its earlier ones, is postponed (future-work.md, next).
* transport: a pushed change written out by its size, never its bytestv43 hours1-2/+39
| | | | | | | | | | | | Review 4, F3: the console writes each message out with `{:#?}`, and `SyncBody` derived `Debug`, so `Changes` (changes pushed as they are) printed every byte of every change. Pushes are the small messages, and Automerge deflates no change under 256 bytes: a short value's path and value went into the console in clear, which any user with any capability reads (the page's `messages` events, and since 1266 a subscribed node's `messages/own/{seq}` too), paths it may not read included. `SyncBody` now writes itself out with sizes for the changes, as `SyncPayload` does for an Automerge message; heads and a delta's spec stay as they were.
* access: a token's limit narrows what an endpoint takestv43 hours1-19/+62
| | | | | | | | | | | | | | Review 4, F1: `authorize` decided whether a request's endpoint was allowed by the user's full capabilities, and applied a token's limit only afterwards, to the `Access` it left for handlers. The store and the users endpoints read that; nothing else does. So a token saying less than its user may do (the dashboard's hand-over token, reading the member list only, for five minutes) did whatever the user may among the other endpoints, on every node: for an operator, forgetting members, proposing or aborting remakes, exporting the document, the quarantine, the log filter. The limit is now applied first, and the endpoint decided by what is left (`allowed`, its own function now, with a test: the hand-over token of an operator may look, and nothing else).
* api: a stream opened with a token ends with ittv46 hours7-15/+126
| | | | | | | | | | | | | | | | | | | | A token was checked when a request came, and a stream answered to it (the dashboard's events, a subscription's links and messages, the store's streams) then went on for as long as the client kept it open: past the token's expiry, past its user's tokens being revoked, its user disabled or removed, its session ended. Now a middleware inside `login::bearer` (`login::streams_end`) ends a streaming answer (`text/event-stream`, `application/x-ndjson`) to a request with a token at the token's deadline, its expiry, or as soon as the document no longer takes it (`token::still_taken`, the checks `verify` makes of the document, now its own function): looked at when a change touches protocol data (the root, a switch, among it), and when a session ends on the node itself in a swarm without a registry (`AppState::session_ended`). No polling: a timer for the expiry, and the changes as they come. The client reconnects with the token it has then; a refreshed token is a new one, so a stream lasts a token's lifetime at most (12 hours by default). `futures` is a dependency of every build now (for `take_until`), not only of libp2p's and Tor's.
* access: ending a single sessiontv46 hours10-11/+188
| | | | | | | | | | | | | | | | | | | | Logging out of the dashboard (`DELETE /auth/session`) only cleared the cookie: the session's tokens stayed taken until they expired, by every node. Ending them took `POST /auth/logout`, which ends all of a user's sessions. Now `DELETE /auth/session` ends the session of the token it comes with (the cookie, or a bearer token): where the swarm has a registry, it is recorded as `_continuum/sessions/<user>/<session>` (`registry::session_path`), its value until when the session would have lasted, and every node refuses the session's tokens once the change reaches it (`token::Refused::Ended`): the page's own, its narrow tokens, and one handed to another member's dashboard, all refreshed from it and keeping its ID. The rules take the entry from the user itself or one who manages users, as a logout. A remake's snapshot leaves out entries past that time, a user's map where none is left: their tokens are expired anyway. Without a registry the document can't record it: the node ending it refuses the session's tokens itself (`AppState::ended_sessions`, until it restarts), others take them until they expire, as a logout there is refused.
* dashboard: nothing shown or read after logging out, back button includedtv46 hours2-3/+21
| | | | | | | | | | | | After "log out" the page went to the login page, and the browser's back button brought it back as it was (kept whole in its history): the map, the peers and the console went on updating, by the stream it still had open and the subscriptions' token it still held, though the session had ended (the document box, reading anew, got 401s). Now leaving for the login page (`toLogin`: logging out, or the login ending) stops the page's reading first: its stream, its subscriptions, its timers and tokens. And a page the browser brings back whole (`pageshow`, `persisted`) loads again instead, so it reads with the session it has now, and without one goes to the login page.
* dashboard: going to another node's dashboard without its login page in betweentv46 hours5-24/+42
| | | | | | | | | | | | | | | Going to another member's dashboard showed its login page for a moment: a browser without a session there was sent from `/admin/dashboard` to `/login` (by `auth::basic_auth`), which began a session with the token handed over and went on to the dashboard. Now the dashboard's page is served without authentication, as the login page is (it holds nothing: what it shows it reads with the browser's session). Opened with a token handed over, the page begins the session with it (`POST /auth/session`) before it reads anything; without a session, `obtainTokens` goes to the login page, as the redirect did. A request without credentials is refused at once rather than after the 1.5 s that slow down guessing passwords: nothing is guessed there, and a page without a session goes to the login page without waiting twice for it.
* dashboard: tokens renewed after sleep too, never basic authenticationtv47 hours3-23/+62
| | | | | | | | | | | | | | | | | | | | The page renewed its token half way through its life by a timer. A timer fires late where the computer slept or the tab was in the background, by when the token (and the session cookie, which lives as long) may have ended: the page then fell back to `POST /auth/login`, which takes a password the browser keeps (basic authentication), and without one retried every 30 s, logged out without saying so. Now whether the token is half way through its life is looked at again when the page shows and every 30 s (`refreshIfDue`, one renewal at a time), so it is renewed while it still can be, until the login ends (`--session-lifetime`). Where the login ended, or there is none (400, 401, 403), the page goes to the login page; a node away is tried again in 30 s. And the page no longer gets in by a password the browser keeps: only by its session or through a proxy naming the user (`throughProxy`, `login: "header"`). The node never challenges a browser for a password either (`WWW-Authenticate`, the browser's prompt): not only on the dashboard's paths and for a page's own requests, but for any request with `Sec-Fetch-*` or a `Mozilla/…` user agent. curl sends the password unasked with `-u`, as before.
* dashboard: a subscribed node's own figures when it is in focustv2 days4-13/+90
| | | | | | | | | | | | | | | A node in focus showed this node's `/peers` entry for it and the links it reports: what this node knows of it, never what it says of itself, though the page subscribes to it. Now a subscribed node in focus shows its own figures first (`Node:`), as this node's are (its `/admin/stats`), then this node's entry for it (`as this node sees it`) and its links, each a branch. A link in focus shows each end's paths to the other as that end has them: this node's, and a subscribed node's from its `/peers`. The page reads them from the node every 2 s while it is in focus (`readFigures`, `figuresOf`), with its narrow token, which may look at a node as any user with a capability may; pages of other origins may now call `/admin/stats` and `/peers` (`cors::open`), as they do a node's links and messages. Where they can't be read, the box says why.
* dashboard: a subscribed node's message written out, read from it when showntv2 days7-18/+102
| | | | | | | | | | A subscribed node's messages come without being written out (their stream leaves the text out: up to 8 KiB each, 500 at a time), so the figures box said only when, both ends, type and size of one. Now the node answers for one message by its number, `GET /admin/dashboard/messages/own/{seq}` (`{"text": …}`, `404` once it isn't kept), which pages of other origins may call as they do its streams (`cors::open`), and the page reads it when the message is shown, with its narrow token, and keeps it on the message. Where it can't be read, the box says why.
* dashboard: the transport each message went by, in the consoletv2 days5-36/+90
| | | | | | | | | | | | | | | The console said with whom a message went, not how: the message log was noted above the identity layer (`CountingTransport`), where a message is to or from a node, and the address it went to (the paths layer picks one, `Paths::send_first`) or came from is gone. Only the paths layer's own PINGs and PONGs, logged under their addresses, had one. Now the identity layer notes the node's messages (`MessageLog::set_addressed`), with the address each went to (`IdentityTransport::send_proven` returns it) or came from; `CountingTransport` leaves them to it, and still notes them where there is no identity layer (tests). A console line ends with the transport (the address's scheme, in its colour on the map, the address in the tooltip), and the figures box shows the address. A subscribed node's messages carry theirs too, and the map runs every message along its transport's line, not only the PINGs and PONGs.
* access: what a node holds alone by `_continuum/local/<node ID>/<name>`tv2 days6-33/+88
| | | | | | | | | | | | | | | | Setting a node's log filter took `write` on `_continuum`, all of the protocol data: whoever could change one node's logging could change the swarm's members, remakes and users too. Now what a node holds alone has paths of its own for capabilities, under `_continuum/local/<node ID>/`: `log` (`/admin/log`), `config` (`/admin/config`) and `debug` (`/debug`, always `write`), `read` to look and `write` to act (`api::access::needed`, which takes the node's ID). So `_continuum/local/<node ID>` is everything on one node, `_continuum/local/*/log` the log filter on every node, and holders of `_continuum` keep it all. Nothing is stored there: the store refuses writes under `_continuum/local`, and the rules refuse a change touching it, as for epoch metadata. The quarantine stays with `_continuum`: its entries are everyone's writes, which a node-local capability mustn't show.
* access: `*` for any one key in a capability's pathtv2 days5-36/+73
| | | | | | | | | | | | | | | | | | A capability's path covered by prefix only, so what is the same at every key of a map (each node's log filter, say, at `_continuum/local/<id>/log` from the next patch on) took a capability per key. Now a `*` segment stands for any one key, as it already does in signed paths and sealed places (`envelope`, `sealed`, which now share one `agree`): * covering (`Capability::covers`): the pattern no longer than the path, and agreeing with it segment by segment; * listings (`reaches`): pattern and path agreeing on the segments both have, so every key on the way shows, nothing past where the pattern differs; * narrowing (`Capability::holds`, for a token's limit narrowed again and a refresh's): at or below, a `*` only under a `*`: a limit naming one key never widens to all. A `*` first is outside protocol data, so it never covers `_continuum`, as `[]` doesn't. A key that is `*` itself can't be named alone. Every node judges changes by capabilities, so all must read them alike: they run the same version.
* paths: PINGs of a path take turns with the peer'stv2 days2-5/+85
| | | | | | | | | | | | | | | | | | The node's keepalives take turns between the two sides (`PeerState::keepalive_due_at`), but the paths layer pinged every path in use every `probe_every` (the keepalive interval, 30 s at least) whatever the peer did: a PING from the peer over the path didn't count. So both sides pinged each path every interval, often within a second of each other (seen on the dashboard: a PING answered, and our own PING right after it). Now a PING from the peer over a path is noted (`PathState::peer_probe`), and a path is pinged again (`rested`) an interval after the peer's PING, if that came last; after ours, an interval and a half while the peer pings too (its PING within two intervals before ours), so its turn comes first; else an interval. One exchange per interval goes over a path, as the measurements' "two keepalives per neighbour per interval" says, and each side measures every other interval; a shorter `--keepalive` measures more often. A path whose way out broke while the peer's PINGs still come in is found by our own PINGs half as often; a failed send or unanswered traffic (`suspect`) still pings at once.
* api: a log line per HTTP requesttv2 days4-6/+70
| | | | | | | | | | | | | Requests weren't logged at all, short of what a handler logs itself. Now a middleware, outermost on the API (and on a relay-only node's), logs a line per request as web servers do: method and path (with the query), status, the milliseconds until the answer began (a stream goes on after), where it came from (the address, or `unix`), and the user (`-` where none was authenticated). The user comes back on the response: `basic_auth` puts it there, for a token as for a password or a trusted proxy's header. At debug, under the target `http` (`RUST_LOG=info,http=debug`, or `PUT /admin/log`): an open dashboard alone would fill the log at info. With the target off, the request passes untouched.
* remake: earlier epochs' metadata read as the snapshot built ittv2 days2-9/+55
| | | | | | | | | | | | | | A snapshot leaving out earlier epochs' metadata (`snapshot::left_out`) and a rejoining node (`catchup::prepare`) read it from the document as it stands. It is never writable through the store, and under a policy the rules take a change to it only from an epoch's first; but with no policy, screening judges only an epoch's root, so a member's own change could rewrite or remove an entry: a replay window narrowed or widened, a translation or a clock changed, a strict remake's entry gone. Now both read it at the epoch's snapshot change (`snapshot::as_built`), as `verify_document` reads the epoch's own: a snapshot from its base's epoch's, a rejoining node from the target's. Every earlier entry is a copy in that change, so what was written there since doesn't count. The first epoch follows none and has none.
* dashboard: the replay window and archive retention in the remake dialogtv2 days2-1/+35
| | | | | | | | | The remake dialog set the kind, its input, the settle time and the switch delay, but not the replay window or the archive retention. Now each has two fields, epochs and an age, their defaults as placeholders: both empty sends nothing (the node's defaults), one given sends that limit alone, as the API reads it; in the body, or for an import in the query (`replayWindow`, `archiveRetention`). Epochs that aren't a whole number are refused in the page.
* remake: replayWindow and archiveRetention in an octet-stream import's querytv2 days3-5/+64
| | | | | | | An import uploaded as `application/octet-stream` takes `rules`, `baseHeads`, `settle` and `switchDelay` in the query, but not the retentions: for those the document had to go base64-encoded in the JSON form. Now `replayWindow` and `archiveRetention` go in the query too, as JSON like the rules (`{"epochs":1,"age":"7d"}`, as in the body).
* remake: leave out earlier epochs' metadata past the replay windowtv2 days3-6/+124
| | | | | | | | | | | | | | | | | | | | | Every snapshot carried the metadata of all earlier epochs (`_continuum/epochs`) forward, so it grew by an entry, a clock per actor among it, with every remake. Only a rejoining node reads an earlier entry: a node on epoch P reads P+1's (its clock, when it was proposed, its replay window) and every one up to the swarm's epoch (kind, translation, whether it was strict). It replays only while P+1's replay window covers it; beyond, its writes are quarantined whatever the entries say. So a snapshot now leaves out the generations older than the oldest whose replay window still covers a node on the epoch before it (`snapshot::left_out`), judged by the remake's `proposedAt`: the same on every node, and earlier than any rejoining node's clock, so nothing a node can still replay through goes. Metadata that doesn't read stays, with all after it. A strict remake's metadata stays for good: by it a rejoining node, however far behind, tells a strict remake of a swarm strict already (`catchup::prepare`, which reads the generations it finds). There is at most one, short of such a remake. No new key and no change to the rejoin: a node beyond the window that finds some entries and not others quarantines with "cannot translate" rather than "outside the replay window".
* access: keep an SSH key's commenttv2 days4-6/+25
| | | | | | | | A key's line was stored as the key alone (`ssh-ed25519 <base64>`), whatever comment it came with, both from `PUT /auth/keys/<name>` and from `--bootstrap-keys`. Now its comment is kept after it (`PublicKey::kept_line`), the spaces between its words made one. The comment checks nothing: every use of a key parses the line, and the parse reads the key alone.
* dashboard: no subscription to this node itselftv2 days1-1/+8
| | | | | | | | | | | | A page opened from another node's page takes that page's subscriptions along; one of them can be this node, another there. The page then followed this node as a subscribed one too, and showed its messages twice, from its own log and from the subscription (`→ LLL PING` and `LLL ← RRR PING`, the same message), on the map as in the console. The page follows no subscription of its own node: not when it knows which node it is, and one begun before that stops once it does; its messages, if any came, are left out.
* dashboard: keys edited, the last removed with a password, failures kepttv2 days8-93/+231
| | | | | | | | | | | | | | | | | | | | | The credentials dialog listed a user's keys whole, to add one or remove one but the last, and any failure replaced the dialog with the error, the form lost. * A key is shown short: its type, the first and last four characters of what tells it apart (an Ed25519 key's base64 after the part all of them share), its comment; the whole line in its tooltip. * A key can be edited, its name and its line: saved under the name, a new name written first and the old one removed. * A user with a password on this node (`--htpasswd`) may remove its last key: it logs in by the password. GET /auth/keys says so (`password`), and DELETE /auth/keys/{id} takes it then; the rules no longer keep the last key on every node (no other node can tell who has a password where; under a strict policy the signed entry keeps one still). * What fails says why beside where it was asked (the add form, the edited row, the removed one), which stays as it was, to be set right and tried again.
* dashboard: a node's menu on a right-click, and going to its dashboardtv2 days2-7/+17
| | | | | | | | | | A right press on a node started dragging it, as any press did: the map took hold of the pointer, so the right-click went to the map and the node's menu didn't open, and letting go put the node in focus. The right button now opens the node's menu alone. Its entry for the node's dashboard is `go to its dashboard`, greyed where the node publishes none (and on this node's own), its tooltip saying where it goes or why not.
* dashboard: move pull slider to link legendtv2 days2-35/+51
|
* dashboard: an entry's menu left of the fingertv2 days1-4/+12
| | | | | | | Opened by a finger held on an entry, its menu had its top left corner at the finger, under the hand holding it, a right hand mostly. By touch it now has its top right corner there; opened by a pointer, its top left, as before.
* dashboard: the document box edited from its menus alonetv2 days2-59/+32
| | | | | | | | With the entries' menus (1238), editing had three ways in: the double-click, the `+` on a map's or list's row, and the menu. The double-click and the `+` go; the menu does all of it. The title, which stands for the root (its row isn't shown), now takes the right-click too: its menu adds a key at the top level.
* dashboard: an entry's menu in the document boxtv2 days2-25/+197
| | | | | | | | | | | | | | The document box edited an entry by a double-click and added to a map or list by the `+` on its row; a key couldn't be renamed, and a list's `+` always asked for an index. A right-click on an entry, or a finger held on it half a second, opens its menu: `Edit value` (as the double-click), `Rename key` for a map's key, and on a map `Add a key`, on a list `Append an item` or `Insert an item at…`. Renaming reads the value, writes it under the new key and deletes the old one (two writes, the value as JSON; a key that exists already isn't overwritten). Inside a field being edited, and where an entry offers nothing, the browser's own menu shows.
* dashboard: where a stream's lag comes fromtv2 days3-29/+70
| | | | | | | | | | | | | A stream's lag in the tooltip of `behind: …` was one figure, from when a node logged a message to when the page had it: a second and a half for a node a LAN away said nothing of where it went. The node now stamps each `messages` event with when it sent it (`{"sent": …, "messages": […]}`), and the tooltip splits each stream's lag: how long its batches waited in the node, from logging the first message to sending the batch (the node's own clock: exact), and how long they took on the way, from sending to arriving. This node's own stream is always listed, beside the three slowest.
* dashboard: the console in the order messages wenttv2 days2-7/+27
| | | | | | | | | | | | The console appended messages as their streams brought them, a subscribed node's later than this node's by its path, each line with its node's own clock's time: lines of one moment came apart, and their times read out of order. Each message is now listed by when its node logged it, on this node's clock, as the map runs them (`aheadOf`): one a slower stream brought late goes in among those of its moment. The time shown is on this node's clock; a subscribed node's own clock's time is in its tooltip.
* dashboard: messages on one timeline, when they wenttv2 days2-33/+137
| | | | | | | | | | | | | | | | | Each node's message stream comes its own way to the page, a subscribed node's over the network to it: messages logged 16 ms apart on three nodes ran over a second apart, as their streams arrived. Messages now run when they went, on this node's clock: each at the time its node logged it, taken back by how far that node's clock is ahead of this node's (the map's clock chain, `aheadOf`), a while behind real time (`replayDelay`): the most any stream lagged in the last 30 s and a tenth of a second more, shown after the sliders (`behind: …`), each stream's lag behind the fastest in its tooltip. The browser's clock is against all streams alike and drops out. A message known only from its receiver (its sender not subscribed) is placed at its arrival, starting a crossing before. Messages lagging more than 10 s (a gap) don't run, nor hold the map behind.
* dashboard: messages as they cometv2 days4-127/+167
| | | | | | | | | | | | | | | | | | The node sent its messages to the page every 2 s (`messages` events, this node's and a subscribed node's), and this node's ran on the map from its counts, which come with the state: the map showed a message 2 to 4 s after it went, the messages of each 2 s spread evenly over the next. The message log now tells those waiting of each message logged (MessageLog::logged_after), and both streams send what was logged a tenth of a second after the first of it (MESSAGES_GATHERED): a busy node about ten events a second, a quiet one each message soon after it went. The page runs messages as their streams bring them, this node's from its own log too (with the addresses its PINGs went to), as far apart as they were sent; more than six on a link and way at once are shared out by kind over the moment, and after a gap only the last 2 s run. The counts still give the rates and what was delivered.
* dashboard: messages run as they left; a link breathes red for those losttv2 days2-14/+137
| | | | | | | | | | | | | | | | | | A message between this node and a subscribed one ran from this node's counts both ways: one the subscribed node sent ran once this node had counted it received, as it arrived. Each message now runs from its sender where the page reads the sender (this node's counts, a subscribed node's messages), so it runs the way it went, also where it never arrived; only one from a node the page doesn't read runs as it arrived. The console lists them as before. 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 (`breatheLink`), its tooltip saying how many. Kinds the paths and identity layers count under addresses are left out, as this node's counts leave them out; a subscribed node's tick that came short (the latest 500 of more) starts the count afresh, so what the page didn't see isn't taken as lost.
* dashboard: messages follow the line they went overtv2 days2-28/+72
| | | | | | | | | | | | Dots and flashes ran straight from sender to receiver, whatever line the message took: a PING over another transport, drawn as a curved line beside the one in use, crossed the map off its line. Each message now runs along the line it went over, as drawn (`route`: the link's quadratic curve, its control point where renderGraph put it): a message naming the address it went to (the paths layer's PINGs and PONGs) over that address's transport's line, any other over the line in use; a flash's streak bends with it.
* dashboard: how hard links pull, on a dynamic maptv2 days2-4/+32
| | | | | | | | On a dynamic map, links pull toward their lengths against the nodes pushing each other apart, at one strength. A `pull` slider before the layout's toggle, shown while dynamic, sets it from a quarter to four times as hard (2 to its value, once at first), kept in the browser; the map moves to the new balance smoothly.
* dashboard: no dotted links for an end the page doesn't readtv2 days2-13/+9
| | | | | | | | | | A link was dotted where an end reported no links. Members used to report their links to every dashboard (LINKS); the page now reads a member's links only while subscribed to it, so a dotted link said no more than that its other end isn't subscribed, which the node shows already (hollow), and most links were dotted. The style and its legend entry go; the link's tooltip says when one end alone reports it.