State Management
Zustand stores for client state, TanStack Query for server state
Philosophy
Split by Origin
State is split into two categories with different tools:
| Category | Tool | Examples |
|---|---|---|
| Server state | TanStack Query | Threats, metrics, config from API |
| Client state | Zustand | Sidebar open, theme, filters, selections |
When to Use What
| Question | Tool |
|---|---|
| Does the data come from the API? | TanStack Query |
| Is it UI state (toggles, filters, selections)? | Zustand |
| Does it need caching / refetching? | TanStack Query |
| Is it ephemeral and local? | Zustand (or useState) |
Zustand Stores
Four Zustand stores manage the client-side state:
useMetricsStore
Real-time metrics with auto-ticking for live dashboard updates.
| Field | Type | Description |
|---|---|---|
current | MetricsSnapshot | Latest snapshot (packets, bytes, threats, uptime) |
history | MetricsDataPoint[] | Rolling 60-point history for charts |
Methods: tick() generates a new data point, startTicking() / stopTicking() control the 1-second interval.
useThreatsStore
Threat event tracking with severity counting.
| Field | Type | Description |
|---|---|---|
threats | ThreatEvent[] | Rolling list (max 200 entries) |
severityCounts | Record<Severity, number> | Count per severity level |
Method: addThreat(threat) appends and updates severity counts. Threats come from the live API (GET /api/v1/alerts via TanStack Query) — there is no mock generator.
useCaptureStore
Packet capture state and protocol distribution.
| Field | Type | Description |
|---|---|---|
status | "idle" | "running" | Capture state |
selectedInterface | string | null | Active network interface |
interfaces | NetworkInterface[] | Available interfaces (eth0, wlan0, lo, docker0) |
packetCount / byteCount / droppedCount | number | Running counters |
protocolStats | Record<string, ProtocolStat> | Per-protocol packet/byte counts (TCP, UDP, DNS, HTTP, TLS, ICMP) |
Methods: start() / stop() control capture, setInterface(name) selects an interface.
useConfigStore
Application settings with section-level update methods.
| Section | Fields |
|---|---|
general | autoStart, notifications, darkMode |
capture | maxPacketSize, promiscuousMode, bufferSize |
detection | sensitivityLevel, enabledLayers, autoBlock |
storage | retentionDays, maxStorageGb, exportFormat |
Methods: updateGeneral(partial), updateCapture(partial), updateDetection(partial), updateStorage(partial).
Server State (TanStack Query)
Creating a Hook
Use TanStack Query for all data fetched from the Rust API:
import { useQuery } from "@tanstack/react-query";
export function useThreats() {
return useQuery({
queryKey: ["threats"],
queryFn: () => window.exfilguardian.threats.list(),
});
}Consuming in Components
export function ThreatList() {
const { data, isLoading, error } = useThreats();
if (isLoading) return <Skeleton />;
if (error) return <ErrorMessage error={error} />;
return (
<ul>
{data.map((t) => (
<ThreatCard key={t.id} threat={t} />
))}
</ul>
);
}Custom Hooks
| Hook | Purpose |
|---|---|
useRealtime() | Polls live metrics (GET /api/v1/metrics) on an interval |
useThreats() | Returns threats fetched from the API (GET /api/v1/alerts) |
useCapture() | Returns capture store, cleans up on unmount |
useElectron() | Detects Electron environment, exposes window.exfilguardian |
useAppVersion() | Fetches app version from Electron API |
useWebSocket() | Generic WebSocket wrapper with connect/disconnect/send |
Types
Core TypeScript types in src/lib/types/:
type Severity = "critical" | "high" | "medium" | "low";
type DetectionLayer = "network" | "dns" | "http" | "tls" | "behavioral";
type CaptureStatus = "idle" | "running" | "error";
interface ThreatEvent {
id: string;
timestamp: number;
severity: Severity;
layer: DetectionLayer;
description: string;
sourceIp: string;
destinationIp: string;
protocol: string;
bytes: number;
packets: number;
}
interface MetricsSnapshot {
timestamp: number;
totalPackets: number;
totalBytes: number;
packetsPerSecond: number;
bytesPerSecond: number;
activeThreats: number;
captureUptime: number;
}Store Naming
- File:
<name>-store.ts(e.g.,metrics-store.ts) - Hook:
use<Name>Store(e.g.,useMetricsStore)