feat(dashboard): GuruConnect v2 operator console (pass 1)
All checks were successful
All checks were successful
React + Vite + TypeScript SPA: scaffold, operations-terminal design system, Bearer-token auth, and the Machines view. - Design system: OKLCH-tinted dark theme (ink-slate + signal-cyan), Hanken Grotesk + JetBrains Mono, status-color language (online/offline/granted/pending/denied/not_required), motion with prefers-reduced-motion honored. - Auth: token in sessionStorage via ref (never React state), protected routes, 401 session teardown, admin-gated per-agent-key UI. - Machines view: data table (sticky header, keyboard-activated rows, skeleton loading, actionable empty/error states), non-blocking detail drawer, delete confirm, admin key management with copy-once reveal. - UI primitives: Modal (focus trap + inert + portal + dialogStack), Drawer, Table, Badge/StatusDot, toast, states. - Typed API client normalizing the two error-envelope shapes. Passed Code Review (no blockers), impeccable critique-and-polish, and local gates (tsc/lint/build green). Dev-only Vite proxy to :3002. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
109
dashboard/README.md
Normal file
109
dashboard/README.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# GuruConnect Operator Dashboard (v2)
|
||||
|
||||
React + Vite + TypeScript SPA — the operator console for GuruConnect v2. A dark
|
||||
"operations terminal" UI for managing the remote-support fleet.
|
||||
|
||||
> **Pass 1 scope.** This pass ships the scaffold, design system, app shell,
|
||||
> auth, the typed API client, and the **Machines** view. Sessions, Codes, and
|
||||
> Users are nav stubs only (disabled in the sidebar) and arrive in later passes.
|
||||
|
||||
## Stack
|
||||
|
||||
- **React 18** + **React Router 6** (client-side routing)
|
||||
- **Vite 5** (dev server + build)
|
||||
- **TypeScript** (strict)
|
||||
- **@tanstack/react-query** (server-state, polling, cache invalidation)
|
||||
- **@fontsource** — Hanken Grotesk (UI) + JetBrains Mono (technical data)
|
||||
|
||||
No component/icon libraries — primitives and icons are hand-built to keep the
|
||||
console aesthetic and the bundle lean.
|
||||
|
||||
## Scripts
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev # Vite dev server (proxies /api + /ws to the local GC server)
|
||||
npm run build # tsc -b && vite build -> dist/
|
||||
npm run preview # serve the production build locally
|
||||
npm run typecheck # tsc --noEmit
|
||||
npm run lint # eslint
|
||||
```
|
||||
|
||||
## Project layout
|
||||
|
||||
```
|
||||
src/
|
||||
api/ Typed API client + response interfaces (source of truth: server/src/api/*.rs)
|
||||
client.ts fetch wrapper: base URL, bearer token, dual error-envelope normalization
|
||||
types.ts TS mirrors of the Rust response structs
|
||||
auth.ts login / me / logout
|
||||
machines.ts list / get / history / delete + admin key endpoints
|
||||
stubs.ts sessions / codes / users — scaffolds for later passes
|
||||
auth/ AuthProvider (token in memory + sessionStorage), context, ProtectedRoute
|
||||
components/
|
||||
ui/ Reusable primitives: Button, Badge/StatusDot, Table, Panel,
|
||||
Modal, ConfirmDialog, Input/Field, Spinner, States, Toast
|
||||
layout/ AppShell, Sidebar, Topbar, PageHeader, inline SVG icons
|
||||
features/
|
||||
auth/ LoginPage
|
||||
machines/ MachinesPage + detail / delete / admin-keys modals + hooks
|
||||
lib/ time formatting, clipboard, relay-status probe
|
||||
styles/ tokens.css (design tokens)
|
||||
```
|
||||
|
||||
## Design system — "operations terminal"
|
||||
|
||||
Dark control-room console. Tokens live in `src/styles/tokens.css`; primitive
|
||||
styles in `src/components/ui/*.css`.
|
||||
|
||||
- **Surfaces:** `--bg #0b0f14`, `--panel #141b22`, `--panel-2 #0e1419`
|
||||
- **Accent (signal cyan):** `--accent #22d3bf` — primary actions + live state
|
||||
- **Status language (dot + label, used everywhere):** ok/online `--ok`,
|
||||
pending `--warn` (soft pulse), denied/offline/error `--bad`, neutral
|
||||
`--neutral`. Mapping centralised in `components/ui/status.ts`.
|
||||
- **Type:** Hanken Grotesk for UI; **JetBrains Mono for all technical data**
|
||||
(agent IDs, support codes, IPs, versions, timestamps, key fingerprints).
|
||||
- **Motion (restrained):** staggered row fade-in, the consent pulse, the live
|
||||
relay pip, hover transitions. All disabled under `prefers-reduced-motion`.
|
||||
|
||||
## Auth
|
||||
|
||||
`POST /api/auth/login` → `{ token, user }`. The token is held in an in-memory
|
||||
ref and mirrored to **sessionStorage** (never localStorage), so it clears when
|
||||
the tab closes. `GET /api/auth/me` restores the session on reload;
|
||||
`POST /api/auth/logout` revokes it server-side. The client attaches
|
||||
`Authorization: Bearer <token>` to every request and bounces to `/login` on any
|
||||
401. Admin-only UI (per-agent key management) is gated on `role === "admin"`.
|
||||
|
||||
The API uses **two** error envelopes — `{ error }` and
|
||||
`{ detail, error_code, status_code }`. `api/client.ts` extracts a message from
|
||||
whichever is present (and falls back to plain-text bodies that some routes
|
||||
return), so callers see one normalized `ApiError`.
|
||||
|
||||
## Dev proxy
|
||||
|
||||
`vite.config.ts` proxies `/api` and `/ws` to the local GC server
|
||||
(`http://localhost:3002`). Run the Rust server locally, then `npm run dev` —
|
||||
same-origin requests reach the backend with no CORS setup.
|
||||
|
||||
To develop the UI against a *remote* backend instead, set `VITE_API_URL`
|
||||
(see `.env.example`).
|
||||
|
||||
## Production serving — follow-up (NOT wired in this pass)
|
||||
|
||||
The build uses `base: "./"` so emitted assets use relative paths. Production
|
||||
serving means copying `dist/` into the GC server's static directory and adding a
|
||||
catch-all route that returns `index.html` for non-API, non-asset paths (so deep
|
||||
links like `/machines` survive a hard reload under the `BrowserRouter`).
|
||||
|
||||
That Rust-side wiring is a **deploy concern** and is intentionally left for a
|
||||
later step:
|
||||
|
||||
1. Copy `dist/` → `server/static/` (or serve `dist/` directly).
|
||||
2. Add an Axum fallback route serving `index.html` for unmatched GET paths,
|
||||
*after* the `/api/*`, `/ws/*`, and static-asset routes.
|
||||
3. If the dashboard is mounted under a sub-path rather than the server root,
|
||||
switch Vite `base` to that path and pass the same `basename` to
|
||||
`<BrowserRouter>`.
|
||||
|
||||
No server/Rust changes were made in this pass.
|
||||
Reference in New Issue
Block a user