Repository navigation
ui: show a sandbox's placement, timeline, operations and snapshots - #1245
Conversation
|
Thanks for opening this pull request! A SPACE maintainer will take a look. CONTRIBUTING.md explains how we review outside contributions. |
1f50456 to
9338329
Compare
Review — 4 findingsMedium
Low
Verified against |
the-gigi-pplx
left a comment
There was a problem hiding this comment.
Approved. 4 non-blocking findings in the review comment above.
zibi-pplx
left a comment
There was a problem hiding this comment.
One addition to the existing review, about tracking the backend gaps this page works around.
-
[low] Link tracking issues for the three backend gaps the page compensates for.
- The debug route returns environment variable values, and the page now fetches it about every 20 s while a detail page is open. The page shows names only, but the values reach the browser.
- Fork operations record their source under the bare UUID (
crates/libs/api/src/fork_operations.rs:986,target_id: Some(&prepared.source_id)), while other operations usesbx_, so the page reads operations twice and merges them. - An admin create records the sandbox owner as the operation's actor, hence the "Recorded for" label (
sandbox-insight.tsx:321).
The description names all three as separate backend changes. Linking an issue for each, and referencing it in the code comments, would make the double read and the label easy to remove once they are fixed.
Withdrawing this approval for now. Any feedback above still stands.
The sandbox detail page showed only the sandbox row's current fields. An operator working a support case had no pod name, no history and no operation log, and the Snapshots card read the public snapshot route, which is an org-scoped hidden operation: for any other org's sandbox, or wherever snapshot operations are not enabled, it said "Snapshots are not enabled" even when a suspended sandbox's only copy of its state was a ready snapshot. The page now reads the admin debug route and the operations log, which already return all of this under the operator gate: - Placement and pods: active and target node, and every pod with its name, namespace, Kubernetes node, generation, state and times. - Timeline: the debug route's merged sandbox, pod, snapshot and port events, newest first. - Operations: each lifecycle operation with its actor, attempts and result or terminal error. - Resources and runtime: shape, network counters, environment variable names (never values), metadata and port exposures. - Snapshots: when the public listing is unavailable, the snapshots the debug route reports (state, codec, age, size, failure), read-only. The header shows the sandbox name and full ID, Org and Owner link to their pages, and a deleted sandbox's not-found page lists the operations recorded against its ID, which outlive the row.
…bled The detail page's Snapshots card fell back to the admin view for every error from the public snapshot listing, so an outage of that route looked the same as snapshot operations being off. It now falls back silently only for the hidden-operation answer, and does not call the org-scoped public route at all for another org's sandbox. Any other failure still shows the snapshots the control plane records, with a warning naming the error. Metadata values that are not strings render as compact JSON instead of "[object Object]".
When the debug read failed, or one of its sections did, the placement, pods, timeline, ports, resources and snapshot views said "none", "No pods recorded" or "template default". Each now says it could not load, with the error, and shows loading until the first answer. Only the newest debug and operations request may write its answer. A failed snapshot listing is retried on Refresh and with the page's other reads, instead of switching the card to the read-only view for the rest of the page's life. The not-found view says the sandbox row is gone only for a 404; other failures read "Could not load the sandbox" with Retry. It shows a failed operations read, and a deleted sandbox's operations keep refreshing. The operations card also lists forks, which record their source by its bare UUID, says when more than the newest 50 exist and that operations are removed 7 days after they were requested, and scopes its reads to the sandbox's org. Timeline summaries use the keys the debug route's timeline source builds.
- Snapshot listings carry a request counter, like the insight reads, so a slow failed retry cannot replace a newer successful listing. - A 404 while the sandbox's create operation is still queued or running says the sandbox is being created instead of "the sandbox row is gone". The page keeps polling and loads the sandbox, with its debug and operations cards, as soon as it appears. - The operations card says finished operations are removed 7 days after they were requested and unfinished ones are kept. - The timeline says when it reaches the debug route's 200-event cap.
The Snapshots card lists its snapshots again on every insight load, every 20 s and after each action, so one failed background listing switched a healthy card to the read-only admin view and removed its Rollback column. A failed listing after a good one now keeps that listing and its rollback, with a warning above it saying when it loaded. A card whose first listing failed still shows the admin view and recovers at the next listing that succeeds. Only the hidden- operation answer switches the card to the admin view.
A 404 from the sandbox read kept the last loaded sandbox on screen, so the debug reads went on and every insight card said "Could not load …: sandbox_not_found" over a page that still showed the sandbox as live. A 404 now clears the loaded sandbox: the page shows the not-found view a page opened after the delete shows, stops the debug reads, and loads the operations at once so the delete is listed.
Every operations row mounted its own user link, and each fetched
GET /v1/users/{id}, so a sandbox with 50 operations by one user made
50 identical requests. The card now looks up each distinct
actor_user_id once and passes the emails to its rows; a failed lookup
is tried again when the set of actors changes.
The sandbox list, the sandbox detail page and the debug helpers each had a copy. The list and detail pages now import the helpers' copy, which also renders NaN as a dash.
db385f9 to
09797bf
Compare
Summary
The sandbox detail page now shows what the control plane records about a sandbox, not just the row's current fields. Operators can answer "where is it running, what happened to it, and who did that" without leaving the console.
The page reads two admin routes that already exist and already sit behind the operator gate:
GET /admin/api/v1/debug/sandbox/{uuid}GET /admin/api/v1/operations?target_type=sandbox&target_id={id}&org_id={org}, read twice: once with thesbx_ID, and once with the bare UUID, under which a fork records its source sandbox.It adds these cards:
DEFAULT_OPERATION_GC_DAYSincrates/libs/api/src/public_operations.rssetsgc_after, and terminal operations past it are deleted). The card looks up each distinct recorded user once, not once per row.Other changes:
sbx_ID (copyable). Org and Owner link to the org and user pages by name and email.crates/libs/api/src/routes/debug/sources/timeline.rs), such aslast_errorandapply_statefor port exposures.formatByteshas one NaN-safe copy, inui/src/lib/sandbox-debug.ts, which the sandbox list and detail pages import. The templates page keeps its own copy.The "Recorded for" column is labelled that way on purpose. A create through the admin API records the sandbox's owner as the operation's actor, while the admin audit log records the operator. That is a backend issue, not changed here.
Stack
Stacked on #1242, which adds
parseSandboxRef; this page uses it to turn its ID into the bare UUID the debug route takes. Merge #1242 first. It is independent of #1244.Validation
npm ci,npm test(77 pass, includingsrc/lib/sandbox-debug.test.mjsfor section errors, timeline ordering and summaries for each event kind, merging operation reads, ages, and sizes includingformatBytes(NaN)),npx tsc --noEmit -p .(no errors) andnpm run buildinui/.space-uiimage):main.startedwith "Could not load placement: sandbox_not_found".locations. The Kind stack's inventory was empty, so the card does not render locations yet. Also not captured: a first snapshot listing that fails, followed by one that succeeds.Visual confirmation
A suspended sandbox, before and after:
Scrolling the new page for a suspended sandbox:
Videos: before, after.
A deleted sandbox, before and after:
A snapshot listing that fails after a good one, on an earlier head of this branch and on this head: the listing and its Rollback column now stay, under a warning:
A sandbox deleted while its page is open, on an earlier head of this branch and on this head: the page now shows the "Sandbox not found" view with the create and delete operations:
Severity and risk
Severity: high. The detail page is where a support case starts, and it hid the timeline, pods and snapshots that the API already serves.
Risk: low to medium.
sbx_ID and under the bare UUID, at the times above; for a sandbox in the signed-in organization, the public snapshot listing at the same times; and the organization and owner once. The Operations card looks up each distinct "Recorded for" user once, however many rows name that user.AI assistance: Claude Code wrote the code, the tests and this description, and ran the validation; a maintainer reviews it.