State Management
The Cloud Frontend splits state into two clear categories:
| Category | Tool | Examples |
|---|---|---|
| Server state (remote data) | TanStack Query v5 | Incidents list, asset details, notifications badge |
| Client/UI state | Zustand | Auth tokens, dashboard layout, WebSocket connection |
Zustand stores
Section titled “Zustand stores”All stores live in Frontend/lib/stores/.
authStore
Section titled “authStore”The primary auth store. Persisted to localStorage (user metadata only — never the access token).
| Key | Type | Notes |
|---|---|---|
user | User | null | Current user object from GET /api/v1/me |
access_token | string | null | JWT access token — in memory only, not persisted |
permissions | string[] | Resolved permission set for the current user |
Methods: setUser(), setAccessToken(), clearAuth(), hasPermission(permission: string)
Because access_token is not persisted, every full page reload triggers SessionRestore to call POST /api/v1/auth/refresh before rendering protected pages.
dashboardStore
Section titled “dashboardStore”Dashboard layout state: widget positions, visible widgets, drag-and-drop grid configuration. Persisted to localStorage and synced to the server via PATCH /api/v1/dashboard/layout.
wsStore
Section titled “wsStore”WebSocket connection state for the security and VPN hubs.
| Key | Type | Notes |
|---|---|---|
securityWs | WebSocket | null | Active /api/ws/security connection |
vpnWs | WebSocket | null | Active /api/ws/vpn connection |
securityStatus | 'connecting' | 'open' | 'closed' | UI connection indicator |
vpnStatus | 'connecting' | 'open' | 'closed' | UI connection indicator |
WS connections are opened when the relevant module page mounts and closed on unmount.
TanStack Query
Section titled “TanStack Query”Configuration
Section titled “Configuration”The QueryClient is configured in Frontend/src/main.tsx with:
staleTime: varies per query (short for notifications, longer for static data like permission catalog)refetchInterval: 60 seconds for notifications badge- Global
retry: 1 (prevents hammering the API on 5xx) - Error handling: 401 responses trigger
authStore.clearAuth()and redirect to/login
API client pattern
Section titled “API client pattern”Each domain has a dedicated Axios client in Frontend/lib/api/<domain>.ts:
// Example pattern (illustrative)export const incidentsApi = { list: (params) => apiClient.get('/incidents', { params }), get: (id) => apiClient.get(`/incidents/${id}`), create: (data) => apiClient.post('/incidents', data), update: (id, data) => apiClient.patch(`/incidents/${id}`, data),};The shared apiClient (Axios instance in lib/config/api.ts) automatically attaches Authorization: Bearer <access_token> from authStore on every request.
401 interceptor
Section titled “401 interceptor”The Axios response interceptor catches 401 responses, attempts POST /api/v1/auth/refresh, and retries the original request with the new token. If the refresh also fails (expired cookie), authStore.clearAuth() is called and the user is redirected to /login.
WebSocket + TanStack Query integration
Section titled “WebSocket + TanStack Query integration”When the security or VPN WebSocket receives a relevant event, it invalidates the corresponding TanStack Query cache key:
WS event { type: "alert" } → queryClient.invalidateQueries(['security', 'alerts']) → TanStack Query refetches alert list → UI updates automaticallyThis avoids manual state management for real-time data — WebSocket acts as a cache invalidation signal, and TanStack Query handles the actual data fetching.