import { useMutation, useQuery } from "@tanstack/react-query"; import { Activity, Cpu, Copy, RadioTower, RefreshCcw, ScrollText } from "lucide-react"; import { useState } from "react"; import { AgentInstallInfo, api, Node, RuntimeSettings } from "../api/client"; import { DataTable } from "../components/DataTable"; import { iconButtonClass, secondaryButtonClass } from "../components/FormControls"; import { LoadingOverlay } from "../components/LoadingOverlay"; import { Modal } from "../components/Modal"; import { PageHeader } from "../components/PageHeader"; export function Nodes() { const nodes = useQuery({ queryKey: ["nodes-agents"], queryFn: () => api("/nodes/agents") }); const settings = useQuery({ queryKey: ["settings"], queryFn: () => api("/settings") }); const [selectedNode, setSelectedNode] = useState(null); const [detailNode, setDetailNode] = useState(null); const [copied, setCopied] = useState(false); const installInfo = useMutation({ mutationFn: (node: Node) => api(`/nodes/${node.id}/agent/install-info`), onSuccess: (_data, node) => { setSelectedNode(node); setCopied(false); }, }); const toggleAutoSync = useMutation({ mutationFn: () => api("/settings", { method: "PATCH", body: JSON.stringify({ auto_node_sync_enabled: !settings.data?.auto_node_sync_enabled }), }), onSuccess: () => settings.refetch(), }); async function copyCommand() { if (!installInfo.data) { return; } await navigator.clipboard.writeText(installInfo.data.command); setCopied(true); } function agentFlows(node: Node) { const flows = node.agent?.last_payload?.flows; const interfaceTraffic = node.agent?.last_payload?.interface_traffic; if (Array.isArray(flows) && flows.length) { return flows; } return Array.isArray(interfaceTraffic) ? interfaceTraffic : []; } function agentInterfaces(node: Node) { const interfaces = node.agent?.last_payload?.interfaces; return Array.isArray(interfaces) ? interfaces : []; } function agentConntrack(node: Node) { const conntrack = node.agent?.last_payload?.conntrack; return conntrack && typeof conntrack === "object" ? conntrack as Record : {}; } return ( <>
Automatic node sync
{settings.data?.auto_node_sync_enabled ? `Enabled every ${settings.data.auto_node_sync_interval_minutes} minutes` : "Disabled"}
{nodes.isLoading ?
Loading...
: null} {nodes.error ?
Failed to load nodes.
: null} {!nodes.isLoading && !nodes.error ? ( []} columns={[ { key: "name", label: "Name" }, { key: "status", label: "Status" }, { key: "cpu_count", label: "CPU" }, { key: "memory_mb", label: "Memory MB" }, { key: "agent", label: "Agent", render: (row) => { const node = row as unknown as Node; return node.agent ? `${node.agent.status}${node.agent.version ? ` · ${node.agent.version}` : ""}` : "not_installed"; }, }, { key: "actions", label: "Actions", render: (row) => { const node = row as unknown as Node; return (
); }, }, ]} /> ) : null} setSelectedNode(null)}>
{selectedNode?.name}
Run this command as root on the Proxmox node. It installs the agent under /opt/nexafabric-agent and starts a systemd service.
            {installInfo.data?.command ?? "Generating installer..."}
          
Installer link {installInfo.data?.install_url ?? ""}
setDetailNode(null)}> {detailNode ? (
{detailNode.name}
Status
{detailNode.agent?.status ?? "not_installed"}
Flows
{agentFlows(detailNode).length}
Conntrack
{String(agentConntrack(detailNode).count ?? "unknown")}
Interfaces
{agentInterfaces(detailNode).length ? agentInterfaces(detailNode).map((item, index) => { const iface = item as Record; return (
{String(iface.name)}
state {String(iface.operstate ?? "unknown")} {iface.vmid ? `· VMID ${String(iface.vmid)}` : ""}
rx {String(iface.rx_bytes ?? 0)}
tx {String(iface.tx_bytes ?? 0)}
); }) :
No interface telemetry in the last heartbeat.
}
Flows
{agentFlows(detailNode).length ? agentFlows(detailNode).slice(0, 50).map((item, index) => { const flow = item as Record; const isInterfaceCounter = flow.protocol === "interface-counter"; return (
{isInterfaceCounter ? `VMID ${String(flow.vmid)} ${String(flow.interface ?? "")}` : `${String(flow.source_ip)}:${String(flow.source_port ?? "")} -> ${String(flow.destination_ip)}:${String(flow.destination_port ?? "")}`}
{String(flow.protocol ?? "unknown")} · {String(flow.bytes ?? 0)} bytes · {String(flow.packets ?? 0)} packets · {String(flow.state ?? "unknown")}
{isInterfaceCounter ?
rx {String(flow.rx_bytes ?? 0)} · tx {String(flow.tx_bytes ?? 0)}
: null}
); }) :
No conntrack flows or interface counters were reported in the last heartbeat.
}
Raw Payload
                {JSON.stringify(detailNode.agent?.last_payload ?? {}, null, 2)}
              
) : null}
); }