/** * TorchTool -- Live traffic monitoring per interface. * * Uses /tool/torch via config editor execute. * Filter by src/dst address, protocol, port. * Auto-refresh with configurable interval. */ import { useState, useCallback, useEffect, useRef } from 'react' import { useMutation } from '@tanstack/react-query' import { Play, Square, Flame, RefreshCw } from 'lucide-react' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { configEditorApi } from '@/lib/configEditorApi' import { useConfigBrowse } from '@/hooks/useConfigPanel' import { cn } from '@/lib/utils' import type { ConfigPanelProps } from '@/lib/configPanelTypes' interface TorchEntry { srcAddress: string dstAddress: string protocol: string srcPort: string dstPort: string txRate: string rxRate: string tx: string rx: string } function formatBps(val: string): string { const n = parseInt(val, 10) if (isNaN(n)) return val || '-' if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)} Mbps` if (n >= 1_000) return `${(n / 1_000).toFixed(0)} Kbps` return `${n} bps` } export function TorchTool({ tenantId, deviceId, active }: ConfigPanelProps) { const interfaces = useConfigBrowse(tenantId, deviceId, '/interface', { enabled: active }) const [iface, setIface] = useState('ether1') const [srcFilter, setSrcFilter] = useState('') const [dstFilter, setDstFilter] = useState('') const [protocolFilter, setProtocolFilter] = useState('') const [portFilter, setPortFilter] = useState('') const [autoRefresh, setAutoRefresh] = useState(false) const [entries, setEntries] = useState([]) const timerRef = useRef | null>(null) const torchMutation = useMutation({ mutationFn: async () => { const parts = ['/tool/torch', `interface=${iface}`, 'duration=3s'] if (srcFilter) parts.push(`src-address=${srcFilter}`) if (dstFilter) parts.push(`dst-address=${dstFilter}`) if (protocolFilter) parts.push(`protocol=${protocolFilter}`) if (portFilter) parts.push(`port=${portFilter}`) return configEditorApi.execute(tenantId, deviceId, parts.join(' ')) }, onSuccess: (resp) => { if (!resp.success) { setEntries([]); return } const rows: TorchEntry[] = resp.data .filter((d) => d['src-address'] || d['dst-address']) .map((d) => ({ srcAddress: d['src-address'] || '', dstAddress: d['dst-address'] || '', protocol: d['ip-protocol'] || d['protocol'] || '', srcPort: d['src-port'] || '', dstPort: d['dst-port'] || '', txRate: d['tx-rate'] || d['tx'] || '', rxRate: d['rx-rate'] || d['rx'] || '', tx: d['tx-packets'] || '', rx: d['rx-packets'] || '', })) setEntries(rows) }, }) const handleRun = useCallback(() => { torchMutation.mutate() }, [torchMutation]) // Auto-refresh useEffect(() => { if (autoRefresh && !torchMutation.isPending) { timerRef.current = setInterval(() => { torchMutation.mutate() }, 5000) } return () => { if (timerRef.current) clearInterval(timerRef.current) } }, [autoRefresh, torchMutation.isPending]) const handleToggleAuto = useCallback(() => { if (autoRefresh) { setAutoRefresh(false) if (timerRef.current) clearInterval(timerRef.current) } else { setAutoRefresh(true) handleRun() } }, [autoRefresh, handleRun]) const ifaceNames = interfaces.entries.map((e) => e['name']).filter(Boolean) return (
setSrcFilter(e.target.value)} placeholder="any" className="h-8 text-sm font-mono" />
setDstFilter(e.target.value)} placeholder="any" className="h-8 text-sm font-mono" />
setProtocolFilter(e.target.value)} placeholder="any" className="h-8 text-sm" />
setPortFilter(e.target.value)} placeholder="any" className="h-8 text-sm" />
{torchMutation.isError && (
Failed to execute torch command.
)} {entries.length > 0 && (
Torch — {iface} ({entries.length} flows)
{entries.map((e, i) => ( ))}
Src Address Dst Address Proto Src Port Dst Port TX Rate RX Rate
{e.srcAddress || '-'} {e.dstAddress || '-'} {e.protocol || '-'} {e.srcPort || '-'} {e.dstPort || '-'} {formatBps(e.txRate)} {formatBps(e.rxRate)}
)} {entries.length === 0 && !torchMutation.isPending && !torchMutation.isIdle && (
No traffic captured. Try a different interface or remove filters.
)}
) }