ExfilGuardian
Frontend

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:

CategoryToolExamples
Server stateTanStack QueryThreats, metrics, config from API
Client stateZustandSidebar open, theme, filters, selections

When to Use What

QuestionTool
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.

FieldTypeDescription
currentMetricsSnapshotLatest snapshot (packets, bytes, threats, uptime)
historyMetricsDataPoint[]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.

FieldTypeDescription
threatsThreatEvent[]Rolling list (max 200 entries)
severityCountsRecord<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.

FieldTypeDescription
status"idle" | "running"Capture state
selectedInterfacestring | nullActive network interface
interfacesNetworkInterface[]Available interfaces (eth0, wlan0, lo, docker0)
packetCount / byteCount / droppedCountnumberRunning counters
protocolStatsRecord<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.

SectionFields
generalautoStart, notifications, darkMode
capturemaxPacketSize, promiscuousMode, bufferSize
detectionsensitivityLevel, enabledLayers, autoBlock
storageretentionDays, 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

HookPurpose
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)

On this page