Admin Panel
Read-only ops dashboard at /admin,
shared-secret bearer gate, fail-closed when not configured.
What's exposed
| Page | Shows |
|---|---|
/admin |
Overview — scoring totals, stub rate, recent scores, enrollments, API keys |
/admin/analytics |
Band distribution, 7d/24h deltas, conversion funnel, key tiers |
/admin/status |
Live DB probe, uptime, commit SHA |
Every page is bearer-gated at the API layer. The web reads ADMIN_TOKEN
from its own env (never leaked to the browser bundle) and proxies
requests to the API with Authorization: Bearer <ADMIN_TOKEN>.
Auth flow
visit /admin
│
▼
server component reads ADMIN_TOKEN env
├─ missing: notFound() → 404
├─ present: check hatch_admin cookie
│ ├─ missing/mismatch: render LoginForm
│ └─ match: render the panel
Login:
- Paste the ADMIN_TOKEN in the login form.
POST /api/admin/login— setshatch_admincookie, httpOnly, secure.- Redirect back to
/admin.
Logout: POST /api/admin/logout — clears the cookie.
API surface
All admin API routes live under /v1/admin/*:
GET /v1/admin/statsGET /v1/admin/scores?limit=25GET /v1/admin/enrollments?limit=25GET /v1/admin/api-keysGET /v1/admin/analyticsGET /v1/admin/status
See API.md — admin for request/response shapes.
Design rules
Read-only
Mutations route through per-domain admin endpoints with their own tokens:
POST /v1/launch/dispatch(launch scheduler admin)POST /v1/webhooks/dispatch(webhook dispatcher admin)- API key issuance via CLI (
scripts/issue-api-key.ts)
This keeps the blast radius of a leaked ADMIN_TOKEN contained —
reading recent scores is not the same as reshaping treasury state.
Shared-secret for now
Until we have a second operator (and legal-entity / HR workflows for revocation), multi-user RBAC is overkill. When we cross that threshold, I.3 gets a follow-up sprint that swaps in per-user Hatcher-NFA-gated admin.
Fail-closed on env
If ADMIN_TOKEN isn't set on either side, the route returns 503
not_configured. No silent open-admin mode. No test-tokens in code.
Rotation
Rotate the admin token:
- Generate a new high-entropy string (>= 256 bits).
- Update Vercel env var
ADMIN_TOKEN(web). - Update Railway env var
ADMIN_TOKEN(api). - Both services must redeploy for the new value to take effect. Vercel is automatic on env change; Railway may need a manual restart.
- Evict existing cookies: rotate
hatch_admincookie domain/name, or just let the cookie-check fail and force re-login.
Observability
- Every admin request is logged at
infolevel with the request ID. - Failed auth (401) is logged at
warn. /admin/statusis probe-safe — don't alert on 503 from it because 503 is the expected "degraded" state (see on-call runbook).
Screenshots (add during next docs pass)
/adminoverview/admin/analyticsdashboard/admin/statushealthy + degraded states
Future work
- RBAC — per-user tokens tied to Hatcher NFA ownership (post-D.2).
- Audit log — every admin view is logged with the presenter's wallet.
- Read-only team members — limited tokens that can see analytics but not recent enrollments.
- PagerDuty integration —
/admin/statusfeeds a dedicated health channel.
Tracked under Sprint I.3 v2 + I.7 extension.