|
| 1 | +# Browser Portal |
| 2 | + |
| 3 | +Read when: |
| 4 | + |
| 5 | +- using the web UI to inspect leases or runs; |
| 6 | +- changing portal pages or page-level routes; |
| 7 | +- deciding whether a feature should land in the CLI, the API, or the portal. |
| 8 | + |
| 9 | +The browser portal is a small server-rendered web UI hosted by the same |
| 10 | +Cloudflare Worker that backs the Crabbox API. It is not a separate frontend |
| 11 | +or single-page app: every page is HTML rendered by the Worker, with light |
| 12 | +client-side JavaScript only for filtering, sorting, and clipboard copy. |
| 13 | + |
| 14 | +## URL Map |
| 15 | + |
| 16 | +```text |
| 17 | +GET /portal |
| 18 | +GET /portal/leases/{id-or-slug} |
| 19 | +POST /portal/leases/{id-or-slug}/release |
| 20 | +GET /portal/leases/{id-or-slug}/vnc |
| 21 | +GET /portal/leases/{id-or-slug}/code/ |
| 22 | +GET /portal/runs/{run-id} |
| 23 | +GET /portal/runs/{run-id}/logs |
| 24 | +GET /portal/runs/{run-id}/events |
| 25 | +GET /portal/runners/{provider}/{runner-id} |
| 26 | +``` |
| 27 | + |
| 28 | +Portal authentication uses a browser session cookie minted after a successful |
| 29 | +GitHub login through the same OAuth flow as `crabbox login`. The cookie |
| 30 | +carries owner/org claims; the Worker scopes every page to that identity. Raw |
| 31 | +Cloudflare Access headers are not trusted - only a verified Access JWT email |
| 32 | +can become the portal owner. |
| 33 | + |
| 34 | +## Lease Index `/portal` |
| 35 | + |
| 36 | +The index renders a searchable, paginated, sortable lease grid. Columns |
| 37 | +include compact provider/target badges, icon-only access capabilities (SSH, |
| 38 | +VNC, code, browser), relative time cells, dense rows, and sticky column |
| 39 | +headers. Filters at the top of the page select active, ended, provider, |
| 40 | +target, or all. |
| 41 | + |
| 42 | +Default view rules: |
| 43 | + |
| 44 | +- Defaults to active leases when any are active. |
| 45 | +- Falls back to all visible leases when the active list is empty. |
| 46 | +- Normal browser sessions see only their own owner/org leases. |
| 47 | +- Admin sessions also see non-owned runner leases. `mine` and `system` |
| 48 | + filters distinguish personal leases from external runners (Blacksmith |
| 49 | + Testboxes synced from CLI list output) so external rows do not leak to |
| 50 | + normal users. |
| 51 | + |
| 52 | +External runner rows render in the same grid as muted, disabled rows. They |
| 53 | +include status/provider filters, inferred GitHub Actions run/workflow links, |
| 54 | +status badges, `stuck` markers for long-queued or long-running Actions |
| 55 | +owners, a copyable local stop command, and stale markers when the next |
| 56 | +runner sync no longer sees a previously visible runner. Clicking an |
| 57 | +external runner opens `/portal/runners/{provider}/{runner-id}`, a |
| 58 | +visibility-only detail page. |
| 59 | + |
| 60 | +## Lease Detail `/portal/leases/{id-or-slug}` |
| 61 | + |
| 62 | +The lease detail page shows: |
| 63 | + |
| 64 | +- compact provider/target badges and the lease state pill; |
| 65 | +- bridge status (whether the WebVNC and code-server bridges are up); |
| 66 | +- the latest Linux telemetry sample as gauges, with sparklines when multiple |
| 67 | + samples are present; |
| 68 | +- stale-telemetry, high-load, high-memory, and high-disk status pills when |
| 69 | + thresholds are exceeded; |
| 70 | +- an access panel with copy-to-clipboard commands for `crabbox ssh`, |
| 71 | + `crabbox run`, `crabbox webvnc`, and `crabbox code`; |
| 72 | +- a viewport-fitted "recent runs" grid with state filters; |
| 73 | +- a stop action when the lease is releasable. |
| 74 | + |
| 75 | +`/portal/leases/{id-or-slug}/vnc` and `/portal/leases/{id-or-slug}/code/` |
| 76 | +are bridges, not portal pages. They proxy WebSocket and HTTP traffic to the |
| 77 | +matching capability on the lease so a user does not need an SSH tunnel to |
| 78 | +open the desktop or editor. See |
| 79 | +[Interactive desktop and VNC](interactive-desktop-vnc.md) and |
| 80 | +[code command](../commands/code.md). |
| 81 | + |
| 82 | +## Run Detail `/portal/runs/{run-id}` |
| 83 | + |
| 84 | +Run detail mirrors the `/v1/runs/...` resources but uses the browser session |
| 85 | +cookie, so users can inspect logs and events without copying a bearer token |
| 86 | +into the browser. The page renders: |
| 87 | + |
| 88 | +- the command, owner, lease, provider metadata, and exit status; |
| 89 | +- a JUnit summary when the run attached results; |
| 90 | +- a searchable, paginated event table with event-type filters; |
| 91 | +- a copyable retained log tail; |
| 92 | +- bounded load, memory, and disk trend lines for longer Linux runs that |
| 93 | + attached mid-run telemetry samples. |
| 94 | + |
| 95 | +`/portal/runs/{run-id}/logs` returns the retained log as plain text. |
| 96 | +`/portal/runs/{run-id}/events` returns the events as JSON. Both stay raw on |
| 97 | +purpose so they are easy to copy or pipe. |
| 98 | + |
| 99 | +## Runner Detail `/portal/runners/{provider}/{runner-id}` |
| 100 | + |
| 101 | +External runner detail is visibility-only. It shows: |
| 102 | + |
| 103 | +- owner/org; |
| 104 | +- inferred GitHub Actions ownership (workflow, run id, status); |
| 105 | +- lifecycle timestamps; |
| 106 | +- boundary notes that explain Crabbox cannot stop or release the runner; |
| 107 | +- a copyable local stop command for the operator's terminal. |
| 108 | + |
| 109 | +External runners do not heartbeat through Crabbox and do not participate in |
| 110 | +Crabbox lease expiry, cleanup, or cost accounting. The detail page exists so |
| 111 | +operators have a single URL to share when an external runner is stuck. |
| 112 | + |
| 113 | +## Authentication And Scope |
| 114 | + |
| 115 | +```text |
| 116 | +session authenticated GitHub user (owner/org embedded) |
| 117 | +admin portal sessions with the admin token role |
| 118 | +``` |
| 119 | + |
| 120 | +Per-route scope rules: |
| 121 | + |
| 122 | +- Lease index, lease detail, run detail: own leases/runs only. |
| 123 | +- Admin filters and external runner visibility: admin sessions only. |
| 124 | +- VNC and code bridges: only when the lease has the matching capability and |
| 125 | + the session owns the lease. |
| 126 | + |
| 127 | +Tokens for `/v1/...` API calls are separate. The portal never echoes a |
| 128 | +bearer token back to the browser. |
| 129 | + |
| 130 | +## Why Server-Rendered |
| 131 | + |
| 132 | +The portal is intentionally a thin server-rendered surface, not a SPA: |
| 133 | + |
| 134 | +- the Worker already owns lease and run data; rendering at the edge avoids a |
| 135 | + separate API/UI deployment; |
| 136 | +- pages stay copy-pasteable - URLs deep-link to a specific lease or run; |
| 137 | +- there is no build step, no JavaScript framework, and no offline session |
| 138 | + management to maintain; |
| 139 | +- the portal cannot drift from the API because both serve the same Durable |
| 140 | + Object state. |
| 141 | + |
| 142 | +Adding a portal feature usually means a new render in `worker/src/portal.ts`, |
| 143 | +a new endpoint in `worker/src/fleet.ts`, and a doc update here. |
| 144 | + |
| 145 | +Related docs: |
| 146 | + |
| 147 | +- [Coordinator](coordinator.md) |
| 148 | +- [Broker auth and routing](broker-auth-routing.md) |
| 149 | +- [History and logs](history-logs.md) |
| 150 | +- [Telemetry](telemetry.md) |
| 151 | +- [Interactive desktop and VNC](interactive-desktop-vnc.md) |
| 152 | +- [Source map](../source-map.md) |
0 commit comments