Skip to content

connections: live refresh driven by an address-book change counter - #270

Open
jseifeddine wants to merge 1 commit into
sol1:mainfrom
jseifeddine:feat/addressbook-version-live-refresh
Open

jseifeddine wants to merge 1 commit into
sol1:mainfrom
jseifeddine:feat/addressbook-version-live-refresh

Conversation

@jseifeddine

@jseifeddine jseifeddine commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Based on current main on its own: one commit, nothing from any other open PR. Nothing from #268 or #269 and no need of them.

Closes #253 (part 3/3).

What it does

The Connections page keeps itself current without reloading. rustguac keeps an in-memory change log of the address book: every successful write through its API (entries, folders, folder config, moves, copies, bulk) is logged against the folders it touched, under a version counter. The page polls GET /api/addressbook/version?since=N every 3 seconds and gets back only what changed after the version it last saw: one row per folder, saying whether the folder's entries or the folder itself (created, deleted, config) moved.

The page then acts only on what it shows:

change what the tab reads
entries in the folder it has open that folder's entries, once
entries in any other folder nothing: the cached copy is forgotten until the folder is opened
a folder created, deleted or its config changed the folder list (no entries), plus the open folder if it was in that subtree
search index nothing: it is marked stale and rebuilt the next time the search box is focused or typed in; while results are on screen, only the changed folder's rows are re-read and patched in

Worst-case Vault reads per address-book change, per open tab: one folder's entries (the open folder) plus one folder-list listing (when a folder came or went). Never the whole book, never the search walk. A poll reads nothing from Vault.

