Skip to content

Give an owner a page to move or remove their polled radar - #624

Merged
Babissimo merged 2 commits into
mainfrom
polled-radar-owner-page
Sep 25, 2026
Merged

Babissimo merged 2 commits into
mainfrom
polled-radar-owner-page

Conversation

@Babissimo

Copy link
Copy Markdown
Contributor

PR 3 of 6 for subtask 8, Owner and admin views for polled nodes: the owner's side in the console. Dashboard only; the endpoints it calls came in #616 and #619.

What the owner gets

  • /radars/:ref, a page per polled radar. It shows:

    • the address the server polls;
    • liveness in words: waiting for its first answer, streaming, answering with no new frames, or not answering;
    • whether a password protects it;
    • whether an administrator has reviewed it.

    The page refreshes every 10 s. From it the owner can:

    • Change the address. This is registration's check-and-confirm flow, addressed by node_id. It is offered only where the server takes registrations, since the endpoints answer 404 elsewhere. The page warns before the move that another host or port puts the radar back on probation.
    • Remove the radar. The page asks first, then goes back to My nodes.
  • My nodes links every node by its ref. A polled radar goes to its radar page and any other node to its detail page. Until now the list linked nowhere, so a newly registered radar could not be reached.

  • The detail page's ownership card sends a polled radar's owner to the radar page instead of offering Release. Release's wording ("whoever claims it next") is untrue of a radar that removal retires.

Why a page of its own

The node detail page reads per-node analytics and says "Node not found" until a node's first frame. A radar at a wrong address never sends one, and that is the radar whose owner most needs to change its address.

Commits

  1. Share the check-and-confirm steps. useRadarCheck and components/PolledRadar.tsx come out of the registration page unchanged in behaviour, and its 10 tests pass untouched.
  2. The radar page. It also adds the route, the two client calls, the list links and the detail card.

Testing

  • vitest: new tests cover each flow:

    • the page shows the radar, and each liveness state in words;
    • a move sends the confirmed fingerprint and refreshes the page;
    • config_changed shows the new declaration beside the pins;
    • endpoint_registered shows the refusal beside the button;
    • registration switched off;
    • removal, and a failed removal;
    • not found;
    • the list links;
    • the detail card.

    26 of 26 mutations of the new behaviour are caught. tsc is clean, and eslint adds no new warnings.

  • In a browser, against the console stub (Vite plus a stub backend):

    • the list links went to the right pages;
    • a move went through check, confirm and refresh, and the new address came back on probation;
    • liveness turned to "Not answering" on the next refresh;
    • remove asked first, then returned to a list without the radar;
    • the detail card showed the link and no Release.

    There were no console errors.

Not tested

  • A real move. It needs a stock blah2 radar at a publicly routable address, since the probe refuses private and loopback addresses. Staging and prod keep registration off, so after deploy this is verified by finding the new code in the page prod serves.

🤖 Generated with Claude Code

Moving an owner's radar to a new address is the same two steps as
registering one: probe the address, show what the radar declares there,
then send the fingerprint of what the owner saw, handling a
config_changed refusal by showing the new declaration to confirm. The
registration page held that state machine and its cards inline.

useRadarCheck now holds the state and the refusal handling, and
components/PolledRadar.tsx the address card, the declaration card and
the unprotected notice, so the owner's radar page can use them without
a second copy. The registration page behaves as before; its tests pass
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@claude

This comment has been minimized.

The backend has let an owner move a polled radar to a new address and
remove it since #616, and shows them its address and liveness since
#619, but nothing in the console reached any of it. Registration stays
switched off until the owner can see, move and remove what they
registered.

/radars/:ref is that page: the address the server polls, liveness in
words (waiting for its first answer, streaming, answering with no new
frames, not answering), whether a password protects it, and whether an
administrator has reviewed it, refreshed every 10 s. Changing the
address is registration's check-and-confirm, addressed by node_id, and
is offered only where the server takes registrations, since it answers
404 elsewhere. Removing asks first, then returns to My nodes.

It is its own page rather than a card on the node detail page because
that page reads per-node analytics and says "Node not found" until a
node's first frame, which a radar at a wrong address never sends. The
detail page's ownership card now sends a polled radar's owner there
instead of offering Release, whose wording (whoever claims it next) is
untrue of a radar that removal retires.

My nodes links every node by its ref, a polled radar to its radar page
and any other node to its detail page; until now the list linked
nowhere, so a newly registered radar could not be reached.

