Skip to content
BreachPilot

WebUI Overview

Vite + React 18 + TypeScript SPA under webui/src/. Loopback-only; all authority is server-side (AssessmentService / ExploitPolicy).

Bootstrap

LayerFileDetail
HTML entrywebui/index.html:2<html class="dark">, theme toggle script (breachpilot.theme), #root mount
React rootwebui/src/main.tsx:8ReactDOM.createRoot<ErrorBoundary><App />
CSSwebui/src/index.css:1Tailwind base/components/utilities, HSL CSS vars (:root/.dark), custom utilities (bg-grid, glow-primary, animate-scan, etc.)
Version injectwebui/vite.config.ts:13define.__APP_VERSION__ from package.json:version (0.49.12)

index.css defines light :root + dark .dark HSL vars (--background, --primary, etc.). Default is dark via index.html.

Build & Toolchain

ConcernConfigValue
Dev serverwebui/vite.config.ts:22port 5173, strictPort:true, /api proxy → VITE_API_URL or http://127.0.0.1:8765 (ws:true, secure:false)
Previewvite.config.ts:32same proxy as dev
Buildvite.config.ts:44outDir: dist, sourcemap:false, target: es2020
Aliasvite.config.ts:18, tsconfig.app.json:19@/*src/*
Pluginsvite.config.ts:16@vitejs/plugin-react only
TS targettsconfig.app.json:2ES2021, lib:[ES2023, DOM, DOM.Iterable], strict, noUnusedLocals/Parameters, noFallthroughCasesInSwitch
Tailwindtailwind.config.ts:5darkMode:["class"], content index.html + src/**/*.{ts,tsx}, plugins typography+animate, HSL-var colors, typography.invert overrides

See docs/webui/build.md for full build write-up.

Routing

Defined in webui/src/App.tsx:59:

PathComponentLoader
/HomePage (eager)webui/src/routes/HomePage.tsx:62
/sessionsRunListPage (lazy)webui/src/routes/RunListPage.tsx:64
/runs/newNewRunPage<RunWizard>webui/src/routes/NewRunPage.tsx + src/components/run-create/RunWizard.tsx:34
/runs/:runIdRunPage (lazy)webui/src/routes/RunPage.tsx:75
/runs/:runId/artifactsArtifactsPagewebui/src/routes/ArtifactsPage.tsx:23
/runs/:runId/lootLootPagewebui/src/routes/LootPage.tsx:12
/runs/:runId/graphGraphPagewebui/src/routes/GraphPage.tsx:11
/skillsSkillsPagewebui/src/routes/SkillsPage.tsx:223
/modulesAttackModulesPagewebui/src/routes/AttackModulesPage.tsx:29
/goalsGoalsPagewebui/src/routes/GoalsPage.tsx:81
/graphAttackGraphPagewebui/src/features/graph/AttackGraphPage.tsx:37
/statsStatsPagewebui/src/routes/StatsPage.tsx:218
/connectionsConnectionsPagewebui/src/routes/ConnectionsPage.tsx:9
/helpHelpPagewebui/src/routes/HelpPage.tsx:27
/memoryMemoryPagewebui/src/routes/MemoryPage.tsx:9
/systemSystemPageSettingsPagewebui/src/routes/SystemPage.tsx:6, webui/src/features/settings/SettingsPage.tsx:20
*→ /App.tsx:76

All routes are nested under <Layout> (src/components/Layout.tsx:50) which provides sidebar/mobile nav + active-run pill + permission banner + footer. Suspense fallback is Spinner (App.tsx:52).

SPA Serving

python main.py --web builds webui/dist/ if missing, sets api.serve_webui:true in-memory, mounts dist/ at / with deep-link fallback. Dev uses npm run dev against a separately running daemon.

Auth

GateFileBehaviour
TokenGatewebui/src/components/TokenGate.tsxReads sessionStorage breachpilot.apiToken.v1 via api/client.ts:6. Verifies via GET /capabilities (api/hooks.ts:114). 401→ clear+error. status 0 → daemon unreachable hint.
OnboardingGatewebui/src/components/OnboardingGate.tsxAfter token: GET /secrets (api/hooks.ts:169). If any missing and sessionStorage breachpilot.onboarding.v1 !== "1", shows provider+keys+ChatGPT setup.
WelcomeGatewebui/src/components/WelcomeScreen.tsxFirst-visit tour (event breachpilot:open-welcome from HomePage)
ErrorBoundarywebui/src/components/ErrorBoundary.tsxWraps entire app (main.tsx:10)

Token storage: sessionStorage only (survives reload, clears on tab close). Header Authorization: Bearer <token> injected by apiFetch (api/client.ts:75). WS/SSE handshake also uses sessionStorage token (api/ws.ts:236, api/sse.ts:200).

Layout.tsx:63 sign-out clears token and reloads.

State Model

StateOwnerNotes
Server stateTanStack Query (api/hooks.ts)Central queryKeys (hooks.ts:63), defaultQueryOptions (hooks.ts:107), no refetchOnWindowFocus (App.tsx:40)
Live eventsuseRunEvents (api/ws.ts:36)WS-primary, SSE fallback, sequence dedupe, appendBounded + requestAnimationFrame batching
Event cacheeventStore (api/eventStore.ts:19)In-memory LRU (10 runs), MAX_EVENTS_PER_RUN=1000 (api/eventBuffer.ts:4)
Permission modelib/permissionMode.ts + components/permission/PermissionControl.tsxread_only/approve/full_access, auto-answer via autoAnswerFor (routes/RunPage.tsx:169)
Themelib/useTheme.tslocalStorage breachpilot.theme, toggled in Layout
WizardRunWizard.tsx:37 local useStateLifted model/mode/target/goal/power-ups state, buildRequest() serialises to RunCreateRequest
Routerreact-router-dom 6.27`?path=recon

Query Defaults

App.tsx:30QueryClient retry: no retry on 4xx except 408/429, else <2 failures; refetchOnWindowFocus:false. defaultQueryOptions (hooks.ts:107): retry:DEFAULT_RETRY, staleTime:15s, gcTime:5m. Per-hook overrides: capabilities 60s, config 30s, skills 60s, goals/schema Infinity.

API Integration (summary)

api/client.ts:69 apiFetch<T>(path, opts) — prefixes /api/v1 unless absolute, injects bearer + Content-Type, normalises to ApiError (api/client.ts:29, isAuth/isNotFound/isConflict). api/types.ts:879 defines all shapes (RunState, DecisionKind, RunPreview, RunEvent, etc.) plus helpers isActiveState/isTerminalState/stateCategory.

api/hooks.ts — 30+ hooks (see docs/webui/state.md + docs/webui/api-integration.md). Polling: useRuns adaptive 5s/60s (hooks.ts:441), useRun 5s while running/queued/cancelling (hooks.ts:456), useDecisions 5s while pending (hooks.ts:542), useArtifacts 30s while active (hooks.ts:610).

Layout chrome

Layout.tsx:15 NAV_ITEMS (Home/Sessions/Connections/Modules/Goals/Attack Graph/Stats/Skills/Memory/Settings/Help). Connections nav shows active-count badge (desktop active + mobile pill) via useConnections() (Layout.tsx:55). Desktop sidebar + mobile bottom bar. Active-run pill filters isActiveState. Permission banners for approve/full_access. Footer: loopback warning + GitHub link (Layout.tsx:280).

source: repo docs (build sync)Edit this page on GitHub →