changes: null (first poll, server restart, or a tab that fell further behind than the log's 512 entries reach) makes the tab refresh what it shows once: the folder list and the open folder, same bound. Hidden tabs do not poll and catch up with an immediate check when shown; a tab with a dialog open does not poll either, so the change waits for the dialog instead of being missed. There is no timed refresh of any kind.

A user's own credential variables (PUT /api/me/credentials) are not address-book changes and never reach the log. The tab that saved them already reloads its own credential status, which is the only tab that needs to know.

Attribution is by request path for the routes that name their folder; the four whose folders only the body knows (create folder, move, copy, bulk) report them from the handler through a response extension, and a handler that reports nothing is logged as all so the page refreshes rather than misses it. The log is per process, as before.

Review changes

Dave's review, point by point:

  • The unconditional 2-minute full refresh: gone. Nothing reloads on a timer; only a change the server logged, or a null answer, moves anything.
  • Search index rebuilt on every change: no longer. A change marks the index stale (searchIndexStale) and it is rebuilt only when search is next used (focus or typing in the box). While results are showing, the changed folder's entries are re-read and its rows patched into the index, bounded to the changed folders. A folder re-read for the view patches the index for free.
  • Every change reloads everything including every entry from Vault: no longer. The change signal names folders, and a tab re-reads only the folder it has open, or the folder list when a folder came or went. GET /api/addressbook (which reads every top-level folder's entries) is not called by the live refresh at all.
  • /api/me/credentials bumping the global counter: removed. It is not an address-book change; ab_changes_for_request returns None for it, and the middleware test proves a successful PUT /api/me/credentials leaves the log untouched. The saving tab reloads its own status locally, as it already did.
  • Stacked on connections: ?move=true moves the matched NetBox entry with its credentials #268/connections: folder default credentials for entries without their own #269: rebased onto main as its own change.

Also: the version endpoint is answered from memory (AddressBookChanges::since); the handler touches neither Vault nor disk. The request still passes through the auth layer like every API call (a session-cookie lookup in SQLite), which this PR does not change.

Tests

Rust (src/api.rs):

  • address_book_change_attribution: which method and path mean which change, folder segments decoded (a%2Fb), connects and reads are not changes, /api/me/credentials is not a change.
  • address_book_change_log_since: the log answers exactly what came after the version the page saw, collapses repeats, all swallows the rest, and a page ahead of the log (restart) is told to refresh.
  • address_book_change_log_forgets_the_oldest: a range the ring no longer holds in full answers null rather than a partial list.
  • address_book_change_track_middleware: on a real router, a credentials save logs nothing, an entry write is logged against its decoded folder, a failed write logs nothing, a handler-reported folder is logged precisely, and an unreported body-dependent route is logged as all.

Plain node (tests/connections-live.mjs, picked up by the CI client-tests loop): pins what the page re-reads for each kind of change, in particular that a change in a folder you are not looking at reads nothing, that no change rebuilds the index, and that the folder list is re-read only when a folder came or went.

The browser spec from the earlier version of this PR relied on the Playwright harness from #266, which is not on main; it is not included.

@davekempe

Copy link
Copy Markdown
Member

My main concern here is load. Every open Connections tab polls every 3 seconds, and on any change, plus every 2 minutes regardless, reloads everything including the search index, which reads every entry from Vault. With NetBox webhooks firing and a few dozen tabs open, that's a lot of Vault reads on a big address book.

Could the 2 minute refresh go, and the search index refresh only when search is actually used, or per changed folder? Also, /api/me/credentials changes bump the global counter, so one user saving their own credentials reloads everyone's tab.

Also, since the PRs are stacked, could you rebase this onto main on its own so it can be taken separately?

@jseifeddine

Copy link
Copy Markdown
Contributor Author

Rebased onto main as its own change, and reworked for load. The 2-minute refresh is gone; nothing reloads on a timer any more. The version endpoint now answers from memory with the folders that changed since the version a tab last saw, and a tab only re-reads the folder it has open, or the folder list (no entries) when a folder came or went; a change in a folder you aren't looking at reads nothing. The search index is never rebuilt on a poll: it's marked stale and rebuilt only when the search box is next used, and while results are showing just the changed folder's rows are patched. /api/me/credentials no longer touches the counter at all; the saving tab refreshes its own status. Worst case per change per tab is one folder's entries plus one folder listing, however many tabs are open, and a poll costs no Vault read.

The CI audit failure on this branch is the new quinn-proto advisories, not this change; #283 bumps the lockfile and the run goes green once that is in.

@jseifeddine
jseifeddine marked this pull request as ready for review October 13, 2026 03:16
connections: live refresh from a per-folder address-book change counter

The Connections page keeps itself current without reloading. rustguac
keeps an in-memory change log of the address book: every successful
write through its API (entries, folders, folder config, moves, copies,
bulk) is logged against the folders it touched under a version counter.
The page polls GET /api/addressbook/version?since=N every 3 seconds
while visible and gets back only what changed after the version it last
saw: one row per folder, saying whether its entries or the folder itself
moved. The poll is answered from memory; it reads nothing from Vault.

A tab acts only on what it shows. Entries changed in the open folder:
that folder is re-read. Entries changed elsewhere: the cached copy is
forgotten and nothing is read until the folder is opened. A folder
created, deleted or reconfigured: the folder list is re-read (no
entries), plus the open folder if it was in that subtree. The search
index is never rebuilt on a poll: it is marked stale and rebuilt the
next time the box is focused or typed in; while results are showing,
only the changed folder's rows are re-read and patched in. So one
change costs a tab at most one folder's entries plus one folder-list
listing, never the whole book, however many tabs are open. There is no
timed refresh. A null answer (first poll, restart, or a tab further
behind than the 512-entry log reaches) refreshes what is shown once,
under the same bound.

Folders are attributed from the request path; the four routes whose
folders only the body knows (create folder, move, copy, bulk) report
them from the handler through a response extension, and a handler that
reports nothing is logged as `all` so a page refreshes rather than
misses it. A user's own credential variables (/api/me/credentials) are
not address-book changes and never reach the log: the saving tab
reloads its own status, and no other tab needs to know.

Hidden tabs do not poll and catch up with an immediate check when
shown; a tab with a dialog open does not poll either, so the change
waits for the dialog instead of being missed.

Tests: the attribution table, the log's since/dedup/eviction/restart
answers, the middleware on a router (a credentials save logs nothing, a
failed write logs nothing, reported folders are logged precisely), and
a plain-node test pinning what the page re-reads for each change.

Closes sol1#253 (part 3/3).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Connections: stale entries after a NetBox tenant change, minutes without credentials, and no live refresh

2 participants