RedFlag/RAF/components/03-web.md
Source publisher ffdccba10f source: advance the checked projection
Source-Sha: 194a59adfd6c901635da7a9c18253bb2210e2426
Policy-Sha: b9b0be0ee74e55f561a803b8aef6be3c2134f5a83b46816a069a372f2eb04f4d
Tree-Digest: 4697f2e281c1c6ecafff17cf6eeb391a0a1ac0fa6e5fc429f5b473f4579c252c
2026-09-21 14:39:08 +00:00

74 lines
4 KiB
Markdown

# Web Component
**React dashboard, embedded into the server binary — the operator's single pane of glass.**
---
## Stack
React 18 + TypeScript 5 + Vite + Tailwind 3, react-router 6, react-hot-toast. No state framework beyond a small store (`lib/store.ts`); server is the source of truth, the UI polls.
**Build embedding:** the production bundle is staged into `server/internal/webui/dist` before the server compiles — that directory is gitignored, so a bare `go build` embeds an *empty* UI. The release pipeline stages it; local dev runs Vite separately. The supported deployment surface is the root `docker-compose.yml` and `OPERATIONS.md`.
**Aesthetic:** hand-crafted 90's Novell look. This is deliberate and load-bearing for the project's identity — no modern flat-design rewrites.
---
## Structure
```
web/src/
├── pages/ # Route-level views
│ ├── Dashboard, Agents, Updates, PackageDetail
│ ├── Docker, History, LiveOperations
│ ├── SecuritySettings, Settings, settings/, RateLimiting
│ └── Setup, Login, TokenManagement
├── components/
│ ├── primitives/ # FilterBar, SearchInput, FilterDropdown, FilterPill, FilterCountButton,
│ │ # SortableTable, StateBadge (StatusBadge/SeverityBadge), CommandCard,
│ │ # CommandStatusBadge, Modal, PageState, Pagination, StatCard,
│ │ # ScreenshotCard, MetricItem, ProcessTable
│ ├── security/ # Security health panels
│ ├── DependencyClosureTree, VulnerabilityList
│ ├── ProcessesTab, ProcessDetailModal
│ └── AgentHealth, HistoryTimeline, AttentionPanel, ...
├── hooks/ # Stateful composition over primitives
│ ├── useFilterUrl # URL-synced filter state (useFilterUrl.ts)
│ ├── useQueryParser # key:value query string parsing (useQueryParser.ts)
│ ├── useMultimodalFilter # Composed filter: search box + filter pills + URL (useMultimodalFilter.ts)
│ ├── useDebounce # Generic debounce (useDebounce.ts)
│ └── useColumnSort # Reusable column sort state (useColumnSort.tsx)
├── lib/
│ ├── api.ts # API client (web-auth boundary)
│ ├── polling.ts # POLL.* constants — all intervals centralized
│ ├── store.ts, queryParser.ts, vulnerabilities.ts
│ └── client-logger.ts # Client errors ship to the server log (ETHOS #1)
├── desktop/ # Retained legacy Tauri web shell; not RedFlag Desktop
└── types/ # Shared TS types mirroring server models
```
---
## Conventions
- **One way to render state.** Status and severity render through `StatusBadge` / `SeverityBadge` — never ad-hoc colored spans. Tables that sort use `SortableTable`.
- **One way to filter.** Filter state syncs to URL via `useFilterUrl`; free-text search uses a local `useState` + `useDebounce` pair (instant feedback in the input, debounced value for API calls). Compose both into a `FilterBar`. No ad-hoc `useState` chains for filter state.
- **Polling intervals** come from `POLL.*` in `lib/polling.ts` — no hardcoded milliseconds in components.
- **Render the divergence, not the union** (framework §11.8): when agent-reported and server-expected state differ, the UI shows the difference, it does not paper over it.
- All routes sit behind `WebAuthMiddleware` (admin routes additionally behind `AdminRoleMiddleware`) — see [security/01-trust-boundaries](../security/01-trust-boundaries.md).
---
## Honest Gaps
- No automated web tests ([testing/01-test-pyramid](../testing/01-test-pyramid.md))
- Mobile layout usable, not optimized
- Several UI coverage gaps tracked as `UI-*` tasks (not architecture — task tier)
The native RedFlag Desktop is the Qt/QML component described in
[components/05-desktop](05-desktop.md). `web/src/desktop` is retained legacy source, not a
second supported Desktop implementation or an architectural authority.
---
*Last reviewed: 2026-09-14*