🖥️

18-Web-Admin-Panel

18 — Laravel Web Admin Panel & Control Center Spec

Purpose. The Laravel application is not a headless API. It hosts a first-class web UI — the Control Center — that is the system's primary operator surface. The iPhone app is a thin remote control over the same HTTP/SSE contract. This document specifies the web client.

Parity rule. Every operator action the iPhone app can perform, the Control Center can perform — and vice-versa. The HTTP/SSE API in 14 — API Endpoints Reference is the single contract.


1. Architecture & stack

  • Framework. Laravel 11 monolith. Server-rendered, hypermedia-first.
  • Admin CRUD layer. Filament 3 for resource management — projects, server params, agent providers, users, API tokens, audit log, rate-limit overrides. Filament gives us tables, forms, validation, soft-deletes, and roles for free.
  • Operator layer. Livewire 3 + Alpine.js 3 + Tailwind CSS 3 for the high-interaction surfaces: run console, file diff viewer, snapshot compare, RAG inspector, plan/approve flow. These are not Filament resources because they are stream-driven and stateful.
  • Realtime. Laravel Reverb (native WebSockets) for the web; the same backend also emits SSE for the iPhone. One broadcaster, two transports.
  • Build. Vite, TypeScript for Alpine plugins, PostCSS, Tailwind JIT. No SPA framework, no Inertia — keep it boring.
  • Auth (web). Session cookies + CSRF + 2FA (Fortify). Same User model as the API.
  • Auth (API). Sanctum personal-access tokens with abilities. Issued from the Control Center → Profile → API Tokens.
  • Authorization. Spatie laravel-permission. Roles: owner, admin, operator, reviewer, viewer. Abilities mirror Sanctum abilities for the API.
  • Layout. Filament shell for admin pages; a custom Tailwind shell (resources/views/layouts/console.blade.php) for operator pages so Livewire components can take full bleed.

2. Why Laravel hosts the web UI (decision record)

OptionVerdictReason
Separate Next.js / Nuxt SPARejectedDoubles auth, doubles deploy, drifts from API contract, slower to ship.
Inertia.js + VueRejectedAdds a JS tier without buying realtime; Livewire covers the interactive needs.
Filament + LivewireChosenOne codebase, one auth, server is the source of truth, realtime via Reverb, fastest path to parity with iPhone.
Pure Blade + htmxRejectedPossible, but Livewire 3 is closer to the team's Laravel idiom and ships morphdom + wire-stream.

3. Navigation map

/                                  → redirect to /dashboard
/dashboard                         Operator home — last runs, active runs, alerts
/projects                          Project list (Filament resource)
/projects/{project}                Project detail (Livewire)
/projects/{project}/files          Project file browser (read-only)
/projects/{project}/map            PROJECT_MAP viewer
/projects/{project}/schema         DATABASE_SCHEMA viewer
/projects/{project}/diagrams       ARCHITECTURE_DIAGRAMS viewer
/projects/{project}/rag            RAG inspector (chunks, search, reindex)
/runs                              Run list with filters
/runs/new                          New run wizard
/runs/{run}                        Run detail — live console (Livewire + Reverb)
/runs/{run}/files                  File changes + diff viewer
/runs/{run}/snapshots              Snapshots + restore
/runs/{run}/approve                Plan/approve gate
/snapshots                         Cross-project snapshots
/docs/{project}/{path}             Documentation viewer
/admin/server-params               Filament — server params (masked secrets)
/admin/providers                   Filament — agent providers and models
/admin/users                       Filament — users, roles, abilities
/admin/tokens                      Filament — API tokens (web + iPhone)
/admin/audit                       Filament — audit log viewer
/admin/rate-limits                 Filament — per-endpoint overrides
/profile                           User profile, password, 2FA, sessions
/profile/tokens                    User's own API tokens for iPhone pairing