Ref: 123zgec4bb2

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Babissimo
Babissimo force-pushed the polled-radar-owner-page branch from d2c1de5 to 483a8f0 Compare September 25, 2026 11:55
@claude

claude Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Claude finished @Babissimo's task in 1m 39s —— View job


Review: PR #624 — Give an owner a page to move or remove their polled radar

  • Gather context (PR description, CLAUDE.md, changed files)
  • Read the diff (HEAD~2..HEAD, matches the stated 896/−160 across 13 files)
  • Review new files: RadarPage.tsx, useRadarCheck.ts, PolledRadar.tsx
  • Review modified files: NodeDetailPage.tsx, RegisterRadarPage.tsx, OnboardingPage.tsx, client.ts, types.ts, App.tsx, DashboardLayout.tsx
  • Review tests
  • Cross-check client calls against the backend routes/response shapes
  • Post review feedback

Note on tooling: This sandbox has no network access (dashboard/node_modules isn't installed and can't be fetched), so I couldn't run vitest, tsc, or eslint myself. I read the full diff by hand instead and cross-checked it against backend/routes/polled_radar_registration.py, backend/routes/auth.py, and backend/services/polled_radars.py, all already in the tree.

Findings

No correctness, security, or contract issues found. This is a clean extraction + addition, and matches the PR description closely:

  • Contract match, field-for-field:
    • client.ts's probePolledRadarAddress/movePolledRadar hit POST .../{node_id}/probe and PUT .../{node_id}/address (backend/routes/polled_radar_registration.py:101,114), with the {node_id, epoch, trust_state} response shape matching move_polled_radar's return exactly.
    • types.ts's OwnedNode/OwnedPolledRadar line up field-for-field with my_nodes()'s response (backend/routes/auth.py:398-443) and owner_views() (backend/services/polled_radars.py:348-365) — same four keys (address, unprotected, liveness, trust_state), no drift.
    • Removal: RadarPage.tsx's remove() calls the existing api.releaseNode, and auth.py:305-306 already special-cases system_of(node_id) == POLLED_BLAH2 to _remove_polled_radar — so "Remove" correctly retires the radar instead of freeing it for another claimant.
  • useRadarCheck/PolledRadar.tsx extraction: a faithful lift of RegisterRadarPage's check/confirm state machine (at: "check" | "confirm" generalized from the old "check" | "register"). The RegisterRadarPage.tsx diff shows behavior preserved — config_changed still routes to the check step, everything else to confirm.
  • Routing/titles: /radars/:nodeRef is added after the literal /radars/new in both App.tsx's <Routes> and DashboardLayout.tsx's pageTitles. Since pageTitles is walked with Object.entries().find() in insertion order, /radars/new still matches its own literal entry before the pattern is tried — no title regression for "Add a Radar".
  • NodeDetailPage.tsx: nodeRef (data.node_ref || nodeId, line 132) is defined before the ownership card that uses it (~line 235); the ownership?.polled / ownership && !ownership.polled split makes the two ownership cards mutually exclusive, so a polled radar's owner never sees the stale "Release" card.
  • useRadarCheck + nodeId closure: RadarPage's move = useRadarCheck((address) => api.probePolledRadarAddress(nodeId, address)) is re-created each render with the latest nodeId, and the address/move UI only renders once polled.data (and therefore nodeId) is loaded — no stale-empty-nodeId window.
  • OnboardingPage.tsx: OwnedNode now comes from types.ts instead of a locally duplicated type, and the ref link branches correctly on n.polled between /radars/:ref and /nodes/:ref.
  • Tests: RadarPage.test.tsx covers all four liveness words plus an unmapped value, the two-step move (config_changed and endpoint_registered refusals both exercised), registration-closed, both "not found" paths (wrong kind of node vs. not owned), and removal (confirm/cancel/error/clear-error) — genuinely thorough, matching the PR description's list rather than padding line coverage. NodeDetailPage.test.tsx and OnboardingPage.test.tsx additions cover the new link/card behavior directly.

Nothing to flag. The one caveat, as above, is that I could not execute the test suite, tsc, or eslint locally — if CI's matrix turns up something this read-through missed, that remains the authoritative source.

@Babissimo
Babissimo merged commit afbc8e4 into main Sep 25, 2026
21 checks passed
@Babissimo
Babissimo deleted the polled-radar-owner-page branch September 25, 2026 12:22
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.

1 participant