Frontend Overview
The Proxima Console frontend is a single-page application built with React 18, TypeScript 5, Tailwind CSS, and shadcn/ui components. It provides the operator-facing UI for managing infrastructure across client environments. The application is bundled with Vite and served as static files behind nginx in production.
Technology Stack
- Framework: React 18 with TypeScript 5
- Styling: Tailwind CSS with shadcn/ui component library
- Data Fetching: TanStack React Query for caching, background refetching, and deduplication
- Build Tool: Vite
- Testing: Vitest + React Testing Library (unit), Playwright + Chromium (E2E browser tests)
- Linting: ESLint + Prettier
- Code Splitting: Route-level lazy loading with
React.lazy+Suspense
Communication Model
The frontend communicates with the backend primarily via REST API calls. Data freshness is maintained through React Query's refetchInterval at varying frequencies depending on the page context. Two real-time channels supplement REST:
- Server-Sent Events (SSE) — AI Chat streams assistant responses over SSE (
use-chat-stream.tssendsAccept: text/event-stream). - WebSocket — used only for the interactive web terminal. The full-page terminal (
/hosts/:hostId/terminal) connects to/api/v1/terminal/ws(and/terminal/ws/joinfor shared sessions). This is the single WebSocket exception documented in the SPEC; no other feature uses WebSockets.
Theme
The application uses a light-only design system called "Clean Authority" — authority through restraint, standing apart from the dark dashboards that dominate the space. There is no dark mode (darkMode is removed from the Tailwind config; dark: prefixes are never used).
- Background: Near-white slate (
hsl(240, 17%, 98%)≈#f8f8fa) - Neutrals: Blue-tinted slate (
hsl(240, ...)) — never pure gray - Accent: Copper
#a8522d(hsl(18, 58%, 42%)), reserved for brand identity and AI features and used sparingly — there is no cyan - Typography: Geist (UI) and Geist Mono (code, hostnames, IPs), loaded via Google Fonts in
index.html - Elevation: Borders only — no box-shadows, no glassmorphism, no glow effects
- Cards:
rounded-lg border bg-card; interactive cards shiftborder-colortoward the copper accent on hover (no translate, no shadow) - Tables: Uppercase column labels in a faint slate,
--cardrow background,--border-subtledividers - Border radius: 6px base (
--radius)
All color tokens are CSS custom properties (HSL) defined in frontend/src/index.css and mapped through frontend/tailwind.config.js. Status/severity → Tailwind class maps, polling intervals, and throttle constants are centralized in src/lib/design-tokens.ts. The full token reference lives in the repo at docs/standards/design-system.md.
Authentication
The frontend uses JWT-based authentication with the backend. On each request, the API client attaches the access token as a Bearer header. Tokens are stored in localStorage under proxima_access_token, proxima_refresh_token, and proxima_session_id (used to identify the current session in the session management UI).
Token Lifecycle
- Proactive refresh: Before each API request, the client checks if the access token expires within 2 minutes. If so, it refreshes the token automatically.
- 401 retry: If a request returns 401, the client attempts a single token refresh and retries the request. Concurrent refresh calls are deduplicated with a module-level promise lock.
- Session expiry: If refresh fails, the client dispatches a
CustomEvent("auth:session-expired")which triggers automatic logout and redirect to/login.
AuthContext + useAuth
The AuthProvider component wraps the app inside BrowserRouter and provides the useAuth() hook:
| Property | Type | Description |
|---|---|---|
user | AuthUser | null | Current authenticated user |
clientAccess | Record<string, string[]> | null | Client-to-permissions map from /auth/me |
isSuperAdmin | boolean | Whether user bypasses all permission checks |
isAuthenticated | boolean | Whether a user is logged in |
isLoading | boolean | True during initial token validation on mount |
login(email, password) | Promise<LoginResult> | Returns { kind: "success" } or { kind: "mfa", mfaToken } |
loginWithMFA(mfaToken, code) | Promise<void> | Complete MFA challenge |
loginWithGoogle(code) | Promise<void> | Exchange Google one-time auth code |
logout() | Promise<void> | Server logout + clear tokens + reset state |
refreshUser() | Promise<void> | Re-fetch /auth/me |
usePermissions
The usePermissions() hook provides permission checking based on the authenticated user's clientAccess map:
can(permission, clientId?)— super admin returnstrue; otherwise checks if the user has the permission for the given client (or any client ifclientIdis omitted)accessibleClients— array of client IDs the user has access to (nullfor super admins, meaning all)isSuperAdmin— convenience passthrough
Route Guards
| Component | Behavior |
|---|---|
ProtectedRoute | Redirects to /login if not authenticated. Redirects to /setup-mfa if authenticated but MFA is not enabled. Saves the current location in router state for post-login redirect. Shows a spinner during isLoading. |
PublicRoute | Redirects to the saved location (or /clients) if already authenticated. Shows a spinner during isLoading. |
MFASetupRoute | Only renders when authenticated and mfa_enabled === false. Redirects to /login if not authenticated, /clients if MFA already enabled. |
Auth Pages
| Page | Route | Description |
|---|---|---|
LoginPage | /login | Email/password form. Conditionally shows "Sign in with Google" when GET /auth/config returns google_sso_enabled: true. Redirects to /login/mfa on MFA challenge. |
MFAVerifyPage | /login/mfa | 6-digit TOTP code input with "Use a recovery code" toggle. Reads mfaToken from router state. |
MFASetupPage | /setup-mfa | Two-step mandatory MFA setup flow. See MFA Setup Page. |
GoogleCallbackPage | /auth/google/callback | Reads code from URL params, exchanges it via loginWithGoogle(). |
ForgotPasswordPage | /forgot-password | Email input. Always shows success message (no enumeration). |
ResetPasswordPage | /reset-password | Reads token from URL params. New password + confirm, 12-character minimum. |
AcceptInvitePage | /accept-invite?token=... | Set password form. Reads token from URL search params. Auto-logs in on success. |
User Menu
The AppHeader includes a dropdown menu (right side) with the user's initials avatar, display name, and email. The "Sign out" item calls logout() from useAuth().
Sidebar Permission Gating
The sidebar (AppSidebar) uses usePermissions() together with the useFeatures() feature flags to control navigation visibility. Items are organized into labelled groups, and a group is hidden entirely when none of its items are visible:
Assets
- Projects —
clients:read, for super admins or users with access to zero/multiple clients (single-client users instead see an Environments link pointing directly at their client) - Hosts —
hosts:read - Agents —
agents:read - Clusters and Databases —
assets:read
Observability
- Alerts —
alerts:readand thealertsfeature flag - Changes —
changes:readand thechangesfeature flag - Logs —
logs:readand thelogsfeature flag - Integrations —
integrations:read
Automation
- Runbooks —
runbooks:readand therunbooksfeature flag - AI Chat —
chat:readand theai_chatfeature flag
Service Desk (all gated by servicedesk:read and the service_desk feature flag)
- Home, Requests, Projects, Metrics
Admin navigation lives separately under AdminLayout (driven by src/components/layout/admin-nav.ts), not the main sidebar. Its items include Users (users:read), Teams (teams:read), Roles (roles:read), Service Accounts (users:read), Credentials (credentials:read), Webhook Sources (webhooks:read), Alert Config (alertsources:read), Compliance (compliance:read), Audit Log (audit:read), Access Audit (roles:read), and Terminal Sessions (terminal_sessions:read).
The sidebar footer displays the user's initials, name, and email. The user dropdown in AppHeader includes a "Profile & Security" link and a "Send Feedback" item that opens the feedback dialog.
Feedback Widget
The in-app feedback widget allows authenticated users to submit bugs, feature requests, UX issues, and suggestions directly from the console. It is accessible via:
- User menu: "Send Feedback" item in the
AppHeaderdropdown - Keyboard shortcut:
Ctrl+Shift+F(orCmd+Shift+Fon macOS)
The FeedbackDialog component provides:
- Category selector: Bug Report, Feature Request, UX Issue, Suggestion
- Title (required, max 200 characters) and description (required, max 2000 characters)
- Paste-to-attach screenshot: paste an image from clipboard (
Ctrl+V) to attach a PNG screenshot (max 5MB). A thumbnail preview is shown with a remove button. - Auto-populated page URL: the current browser URL is included automatically
On submit, the frontend calls POST /api/v1/feedback. If Jira is configured on the backend, a Jira issue is created with labels (console-feedback, category) and the screenshot is attached as a file. A success toast shows the Jira issue key (e.g. "Feedback submitted — PDD-42"). If Jira is not configured, the backend logs the feedback to structured logs and returns success without a Jira key.
The dialog state is managed via FeedbackContext in RootLayout, exposed through the useFeedback() hook.
Features
Collapsible Sidebar Layout
The application uses a collapsible sidebar navigation with a breadcrumb header. Navigation items are organized into groups (e.g., "Infrastructure" section with Clients and Hosts). Active items are highlighted with a left border accent in the primary color. The sidebar can be collapsed to maximize content area.
Projects List Page
The UI labels this page Projects (the underlying resource is the "client"); the rest of this catalog uses "client" when referring to the data model.
Route: /clients
Displays all managed clients (projects) with a view toggle between a list (table) view and a card grid view. List is the default; the view preference is persisted in the URL (?view=card) for bookmarkable links. Each card/row shows the project name, environment count, and host count. The page uses React Query with a 60-second refetch interval. An Add button opens a dialog form for creating new projects with auto-generated slugs.
Client Detail Page
Shows a single client's environments with inline host lists beneath each environment. Environment names are clickable links to the Environment Detail Page. Provides a quick overview of infrastructure distribution across environments. Host lists use React Query with a 30-second refetch interval (POLLING.CLIENT_HOSTS).
Tabs:
- Environments (default) — environment cards with inline host lists
- Credentials — credential management table with CRUD operations (see Credentials Tab)
CRUD operations:
- Edit Client — opens a dialog with name, slug, and metadata fields (slug auto-generated from name unless manually edited)
- Delete Client — confirmation dialog, redirects to clients list on success
- Add Environment — dialog with name and slug fields
- Edit/Delete Environment — icon buttons on each environment card
Credentials Tab
The Credentials tab displays a sortable table of all credentials scoped to the client. Columns: Name, Type (badge), Environment ("All" for client-wide), Revision, Updated. Each row has an Actions dropdown (Edit, Delete) gated by credentials:write and credentials:delete permissions. A Create Credential button opens the CredentialFormDialog.
CredentialFormDialog (create and edit modes):
- Name — required text input
- Type selector — dropdown populated from
GET /credentials/typesregistry. Disabled in edit mode (type is immutable). Supports: postgresql, redis, nginx, docker, generic - Provider — always "local" (Vault Transit), disabled
- Environment selector — optional scoping to a specific environment ("All environments" default)
- Dynamic fields — rendered based on the selected type's field definitions. Required fields marked with
*. Secret fields use password inputs with show/hide toggle. Each field has a tooltip when the type definition includes help text. In edit mode, existing secret values are not shown; fields display "(currently set)" and only need to be filled to update - Generic type — key-value pair editor with Add/Remove buttons. All values treated as secrets
- Security warnings — real-time regex-based warnings for privileged users in DSN fields (postgres, root, admin patterns). Amber alert banners with specific remediation messages
- Permissions hint — type-specific guidance below the type selector (e.g., "Recommended: pg_monitor role" for PostgreSQL)
- Auto-test (edit mode only) — debounced 800ms connectivity test fires when all required fields are filled. Shows spinner/success/error status badge
- Docs link — links to the least-privilege credential setup guide
Delete confirmation uses an AlertDialog warning that deletion may affect agent configs referencing the credential.
Environment Detail Page
Route: /clients/:clientId/environments/:envId
Displays a single environment with aggregated metrics across all hosts. Breadcrumbs link back to the parent client.
Sections:
- Header — environment name, slug, and creation date
- Stats row — total host count, online count, offline count
- Tabs:
- Metrics (default) — Aggregate Metrics Panel with the same dashboard layout as host metrics (CPU, memory, disk, network, load) but showing AVG-aggregated values across all hosts in the environment. Includes summary cards, time range selector, and "Averaged across N hosts" subtitle.
- Hosts — table with hostname, IP, OS, CPU/memory/disk capacity and live utilization percentages, status badge, and last seen time. Each row links to the host detail page. Utilization data fetched via
useBatchHostLatestMetrics(calls/hosts/metrics/latest). - Changes — recent change events for all hosts in the environment (permission-gated via
changes:read) - Agent Config — environment-level agent configuration panel for managing base configs across all hosts in the environment (see Agent Config UI)
Uses four queries: getEnvironment (environment detail), getClient (breadcrumb name), getEnvironmentHosts (host list with 30-second polling), and useBatchHostLatestMetrics (per-host utilization for online hosts).
Host Inventory Page
A feature-rich inventory view displaying all hosts across all clients and environments. The page supports two view modes (list and card), search, sorting, column management, host pinning, and inline display of agent labels and user tags. All preferences are persisted in localStorage via useHostInventoryPreferences.
Search is handled by the GlobalSearchBar in the page header (simple mode by default). Typing filters hosts inline by hostname, IP, OS, agent labels, and user tags. The search term is stored in the URL (?search=) so browser back/forward preserves the filter. Toggle to PQL mode for advanced infrastructure queries.
Toolbar: The HostInventoryToolbar composes display controls in a responsive layout:
- Client/Environment filters — dropdowns narrowing by client and environment, persisted in URL search params (
?client=uuid&env=uuid) - Tabs — "All Hosts" and "Pinned" tabs; Pinned tab shows only pinned hosts with an empty state when nothing is pinned
- Labels/Tags toggles — expand/collapse inline badge display for agent labels and user tags
- Column Manager — popover for showing/hiding table columns (Hostname always visible, preferences persisted)
- View Toggle — switch between list (table) and card (responsive grid) views
- Sort Control — popover for selecting sort field and direction
List view columns show combined capacity and utilization:
| Column | Example | Description |
|---|---|---|
| CPU | 4 (72.5%) | Core count + colored utilization percentage |
| Memory | 8.0 GB (55.0%) | Total memory + colored utilization percentage |
| Disk | 100.0 GB (80.2%) | Total disk + colored utilization percentage |
Utilization percentages are color-coded: green (below 70%), amber (70–89%), red (90%+). Sortable column headers use arrow icons. Pinned hosts sort to top with a visual separator. When labels/tags are expanded, a secondary row renders below each host with muted gray badges (labels) and primary-colored badges (tags), both with "+N more" truncation.
Card view renders a responsive grid (4/2/1 columns at xl/sm/mobile breakpoints). Each HostCard shows hostname link, status badge, IP, OS, color-coded metric badges, and optionally label/tag badges. Pinned and unpinned sections are separated by a divider.
Metrics are fetched in a single batch request at the page level using useBatchHostLatestMetrics, which calls GET /api/v1/hosts/metrics/latest with all online host IDs. Tags are fetched via useBatchHostTags (gated by tag expansion toggle). A Last Updated indicator shows how recently data was refreshed.
Host Detail Page
Displays detailed system information for a single host with full breadcrumbs (Clients > ClientName > EnvName > Hostname, all clickable) and a host actions dropdown (Activate/Deactivate, Revoke Agent, Delete Host with confirmation dialogs). On mobile screens, the sidebar collapses to a grouped <Select> dropdown for better usability.
Sidebar Navigation: The host detail page uses a grouped left sidebar (replacing the earlier flat tab bar) with 6 section groups:
| Group | Sections |
|---|---|
| Overview | System |
| Inventory | Services, Packages, Open Ports, Network, Users, Hardware, GPU |
| Monitoring | Metrics, Processes, Containers, Integrations |
| Security | Overview, Scheduled Tasks |
| Changes | Changes (permission-gated), Logs (permission-gated) |
| Config | Agent |
The sidebar component (HostDetailSidebar) filters items by permission — groups with no visible items are hidden entirely.
Content sections:
| Section | Content |
|---|---|
| System (default) | System information panel with virtualization, OS, kernel details. Includes an Agent Config Status card showing sync state (In Sync/Out of Sync/Unknown), desired config version, active collectors, override count, and a "Manage Config" button that navigates to the Agent Config section. |
| Services | All systemd services with real statuses (running, exited, stopped, failed, starting, stopping). Services are sorted with unhealthy first. The "Unhealthy" filter excludes running, active, and exited services. Status badges are color-coded: green (running), muted (stopped/exited), red (failed), amber (starting/stopping). |
| Metrics | Interactive time-series dashboard for system metrics (retry button on chart errors) |
| Containers | Docker containers with name, image, state badges, status, port mappings, and container IDs. "Running Only" toggle filter and "Filter Similar" action by image. |
| Processes | htop-like process table with sortable columns (CPU%, MEM%, RSS, THR, PID) defaulting to CPU descending, color-coded usage bars (green/blue/amber/red by utilization), 30-second auto-refresh with live indicator, process name with command subtitle, user, state badge. Server-side pagination (50 per page) and client-side search by name, command, user, or PID. |
| Integrations | Per-host collector status with ok/error/unknown badges and version badges |
| Packages | Installed packages with versions |
| Ports | Open/listening ports |
| Network | Network adapters with IP addresses |
| Users | System users and their properties |
| Changes | Watched files table (path, type, SHA-256, sensitive badge, last seen) and recent change events list with severity and event type badges |
| Hardware | CPU, disk, and filesystem details |
| Security | Sub-tabbed panel with 3 tabs: Firewall (rules with search, target filters, pagination), Certificates (sorted by expiry with color-coded expiry badges), Security Config (grouped key-value settings). Tabs with 0 items are hidden; defaults to the first tab with data. |
| Agent Config | Host-level agent configuration panel (see Agent Config UI) |
All inventory sections (Services, Packages, Ports, Network, Users, Processes) share common UX patterns: debounced search (300ms via useDebounce), pagination, and distinct empty states — "No X discovered"/"No process data available" when no data exists vs. "No X match your filters"/"No processes match your search" when filters produce no results.
System Information panel: Virtualization type and role are displayed inline in the main grid alongside OS, kernel, architecture, etc. Cloud provider/region/zone and labels are shown as separate subsections when present.
Metrics Dashboard
The Metrics tab provides a Datadog/Grafana-inspired dashboard with all metrics visible at once in a scrollable view.
Layout:
- Summary stat cards — CPU, Memory, Disk, Load Average with sparkline backgrounds
- Area charts — CPU usage, memory usage, disk usage with gradient fills
- Network charts — Side-by-side Sent/Received in 2-column grid
- Load averages — Overlaid 1m/5m/15m as multi-line chart
- Grouped sections — CPU, Memory, Disk, Network, Load with section titles
Dashboard Context: Host, integration, and aggregate metric panels all wrap their content with a DashboardProvider (Inner/Outer component pattern) that provides two shared React contexts:
CrosshairContext— Hovering on one chart shows a dashed reference line on all sibling charts at the same timestamp. Uses a 60ms throttle (Grafana EventBus pattern). Each chart publishes hover viauseCrosshair(), other charts render aReferenceLine.DashboardTimeRangeContext— Manages the time range as string expressions (from/to— e.g."now-3h"/"now"for relative, ISO strings for absolute/zoomed). ComputesrangeHours,startISO/endISO, andisZoomed. ProvidessetTimeRange,shiftRange,zoomTo,resetZoom. Persisted to URL search params (?from=now-1h&to=now) for shareable dashboard links.
Time Range Picker: A Grafana-style TimeRangePicker popover with searchable quick presets (Last 15 minutes through Last 30 days, Today, Yesterday), absolute datetime-local inputs for custom ranges, and recently used ranges (localStorage). Shift arrows move the window by half its duration. A shared MetricsToolbar component renders the picker, title, loading spinner, and refresh button consistently across all dashboard panels.
Drag-to-zoom: Click and drag on any chart to zoom into a specific time range. A ReferenceArea overlay provides visual feedback during the drag. The zoom is committed to the shared context, so all charts zoom together. A "Reset Zoom" button returns to the relative range. Auto-refresh is disabled during zoom to prevent reset.
Series toggle: Click a legend item to isolate (solo) that series. Ctrl/Meta/Shift+click toggles individual series without affecting others. A "Reset View" button restores all series. Toggle state persists in localStorage.
Chart gap detection: When data points are missing (gap > 2x expected interval), lines disconnect instead of drawing across gaps. The fillTimeGaps() utility inserts null placeholders that Recharts renders as line breaks.
Network metric aggregation: Network metrics are fetched without a labels_key parameter, letting the backend aggregate across all network interfaces server-side.
Data fetching: The useMetricDashboard hook fetches all metrics in parallel using React Query, with 60-second auto-refresh (paused during zoom). Network metrics skip the series discovery step (no per-interface breakdown needed). The hook accepts startISO/endISO from the dashboard context for zoom support.
Integration Metrics Dashboard
The Integration Detail Page includes a Metrics tab with a dedicated dashboard for integration-specific metrics (e.g., PostgreSQL, Nginx). The dashboard is driven by per-integration config objects in integration-metrics.ts.
Layout:
- Summary cards — Key metrics for the integration (e.g., active connections, cache hit ratio, deadlocks, replication lag)
- Chart groups — Metrics organized by category with configurable chart types (area, line), layout (full/half width), and overlay mode
- Collector picker — When multiple collectors of the same type exist on a host, a dropdown lets the operator switch between instances
Collector label filtering: Integration metrics are filtered by the collector=<name> label prefix. The useIntegrationMetricDashboard hook discovers series with matching labels and fetches data for each. A fallback path handles pre-label-injection metrics (empty labels key).
Per-metric overrides: Individual metrics within a group can override the group's derivative and chartType settings. For example, the "Deadlocks & Replication" group renders deadlocks as a rate-of-change line chart and replication lag as an area chart.
Tooltip formatting: Byte-valued metrics (detected by metric name containing "bytes") automatically format as KB/MB/GB in chart tooltips across all chart types (single-series, multi-series, and overlay).
Integration Config Tab
The Integration Detail Page also includes a Config tab that displays runtime configuration parameters collected by the agent (e.g., PostgreSQL pg_settings, Nginx nginx -T). The tab shares the same host/collector picker as the Metrics tab.
Config table columns:
| Column | Description |
|---|---|
| Parameter | Configuration parameter name (monospace font) |
| Value | Current value — rendered with type-specific formatting for known Nginx config keys (see below), monospace for all others |
| Unit | Unit of measurement (e.g., 8kB, kB) or - if unitless |
| Source | Where the value was set (displayed as a badge, e.g., configuration file, default) |
| Category | Settings category |
Parameters are sorted alphabetically. When no configuration data has been collected yet, an empty state message is shown with a note that config is refreshed every 5 minutes.
Category sub-tabs: When multiple categories exist (e.g., Docker config with "Daemon", "Images", "Volumes"), the table renders sub-tabs using Radix Tabs. Common prefixes are stripped from tab labels (e.g., "Docker / Daemon" becomes "Daemon"). The Category column is hidden when sub-tabs are active since category is implicit from the selected tab.
Structured rendering for Nginx config:
Known Nginx config keys are rendered with rich formatting instead of plain comma-separated strings:
| Key | Rendering |
|---|---|
server_names | Individual outline badges for each domain name |
upstreams | Arrow-separated pairs (server → upstream), one per line |
upstream_backends | Grouped display — upstream name as a header with backend servers listed below |
ssl_certificates | Structured cards with server name, cert path, and a color-coded expiry badge (red = expired, amber = ≤30 days, green = >30 days) |
ssl_cert_earliest_expiry | Standalone color-coded expiry badge |
All other config keys render with the existing monospace behavior.
Change Detection Pages
The change detection UI provides visibility into file integrity monitoring events and external change events (from webhook sources) across the infrastructure.
Changes Page
Route: /changes
A global timeline of all change events across all hosts. Features server-side pagination via URL search params and a filter toolbar with three select dropdowns:
- Severity — Filter by
critical,warning, orinfo - Event Type — Filter by
file_created,file_modified,file_deleted,file_metadata_changed,push,merge_request,tag,release,pipeline_completed,pipeline_failed,workflow_completed,workflow_failed,sync,sync_failed,health_degraded - Source — Filter by event source (
agent,gitlab,github,deployment)
Each event row shows a severity badge, event type badge, summary text, host link, source, and relative timestamp. Clicking a row navigates to the change detail page. Data is polled every 30 seconds.
Change Detail Page
Route: /changes/:changeId
Displays a single change event with:
- Header — Summary text, severity badge, event type badge, timestamp
- Event Metadata card — Source, event type, severity, created timestamp, with links to host, client, and environment when available
- Details card — JSONB details rendered as key-value pairs (excluding
diff_text) - Diff viewer — When the event has a
diff_textin its details, renders a unified diff with red/green line coloring and line numbers
File Versions Page
Route: /hosts/:hostId/files/:fileId
Displays the version history for a single watched file:
- File metadata header — File path, file type badge, sensitive badge, owner, mode
- Versions table — Version number (with "latest" badge on newest), SHA-256 hash (truncated), file size, owner:group, mode, created timestamp
- Click-to-diff — Clicking a version row shows its unified diff in a card below the table
Host Changes Tab
The Changes tab on the Host Detail Page has two sections:
- Watched Files — Table of files being monitored on this host (path, type, SHA-256, sensitive badge, last seen). Click a file row to navigate to its File Versions page.
- Recent Changes — List of recent change events for this host using the shared
ChangeEventRowcomponent.
Change Detection Components
| Component | Location | Purpose |
|---|---|---|
SeverityBadge | components/changes/ | Color-coded badge for severity levels (critical=red, warning=amber, info=blue) |
EventTypeBadge | components/changes/ | Descriptive badge for event types (Created, Modified, Deleted, Metadata Changed) |
ChangeEventRow | components/changes/ | Reusable timeline row with severity, event type, summary, host link, and relative time |
DiffViewer | components/changes/ | Renders unified diff text with red/green line coloring, line numbers, and monospace font |
ChangeFilters | components/changes/ | Filter toolbar with severity, event type, and source select dropdowns |
HostChangesTab | components/changes/ | Host detail tab combining watched files table and recent changes list |
MFA Setup Page
Route: /setup-mfa
Users who log in without MFA enabled are automatically redirected here by ProtectedRoute. The page implements a two-step mandatory MFA enrollment flow:
Step 1 — Verify:
- Calls
authMFASetup()on mount to generate a TOTP secret - Displays a QR code (
QRCodeSVGfromqrcode.react, 200×200) for authenticator app scanning - Shows the secret key for manual entry
- 6-digit TOTP code input with pattern validation
- "Verify & Enable" button (disabled until code is 6 digits) → calls
authMFAConfirm(code)
Step 2 — Recovery Codes:
- Displays 8 recovery codes in a 2×4 monospace grid
- "Copy codes" button (clipboard API)
- "I have saved these recovery codes" checkbox
- "Continue" button (disabled until checkbox checked) → calls
refreshUser()→ navigates to/clients
Recovery codes come from the setup response (not the confirm response). The confirm endpoint returns { message: string } only.
Global Search Bar
The AppHeader includes a GlobalSearchBar component between breadcrumbs and the user menu (hidden on mobile screens and on the /search page to avoid duplicate bars). It supports two modes, switchable via a toggle button:
Simple mode (default):
- Placeholder — "Search hosts by name, IP, OS... Ctrl+K"
- On the Hosts page — Filters hosts inline as you type (live keystroke callbacks via
onSimpleSearch). Search state is stored in the URL (?search=) so browser back/forward preserves the filter. - On other pages — Pressing Enter navigates to
/hosts?search={query} - No autocomplete or PQL validation — Keeps the input fast and distraction-free
Advanced mode (PQL):
- Placeholder — "Find anything... Ctrl+K"
- Real-time PQL validation — Debounced (300ms) calls to
POST /api/v1/search/validatedisplay a visual error indicator when the query has syntax errors - Autocomplete — Context-aware suggestions for fields, operators, values, keywords, and saved searches
- Navigation — Pressing Enter navigates to
/search?q={encoded_query} - PQL help popover — Quick reference for operators, examples, and field prefixes
Shared features (both modes):
- Keyboard shortcut —
Ctrl+K(orCmd+Kon macOS) focuses the search input from anywhere on the page - Toggle button — Shows "PQL" in simple mode / "Simple" in advanced mode. Mode preference is persisted in localStorage via
useSearchModehook.
Search Results Page
Route: /search?q={pql_query}&page=1
Displays PQL search results with paginated host cards. The page reads the q parameter from URL search params and fetches results via searchHosts().
Layout:
- PQL input — Monospace text input with search icon and submit button, pre-filled from the URL
qparameter - Results count — e.g. "42 hosts found"
- Host cards —
SearchHostCardcomponents showing hostname (link to host detail), status badge, IP address, OS, architecture, agent version, and tag badges (truncated at 5 with "+N more") - Pagination — Previous/Next buttons with page counter
- Save search — Button in the header opens
SaveSearchDialogto save the current query - Empty state — When no query, shows PQL syntax help with example queries
Save Search Dialog
The SaveSearchDialog is a modal form for saving PQL queries:
| Field | Required | Description |
|---|---|---|
| Name | Yes | Display name for the saved search |
| Query | — | Read-only display of the PQL query being saved |
| Description | No | Optional description of what the search finds |
| Share with team | No | Toggle to make the search visible to all users |
On save, the dialog validates the query via createSavedSearch() and invalidates the saved searches React Query cache.
Admin Pages
The frontend includes a full admin UI for managing users, teams, roles, service accounts, and viewing audit logs. All admin pages are under the /admin/* route prefix and require appropriate RBAC permissions.
Users Page
Route: /admin/users (requires users:read)
Table layout displaying all users with columns: Name (linked to detail page), Email, Status (badge: active/invited/disabled), MFA (badge: enabled/disabled), Auth Provider, Last Login, and an Actions dropdown menu.
Actions dropdown:
- Edit — opens
UserFormDialogin edit mode - Resend Invite — visible only for
invitedstatus users - Reset Password — visible only for
activestatus users - Enable/Disable — toggles user status
"Invite User" button opens UserFormDialog in create mode (email, display name, super admin toggle).
User Detail Page
Route: /admin/users/:userId (requires users:read)
Displays user information card (email, status, auth provider, MFA status, last login, created at) with Edit/Disable action buttons. Below the info card:
- Client Assignments table — shows directly assigned clients with role, plus "Assign Client" dialog (
UserClientDialog) with client and role pickers - Team Memberships table — read-only display of teams the user belongs to with their role in each team
Roles Page
Route: /admin/roles (requires roles:read)
Card grid displaying roles with name, description, permission count, and a system badge for built-in roles. System roles show a lock icon and cannot be edited or deleted. "Create Role" button opens RoleFormDialog with an interactive permission matrix (resource×action grid). A "Compare" toggle enables selecting 2-3 roles for side-by-side comparison with diff highlighting. See Roles & Permissions UX for full details.
Role Detail Page
Route: /admin/roles/:roleId (requires roles:read)
Displays role information with a read-only permission matrix (resource×action grid) and a "Used By" section showing which users and teams are assigned this role, with source indicators (direct vs team-inherited). Edit/Delete buttons are hidden for system roles. See Roles & Permissions UX for full details.
Teams Page
Route: /admin/teams (requires teams:read)
Card grid with team name, description, member count, and client count. "Create Team" button opens TeamFormDialog (name, description).
Team Detail Page
Route: /admin/teams/:teamId (requires teams:read)
Two sections:
- Members table — user name/email, role, and actions (change role, remove). "Add Member" button opens
TeamMemberDialogwith user and role pickers (role dropdown shows permission preview on hover). - Clients table — client name with remove action. "Add Client" button opens
TeamClientDialogwith client picker.
Access Audit Page
Route: /admin/access-audit (requires roles:read)
Two-tab page for reviewing who has access to what. "By User" tab: select a user to see all their client access with role, source (direct/team), and expandable permission details. "By Client" tab: select a client to see all users with access. See Roles & Permissions UX for full details.
Service Accounts Page
Route: /admin/service-accounts (requires users:read)
Table with columns: Name, API Key Prefix (monospace), Role, Status (active/inactive badge), Last Used, Expires, and Actions (activate/deactivate, delete). Activate/deactivate sends the full required fields (name, role_id, is_active) to the backend PUT endpoint.
Critical UX: When creating a service account, the expiration date is required (calendar date picker, max 1 year from today). The API key is shown exactly once in an ApiKeyRevealDialog with a copy button. The dialog warns that the key cannot be retrieved again.
Credentials Page
Route: /admin/credentials (requires credentials:read)
Cross-client credential management table with columns: Name, Type (badge), Client, Environment, Provider, Last Rotated, Revision, and Actions (edit, delete). Supports text search filtering across name, type, and client fields. "Create Credential" button opens CredentialFormDialog in create mode (see Credentials Tab for dialog details).
Unlike the client-scoped Credentials Tab, this page shows credentials across all clients the user has access to, making it useful for administrators managing credentials at scale.
Webhook Sources Page
Route: /admin/webhook-sources (requires webhooks:read)
Table with columns: Name, Source Type (color-coded badge: purple GitLab, slate GitHub, orange ArgoCD), Environment, Status (enabled/disabled badge), Created, and Actions dropdown (edit, rotate token, delete). Supports text search filtering across name and source type.
Key actions:
- Create opens
WebhookSourceFormDialogwith fields for name, source type, client, environment, secret, and host mapping (JSON textarea) - Rotate Token generates a new token and displays it in
WebhookTokenDisplay(one-time display with copy button and source-specific setup instructions) - Delete requires confirmation
After creation, WebhookTokenDisplay shows the webhook URL and plaintext token with a warning that the token cannot be retrieved again.
Audit Log Page
Route: /admin/audit-log (requires audit:read)
Read-only filterable table with columns: Timestamp, Actor (email), Action, Resource Type, Resource ID, IP Address. Rows are expandable to show the full details JSON payload.
Filters:
- Actor email (text input)
- Action type (select)
- Resource type (select)
- Date range (from/to date inputs)
Profile & Security Page
Route: /profile (no admin permission required — any authenticated user)
Three sections:
- Change Password — form with current password, new password, and confirm new password fields
- Two-Factor Authentication — shows MFA status (enabled/disabled badge). When enabled, provides a "Disable MFA" form requiring the current account password for confirmation. When disabled, notes that MFA will be required on next login.
- Active Sessions — table showing device label (with user agent fallback), IP address, last active time, and expiry. The current session is identified via
session_idstored in localStorage and displays a green "Current" badge. Revoking the current session or using "Revoke All" triggers a confirmation dialog warning that the user will be signed out, then callslogout()automatically. Non-current sessions can be revoked without confirmation.
Admin Dialog Components
| Component | Purpose |
|---|---|
UserFormDialog | Create/edit user (email, display name, super admin toggle) |
UserClientDialog | Assign user to client with role picker |
RoleFormDialog | Create/edit role with grouped permission checkboxes |
TeamFormDialog | Create/edit team (name, description) |
TeamMemberDialog | Add team member (user picker + role picker) |
TeamClientDialog | Add client to team (client picker) |
ServiceAccountFormDialog | Create service account (name, description, role, calendar date picker for required expiry — max 1 year) |
ApiKeyRevealDialog | One-time API key display after service account creation |
WebhookSourceFormDialog | Create/edit webhook source (name, type, client, environment, secret, host mapping JSON) |
WebhookTokenDisplay | One-time webhook URL + token display with copy buttons and source-specific setup instructions |
AgentStatusBadge Component
A reusable badge component that renders the agent's connection status with color-coded indicators. Status classes are provided by the centralized getStatusClasses() function from design tokens:
- Online — Agent is reporting normally
- Offline — Agent has not reported within the expected window (set to offline by the stale agent detector)
- Degraded — Agent is reporting but with issues
- Unknown — Status cannot be determined
Agents / Fleet Page
Route: /agents (requires agents:read)
Fleet management view for installed agents: version distribution, online/offline status, and self-update rollouts. Supports canary rollouts of new agent versions with pause/resume/abort controls. Backend→agent commands are keyed by agent_id.
AI Chat
Route: /chat (requires chat:read and the ai_chat feature flag)
Conversational interface backed by the MCP server. Assistant responses stream over Server-Sent Events (use-chat-stream.ts), not WebSocket. Chat always uses the platform LLM key (per-client BYOK keys are not yet implemented).
Web Terminal & Session Recordings
- Full-page terminal — Route
/hosts/:hostId/terminal(HostTerminalPage). An interactive shell rendered with xterm.js, connected over/api/v1/terminal/ws— the single WebSocket exception. ThepcCLI offers the same access from the command line, including non-interactive exec. - Join shared session — Route
/terminal/join/:sessionId(JoinSessionPage) connects a second viewer to a live session via/terminal/ws/join. - Recording playback — Route
/recordings/:sessionId(RecordingPlaybackPage) replays a recorded terminal session. Sessions are also auditable from the admin Terminal Sessions page (/admin/terminal-sessions).
Runbooks
- Runbooks list —
/runbooks(requiresrunbooks:readand therunbooksfeature flag) - Runbook editor —
/runbooks/newand/runbooks/:runbookId(RunbookEditorPage) - Execution detail —
/runbooks/executions/:executionId(RunbookExecutionDetailPage) shows a step-by-step execution timeline.
Alerts
- Alerts page —
/alerts(requiresalerts:readand thealertsfeature flag). A firing-alert count badge in the sidebar polls every 30s. - Alert detail —
/alerts/:alertGroupId(AlertDetailPage). - Alert configuration lives under admin at
/admin/alert-config(alertsources:read).
Logs
Route: /logs (requires logs:read and the logs feature flag)
Centralized log search across the fleet (backed by VictoriaLogs), including agent self-shipped logs.
Databases & Clusters
- Clusters —
/clustersand/clusters/:clusterId(requiresassets:read) - Databases —
/databases,/databases/:databaseId, and/databases/clusters/:clusterId(requiresassets:read)
Inventory and detail views for database clusters and individual databases discovered across client environments.
Service Desk
Routes under /service-desk (requires servicedesk:read and the service_desk feature flag): Home (/service-desk), Requests (/service-desk/requests), a Kanban board (/service-desk/kanban), request creation (/service-desk/new), ticket detail (/service-desk/tickets/:issueKey), Projects (/service-desk/projects), and Metrics (/service-desk/metrics). Org/project mappings are configured under admin (/admin/service-desk).
Briefing Page
Route: / (the authenticated index/landing page)
A daily operational briefing summarizing fleet health, recent changes, and notable events across accessible clients.
Custom Hooks
useHostLatestMetrics
Fetches the most recent utilization values (CPU, memory, disk) for a single host by making 3 parallel per-metric API calls. Used on the host detail page. Accepts an enabled parameter — when false (e.g., agent is offline), the query is skipped entirely to avoid unnecessary API calls.
useBatchHostLatestMetrics
Fetches the most recent utilization values (CPU, memory, disk) for multiple hosts in a single API call to GET /api/v1/hosts/metrics/latest. Used on the hosts list page to avoid N+1 queries — returns a Record<hostId, { cpu, memory, disk }> map. The query key is stabilized with sorted host IDs to prevent unnecessary refetches when array order changes.
useMetricDashboard
Fetches all dashboard metrics for a host in parallel. Discovers available metric names, then fetches data for each. Network metrics skip series discovery and fetch aggregated data directly. Uses React Query with 60-second refetch interval. Accepts an options parameter with startISO, endISO, and refetchInterval to integrate with DashboardContext zoom (absolute range overrides relative rangeHours, polling paused during zoom).
useAggregateMetricDashboard
Fetches aggregated metrics for an environment or client scope. Mirrors useMetricDashboard but routes API calls to the aggregate endpoints (/environments/:id/metrics or /clients/:id/metrics). Accepts an AggregateMetricScope discriminated union that selects the correct API functions at runtime. Uses React Query with 60-second refetch interval. Accepts an options parameter with startISO, endISO, and refetchInterval to integrate with DashboardContext zoom.
useIntegrationMetricDashboard
Fetches integration-specific metrics for a host, filtered by collector label. Discovers series with the collector=<name> prefix, fetches data for each metric in the integration config, and supports per-metric derivative calculation. Falls back to empty labels key when no collector-labeled series exist (backward compatibility). Uses React Query with 60-second refetch interval. Accepts an options parameter with startISO, endISO, and refetchInterval to integrate with DashboardContext zoom.
useSearchMode
Persists the user's search mode preference (simple vs PQL) in localStorage (proxima:search-mode). Returns { mode, setMode, toggle }. Used by GlobalSearchBar to remember the last-used mode across sessions.
useSearchSuggestions
Provides context-aware autocomplete suggestions for the PQL search input. Fetches field names, operators, and values from the backend based on the cursor position in the query. Debounced to avoid excessive API calls during typing.
usePagination
A hook for offset-based table pagination, providing page state, navigation callbacks, and derived values (total pages, can-go-prev/next).
useDebounce
A generic debounce hook that delays updating a value until a specified timeout (default 300ms). Used by all inventory tables to debounce search input, preventing excessive re-filtering on every keystroke.
useNetworkStatus
Detects online/offline network state using useSyncExternalStore with navigator.onLine and online/offline browser events. When the connection is restored, it shows a toast notification and invalidates all React Query caches to trigger a refresh. The AppHeader component uses this hook to display an amber "You are offline" banner.
CRUD Dialog Components
Infrastructure dialogs are located in src/components/crud/, admin dialogs in src/components/admin/:
| Component | Location | Purpose |
|---|---|---|
ClientFormDialog | crud/ | Create/edit clients with name, slug (auto-generated via toSlug), and optional JSON metadata fields |
EnvironmentFormDialog | crud/ | Create/edit environments with name and slug fields |
DeleteConfirmDialog | crud/ | Reusable confirmation dialog for destructive actions (AlertDialog-based) |
UserFormDialog | admin/ | Create/edit user (email, display name, super admin toggle) |
UserClientDialog | admin/ | Assign user to client with role picker |
RoleFormDialog | admin/ | Create/edit role with grouped permission checkboxes |
TeamFormDialog | admin/ | Create/edit team (name, description) |
TeamMemberDialog | admin/ | Add team member (user picker + role picker) |
TeamClientDialog | admin/ | Add client to team (client picker) |
ServiceAccountFormDialog | admin/ | Create service account (name, description, role, calendar date picker for required expiry — max 1 year) |
ApiKeyRevealDialog | admin/ | One-time API key display after service account creation |
Auto-slug generation: When creating a new entity, the slug field is auto-populated from the name using the toSlug utility (lowercase, strip special characters, spaces to hyphens). Once the user manually edits the slug, auto-generation stops. In edit mode, auto-generation is disabled by default.
Slug validation: Both ClientFormDialog and EnvironmentFormDialog validate slugs inline with the pattern /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/. Invalid slugs show an inline error message with aria-invalid and aria-describedby for accessibility. The submit button is disabled until the slug is valid.
Mutation feedback: All mutations use React Query useMutation with automatic cache invalidation on success. Contextual toast notifications (via sonner) include entity names (e.g., Client "Acme Corp" deleted instead of generic Client deleted). Post-delete operations navigate back to the parent list page.
Common Components
| Component | Purpose |
|---|---|
ErrorBoundary | React error boundary that catches uncaught render errors and displays a fallback UI |
ErrorAlert | Reusable error display with message and optional retry button |
PageTitle | Consistent page header with title, description, and optional action slot |
TablePagination | Prev/next pagination controls with page indicator (<nav> landmark for a11y) |
NotFoundPage | 404 page with link back to clients |
LastUpdated | Shows relative time since last data refresh (e.g., "Updated 15s ago"), re-renders every 10s |
ResponsiveTable | Table wrapper with overflow-x-auto, right-edge fade gradient when content overflows, and role="region" for a11y |
UtilizationDisplay | Shared components for host utilization rendering: HostUtilizationCell, UtilizationValue, UtilizationLegend, and getUtilizationColor — used by both HostsPage and EnvironmentDetailPage |
Host Inventory Components
The HostsPage serves as an orchestrator, delegating rendering to specialized components in src/components/inventory/:
| Component | Purpose |
|---|---|
HostListView | Enhanced table with sortable column headers, pin icons, and expandable label/tag rows |
HostCardView | Responsive card grid (1/2/4 columns) with pinned/unpinned divider |
HostCard | Individual host card with metric badges, status, labels, and tags |
HostInventoryToolbar | Composes search, filters, tabs, and display controls |
HostSystemInfo | System info panel with labels, tags, cloud metadata |
ViewToggle | List/Card icon toggle with active/inactive states |
ColumnManager | Popover for showing/hiding table columns (Hostname always visible) |
SortControl | Popover for selecting sort field and direction |
HostLabelBadges | Muted gray badges for agent labels with "+N more" truncation |
HostTagBadges | Primary-colored badges for user tags with "+N more" truncation |
useHostInventoryPreferences
Manages all localStorage-persisted preferences (view mode, visible columns, pinned hosts, sort field/direction, label/tag expansion) via a single proxima:host-inventory-prefs key. Provides individual setters and toggle functions.
useBatchHostTags
Fetches tags for multiple hosts in parallel using useQueries. Gated by enabled prop (only fetches when tags are expanded). Shares React Query cache keys with HostTagEditor on the host detail page.
Utilities
| Utility | Module | Purpose |
|---|---|---|
formatBytes | lib/format | Converts byte values to human-readable sizes (KB, MB, GB) |
formatDate | lib/format | Formats timestamps to locale-appropriate date strings |
formatDateTime | lib/format | Formats timestamps to locale-appropriate date + time strings |
formatRelativeTime | lib/format | Converts timestamps to relative descriptions (e.g., "5m ago") |
formatChartTime | lib/format | Formats timestamps for chart X-axis labels |
fillTimeGaps | lib/metrics-utils | Inserts null placeholders for chart gap detection |
mergeTimeSeriesByTimestamp | lib/metrics-utils | Merges multiple series into a single dataset for multi-line charts |
toSlug | lib/utils | Converts a name to a URL-safe slug (lowercase, hyphens, no special chars) |
queryKeys | lib/query-keys | Factory for type-safe React Query cache keys |
POLLING | lib/design-tokens | Centralized polling interval constants |
THROTTLE | lib/design-tokens | Centralized throttle constants (e.g., crosshair sync) |
getStatusClasses | lib/design-tokens | Returns Tailwind classes for agent status colors |
getSeverityClasses | lib/design-tokens | Returns Tailwind classes for change event severity colors (critical/warning/info) |
SEVERITY_COLORS | lib/design-tokens | Severity color tokens mapping (text, bg, border classes) |
buildOverlayData | components/monitoring/overlay-utils | Builds overlay chart data from metric groups — shared across host and aggregate metric panels |
parseTimeExpression | lib/time-range | Parses Grafana-style expressions (now-3h, now/d, now-1d/d) into Date objects |
resolveTimeRange | lib/time-range | Resolves a (from, to) expression pair into { start, end } Date objects |
formatTimeRangeLabel | lib/time-range | Formats time range for display (e.g. "Last 3 hours", "Feb 20 to Feb 22") |
computeRangeHours | lib/time-range | Maps resolved duration to nearest TimeRangeHours bucket (1/6/24/168) |
Agent Config UI
The Agent Config UI provides management interfaces for the 8 agent configuration types (collectors, intervals, labels, healthcheck, change_detection, log_collection, agent_settings, watch_files) at both environment and host levels.
Shared Components (components/agent-config/):
- ConfigSection — collapsible section with source badge (inherited/host override/merged/not configured/configured), Override and Remove Override buttons for host-level editing
- ConfigJsonEditor — raw JSON textarea with live validation, Prettify button, and Save
- SyncBanner — config version sync status indicator (In Sync/Out of Sync/Unknown) comparing heartbeat-reported version against desired version
- AgentConfigStatusCard — compact card for the host overview showing sync status, active collectors, override count, and "Manage Config" link
Section Form Components (components/agent-config/sections/): Each config type has a typed form component with appropriate field controls — duration inputs for intervals, toggle + path lists for change detection, key-value editors for labels, card grid with inline expand for collectors (type-specific fields per collector type), file source lists for log collection, etc. All sections share a common prop interface (data, onChange, onSave, readOnly, isPending) and support a form/JSON toggle.
Environment Panel (EnvironmentAgentConfigPanel): Renders all 8 config sections as directly editable forms. No sync banner or inheritance indicators — this is the base config level.
Host Panel (HostAgentConfigPanel): Renders effective (merged) config with per-section source indicators. Inherited configs are read-only; users can click "Override" to create a host-level copy, then edit and save. "Remove Override" reverts to the inherited environment config. Includes a sync banner comparing the agent's heartbeat config version against the desired version.
Responsive Design
The frontend adapts to mobile and desktop viewports using the useIsMobile() hook (768px breakpoint):
- Tables — Wrapped in
ResponsiveTablefor horizontal scroll with a fade gradient edge indicator - Host Detail Sidebar — HostDetailPage renders a grouped
<Select>dropdown on mobile instead of the left sidebar navigation - Charts — Metric charts use 200px height on mobile, 250px on desktop
- Metric controls — Time range picker and refresh button in a shared
MetricsToolbarcomponent
Accessibility
- ARIA labels — All icon-only buttons have
aria-labelattributes (e.g., "Edit environment", "Delete host") - Form validation — Invalid inputs use
aria-invalidandaria-describedbypointing to error messages - Navigation landmarks —
TablePaginationuses<nav aria-label="Table pagination">;ResponsiveTableusesrole="region"with a label - Offline banner — Uses
role="alert"for screen reader announcement - Empty states — Inventory tables distinguish between "no data" and "no filter results" with a clear action button
TypeScript Types
Frontend TypeScript types are auto-generated from the backend swagger spec, ensuring compile-time safety and keeping types in sync with Go structs automatically.
Generation Pipeline
swagger annotations → make swagger → swagger.json (Swagger 2.0)
→ swagger2openapi → OpenAPI 3.0 → openapi-typescript → generated.ts
→ adapter files re-export with clean names
Run make swagger && make generate-types after changing swagger annotations. A CI job (lint:types) verifies generated types are fresh on every MR.
Type Files
| File | Description |
|---|---|
types/generated.ts | Auto-generated from the swagger spec (tens of thousands of lines covering all API schemas). Do not edit. |
types/api.ts | Adapter: domain models (Host, Service, etc.), response wrappers, request types |
types/auth.ts | Adapter: AuthUser, LoginResponse, MFASetupResponse, AuthSession, etc. |
types/admin.ts | Adapter: AdminUser, Role, Team, ServiceAccount, audit log types |
types/search.ts | Adapter: SavedSearch, SearchFieldInfo, SearchValidation, etc. |
types/metrics.ts | Frontend-only MetricSeries type |
types/briefing.ts | Adapter: morning/ops briefing types |
types/chat.ts | Adapter: AI chat message and session types |
types/feedback.ts | Adapter: user feedback types |
types/fleet.ts | Adapter: agent fleet self-update types |
types/kubernetes.ts | Adapter: Kubernetes cluster and workload types |
How Adapters Work
The generated types have three issues that adapters fix:
- All fields optional — Swagger spec lacks
requiredarrays, so adapters useRequired<>to make all response fields non-optional. - Ugly names — Generated names like
components["schemas"]["github_com_..."]are re-exported as clean names (Client,Host, etc.). - No nullability — Go pointer fields (
*string) serialize asnull, but Swagger 2.0 has nonullablekeyword. Adapters use aNullable<T, K>helper to mark specific fields asT | null.
Types not in the swagger spec (generic wrappers like ApiResponse<T>, frontend-only types like MFAChallenge) stay hand-written in the adapter files. Request types with mixed required/optional fields also stay hand-written for correctness.