Filament owns everything under /admin/* and /projects list. Everything else is Livewire pages mounted on the custom console layout.


4. Web ↔ iPhone parity matrix

Every row must be reachable from both clients and call the same API endpoints.

CapabilityWeb (Control Center)iPhoneAPI
LoginSession + 2FAToken (paired from web)POST /api/auth/token
List projects/projectsProjects tabGET /api/projects
Create project + cloneFilament formNew Project sheetPOST /api/projects/clone
Index RAGProject page buttonProject detail buttonPOST /api/projects/{p}/index-rag
New run/runs/new wizardNew Task sheetPOST /api/runs
Start / pause / resume / cancelRun detail buttonsRun detail buttonsPOST /api/runs/{r}/{action}
Live consoleLivewire + Reverb WSSSE via URLSessionGET /api/runs/{r}/events/stream
File diff viewerMonaco diff componentNative diff viewGET /api/runs/{r}/diff
Approve / reject planApprove modalApprove sheetPOST /api/runs/{r}/approve
Commit / pushGated buttonsGated buttonsPOST /api/runs/{r}/commit/push
Restore snapshotSnapshot list actionSnapshot list actionPOST /api/snapshots/{s}/restore
Docs viewerMarkdown rendererNative MD rendererGET /api/projects/{p}/docs
Server paramsFilament resource (masked)Read-only viewerGET/PUT /api/settings/server-params

A contract test (tests/Feature/ParityTest.php) asserts every API route used by the iPhone client is also exercised by at least one web Livewire/Filament action. Drift fails CI.


5. Operator screens — detailed

5.1 Dashboard (/dashboard)

  • Header: workspace name, active run count, queue depth, Horizon status pill.
  • Cards: "Active runs" (live), "Queued runs", "Failed in last 24h", "Stale RAG indexes".
  • Table: Recent 20 runs with project, agent, status, duration, last event.
  • Realtime: subscribes to runs.dashboard Reverb channel; updates rows in place.

5.2 Run detail (/runs/{run})

The centerpiece. Three-pane layout:

  • Left (240px): run metadata, agent, model, branch, status pill, action buttons (pause/resume/cancel/approve/commit/push).
  • Center (flex): Live console Livewire component.
    • Each event renders as a row with timestamp, severity pill, event-type tag, title, expandable JSON payload.
    • Stream source: Reverb channel private-runs.{run_id} carrying the same event payloads the SSE endpoint emits.
    • Filters: severity multi-select, event-type multi-select, free-text search, "only show errors" toggle.
    • Sticky bottom: input box for waiting_for_user state — submits POST /api/runs/{run}/resume.
    • Buttons: copy log, download .ndjson, jump to latest, pin event.
  • Right (360px): tabs — Files, Plan, RAG, Git, Commands, Snapshots.

5.3 File diff viewer (/runs/{run}/files)

  • File tree on left with action badges (+, ~, -).
  • Right pane: Monaco Editor in diff mode, side-by-side and inline toggle.
  • Per-file actions: approve, reject, revert from snapshot, open in editor URL (vscode://, cursor://).
  • Bulk "approve all" gated by role reviewer or higher.

5.4 Plan / approve gate (/runs/{run}/approve)

  • Renders the AI plan as a checklist of intended steps.
  • Shows pre-run snapshot id, affected files preview, commands that will run.
  • Buttons: Approve & continue, Approve & auto-commit, Reject with feedback.

5.5 RAG inspector (/projects/{project}/rag)

  • Top: index stats (chunk count, last indexed, stale count, embedding model).
  • Search box → calls retrieval contract from page 04, shows topK with source, score, chunk_title, expandable text.
  • Actions: Reindex changed, Full reindex, Purge.

5.6 Snapshots (/runs/{run}/snapshots, /snapshots)

  • Table: type, created_at, git_commit_hash, archive_path, size.
  • Row actions: Restore, Compare with current (opens diff viewer in compare mode), Download.

5.7 Documentation viewer (/docs/{project}/{path})

  • Renders Markdown with Mermaid (mermaid.js), syntax highlighting (Shiki), anchor links.
  • Sidebar TOC, breadcrumbs, "open in repo" link, last-updated badge.
  • Read-only — editing happens in the codebase via runs.

6. Admin screens — Filament resources

All under /admin/*. Implemented as standard Filament v3 resources.

ResourcePagesNotes
ProjectResourceList, Create, Edit, ViewSoft-delete; "Clone now" action; "Reindex RAG" action.
ServerParamResourceList, EditSecret fields rendered as • ••••••• with reveal-on-click + audit entry. Validation per param.
AgentProviderResourceList, EditToggle provider on/off, set default model, test connection.
UserResourceList, Create, EditRole assignment, ability matrix, force-logout.
ApiTokenResourceList, Create, RevokeDisplay token once on create; QR code for iPhone pairing.
AuditLogResourceList, ViewRead-only; filters by actor, action, target, time.
RateLimitResourceList, EditPer-endpoint overrides.

Filament policies map 1:1 to Sanctum abilities.


7. Realtime transport

  • Broadcaster: BROADCAST_DRIVER=reverb.
  • Channels:
    • private-runs.{run_id} — per-run event stream (mirrors SSE).
    • private-projects.{project_id} — project-level signals (RAG progress, file scan).
    • private-dashboard — workspace-wide counters.
  • Client: Laravel Echo + pusher-js (Reverb-compatible). Wired into Livewire components via #[On('echo-private:...')].
  • Auth: standard /broadcasting/auth with session for web, Sanctum token for any future web-token use.
  • Fallback: if WS connection drops, Livewire polls /api/runs/{run}/events?since=last_id every 3s until it reconnects.
  • Backpressure: server batches events into groups of up to 50 per WS frame or every 250 ms, whichever first.

The SSE endpoint for iPhone reads the same agent_events rows; the broadcaster and the SSE responder are two sinks for the same ConsoleEventService::publish() call.


8. Component inventory (Livewire + Blade)

app/Livewire/
├── Console/
│   ├── RunConsole.php          Live event stream + filters
│   ├── EventRow.php            Single event with payload expand
│   ├── ResumeInput.php         Sticky input for waiting_for_user
│   └── ConsoleFilters.php
├── Files/
│   ├── FileTree.php
│   ├── DiffViewer.php          Wraps Monaco via Alpine
│   └── ApproveBar.php
├── Run/
│   ├── RunHeader.php           Status pill + action buttons
│   ├── PlanPanel.php
│   ├── GitPanel.php
│   ├── RagPanel.php
│   └── SnapshotPanel.php
├── Project/
│   ├── ProjectHeader.php
│   ├── RagInspector.php
│   └── DocsViewer.php
└── Dashboard/
    ├── ActiveRunsCard.php
    └── RecentRunsTable.php
resources/views/
├── layouts/
│   ├── console.blade.php       Operator layout (full bleed)
│   └── app.blade.php           Filament wrapper
├── components/
│   ├── severity-pill.blade.php
│   ├── status-pill.blade.php
│   ├── kbd.blade.php
│   └── empty-state.blade.php
└── livewire/...

9. Styling system

  • Tailwind 3 with custom tokens in tailwind.config.js:
    • Severity colors: info=sky-500, success=emerald-500, warning=amber-500, error=rose-500, debug=zinc-400.
    • Status colors mirror page 12 console spec.
  • Dark mode default; light mode toggle in profile menu.
  • prose class for docs viewer with @tailwindcss/typography.
  • Monospace stack: JetBrains Mono, ui-monospace, SFMono-Regular, Menlo.
  • Icons: Heroicons (Filament default) + Lucide via Blade Icons.

10. Performance budgets

SurfaceTargetMechanism
Dashboard TTFB< 200 ms p95Cached counters, no N+1.
Run detail first paint< 400 ms p95Skeleton, then hydrate from Reverb.
Console event lag< 500 ms wire-to-render p95Reverb direct push, no polling.
Diff viewer for 1 MB file< 800 msMonaco lazy-loaded, server pre-computes diff.
Filament table page< 250 ms p95Indexed queries, eager loads.

11. Accessibility

  • WCAG 2.1 AA target.
  • Keyboard shortcuts in console: j/k navigate events, f focus filter, / search, r resume, p pause, g d go to dashboard.
  • All action buttons reachable via Tab; focus rings preserved.
  • Severity not encoded by color alone — every pill has text + icon.
  • Screen-reader live region for new console events (polite).

12. Telemetry & audit

  • Every Control Center action that mutates state writes an audit_log row: actor, ip, ua, route, target, before/after hash.
  • Filament audit viewer reads from the same table.
  • Web client emits client_perf beacons for first-paint and console-lag percentiles.

13. iPhone pairing flow

  1. Operator opens /profile/tokens in the Control Center.
  1. Clicks Create token for iPhone → Sanctum personal-access token with abilities checklist.
  1. Server displays the plaintext token once + a QR code encoding agentws://pair?base=https://...&token=....
  1. iPhone app scans QR → stores token in Keychain → first call GET /api/me validates.
  1. Token row in DB stores device_name, last_used_at, ip, ua; revocable from the same page.

14. Testing

  • Filament resources: Pest feature tests per resource (list/create/edit/delete + policy).
  • Livewire components: Livewire::test() for state transitions; snapshot tests for rendered HTML.
  • Realtime: integration test asserts that emitting a ConsoleEvent publishes to both Reverb channel and SSE stream and that a Livewire RunConsole instance receives it.
  • Parity test: tests/Feature/ParityTest.php walks routes/api.php, collects routes tagged #[IphoneClient], and asserts each is exercised by at least one web action route in a manifest.
  • Browser: Playwright smoke tests for login → new run → see first event → pause → resume → approve → commit.

15. Deployment

  • Single Laravel app, one container image, behind nginx.
  • Sidecar: Reverb on its own port (8080 internal), proxied at /app/{appId} and /apps/{appId}/events.
  • Horizon supervisor inside the app container or as separate worker container in prod.
  • Vite build runs in CI; assets served by nginx with long cache + Vite::manifest().
  • Health endpoints: /up (Laravel), /healthz/reverb, /healthz/horizon.

16. Open questions

  1. Should the Control Center embed Claude Code's own UI when present, or remain the canonical surface? Default: canonical surface.
  1. Multi-tenant teamspaces in v1, or single workspace? Default: single workspace, design schema-ready for tenancy.
  1. Inline AI chat inside the docs viewer? Out of scope for v1.
  1. Self-hosted Monaco vs CDN? Default: self-hosted via Vite.

17. Acceptance criteria

  • An operator can complete the full flow — create project → clone → index RAG → new run → watch live console → approve plan → review diff → commit — entirely in the Control Center.
  • The exact same flow is reachable on the iPhone app using the same API.
  • A new API route added without a corresponding web action fails the parity CI check.
  • Secrets are never rendered in plain text in any view.
  • Reverb downtime degrades gracefully to polling without losing events.