import { useMemo, useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { Activity, ArrowRight, CircuitBoard, Hash, Network, ShieldCheck } from "lucide-react"; import { Link, useNavigate, useParams } from "react-router-dom"; import { api, Workload, WorkloadInsight } from "../api/client"; import { DataTable } from "../components/DataTable"; import { secondaryButtonClass } from "../components/FormControls"; import { PageHeader } from "../components/PageHeader"; type TrafficSummary = { key: string; source: string; destination: string; protocol: string; port: string; bytes: number; packets: number; count: number; decision: string; interfaceName: string; note: string; ipAddresses: string[]; }; function formatBytes(value: unknown) { const bytes = Number(value ?? 0); if (!Number.isFinite(bytes) || bytes <= 0) { return "0 B"; } const units = ["B", "KB", "MB", "GB", "TB"]; const index = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1); return `${(bytes / 1024 ** index).toFixed(index === 0 ? 0 : 1)} ${units[index]}`; } function summarizeTraffic(traffic: Array>) { const summaries = new Map(); for (const flow of traffic) { const source = String(flow.source ?? flow.source_ip ?? "external"); const destination = String(flow.destination ?? flow.destination_ip ?? "external"); const protocol = String(flow.protocol ?? "unknown"); const port = String(flow.port ?? flow.destination_port ?? ""); const key = [source, destination, protocol, port, String(flow.interface ?? "")].join("|"); const existing = summaries.get(key); const bytes = Number(flow.bytes ?? 0); const packets = Number(flow.packets ?? 0); const ipAddresses = Array.isArray(flow.ip_addresses) ? flow.ip_addresses.map(String) : []; if (existing) { existing.bytes += Number.isFinite(bytes) ? bytes : 0; existing.packets += Number.isFinite(packets) ? packets : 0; existing.count += 1; existing.ipAddresses = Array.from(new Set([...existing.ipAddresses, ...ipAddresses])); continue; } summaries.set(key, { key, source, destination, protocol, port, bytes: Number.isFinite(bytes) ? bytes : 0, packets: Number.isFinite(packets) ? packets : 0, count: 1, decision: String(flow.decision ?? "observed"), interfaceName: String(flow.interface ?? ""), note: String(flow.note ?? ""), ipAddresses, }); } return Array.from(summaries.values()).sort((left, right) => right.bytes - left.bytes); } function totalBytes(traffic: TrafficSummary[]) { return traffic.reduce((sum, flow) => sum + flow.bytes, 0); } function TrafficBars({ traffic }: { traffic: TrafficSummary[] }) { const top = traffic.slice(0, 5); const max = Math.max(...top.map((flow) => flow.bytes), 1); if (!top.length) { return
No traffic data yet.
; } return (
{top.map((flow) => (
{flow.source} {"->"} {flow.destination} {formatBytes(flow.bytes)}
))}
); } function ProtocolChart({ traffic }: { traffic: TrafficSummary[] }) { const protocolTotals = Array.from( traffic.reduce((map, flow) => map.set(flow.protocol, (map.get(flow.protocol) ?? 0) + flow.bytes), new Map()), ).sort((left, right) => right[1] - left[1]); const total = protocolTotals.reduce((sum, [, bytes]) => sum + bytes, 0); const palette = ["#2dd4bf", "#60a5fa", "#f59e0b", "#f472b6", "#a78bfa"]; if (!total) { return
No protocol split available.
; } return (
{protocolTotals.map(([protocol, bytes], index) => { const width = (bytes / total) * 100; return
; })}
{protocolTotals.map(([protocol, bytes], index) => ( {protocol} {formatBytes(bytes)} ))}
); } function WorkloadFacts({ insight }: { insight: WorkloadInsight }) { return (
Type
{insight.workload.kind}
Status
{insight.workload.status}
VMID
{insight.workload.external_id}
Decision
{insight.effective_decision}
); } function TrafficTable({ traffic }: { traffic: TrafficSummary[] }) { return (
{traffic.map((flow) => ( ))}
Flow Protocol Traffic Packets Seen
{flow.source} {"->"} {flow.destination}
{flow.ipAddresses.join(", ") || flow.interfaceName || "no endpoint metadata"}
{flow.protocol}{flow.port ? `:${flow.port}` : ""} {formatBytes(flow.bytes)} {flow.packets} {flow.count} sample{flow.count === 1 ? "" : "s"}
); } export function Workloads() { const navigate = useNavigate(); const workloads = useQuery({ queryKey: ["workloads"], queryFn: () => api("/vms") }); const [selectedId, setSelectedId] = useState(""); const selected = selectedId || workloads.data?.[0]?.id || ""; const insight = useQuery({ queryKey: ["workload-insight", selected], queryFn: () => api(`/vms/${selected}/insights`), enabled: Boolean(selected), }); const traffic = useMemo(() => summarizeTraffic(insight.data?.traffic ?? []), [insight.data?.traffic]); return ( <>
[]} columns={[{ key: "name", label: "Name" }, { key: "kind", label: "Kind" }, { key: "status", label: "Status" }, { key: "external_id", label: "VMID" }]} selectedId={selected} onRowClick={(row) => setSelectedId(String(row.id))} />
); } export function WorkloadDetail() { const { workloadId } = useParams(); const insight = useQuery({ queryKey: ["workload-insight", workloadId], queryFn: () => api(`/vms/${workloadId}/insights`), enabled: Boolean(workloadId), }); const traffic = useMemo(() => summarizeTraffic(insight.data?.traffic ?? []), [insight.data?.traffic]); if (insight.isLoading) { return
Loading workload...
; } if (!insight.data) { return
Workload details could not be loaded.
; } return ( <>
Back to VMs/LXCs
Traffic Distribution
{traffic.length} aggregated flows · {formatBytes(totalBytes(traffic))}
Flow Table
{traffic.length ? :
No flow telemetry collected yet.
}
); }