// src/components/Sniffing.tsx import { CheckCircleOutlined, ExclamationCircleOutlined, PlayCircleOutlined, ReloadOutlined, StopOutlined, SyncOutlined, } from '@ant-design/icons'; import { Button, Card, Col, Divider, List, message, Row, Select, Space, Spin, Tag, Tooltip, Typography } from 'antd'; import { ReactElement, useCallback, useEffect, useState } from 'react'; import { fetchSnifferStatus as fetchSnifferStatusApi, startSniffer, stopSniffer } from '../api/apiClient'; import { useBackendAPI } from '../hooks/useBackendAPI'; import { InterfaceSnifferStatus } from '../types/sniffer'; const { Title, Text } = Typography; const { Option } = Select; /** Local type for the interface objects returned by your backend/hook. * Adjust fields if your backend has different shape. */ type NetworkInterface = { ifindex: number; name: string; state?: string; mac?: string; mtu?: number; flags?: string[]; addresses?: string[]; }; export default function Sniffing(): ReactElement { const { interfacesQuery, bridgesQuery, fetchInterfaces, fetchBridges, refreshInterfaces, refreshBridges, snifferStatusQuery, } = useBackendAPI(); // local UI state const [selectedInterface, setSelectedInterface] = useState(undefined); const [busy, setBusy] = useState(false); // ensure data is fetched on mount useEffect(() => { fetchInterfaces().catch(() => {}); fetchSnifferStatusApi().catch(() => {}); if (fetchBridges) fetchBridges().catch(() => {}); }, [fetchInterfaces, fetchBridges]); // Properly type interfaces as array of objects (not string[]) const interfaces = (interfacesQuery?.data ?? []) as NetworkInterface[]; const bridges = (bridgesQuery?.data ?? []) as Array<{ name: string }>; const statusMap = (snifferStatusQuery?.data?.interfaces ?? {}) as Record; // Explicitly type entries so renderItem knows the types const statusEntries = Object.entries(statusMap) as [string, InterfaceSnifferStatus][]; const refreshAll = useCallback(async () => { try { if (refreshInterfaces) await refreshInterfaces(); if (snifferStatusQuery?.refetch) await snifferStatusQuery.refetch(); if (refreshBridges) await refreshBridges(); } catch { // ignore — queries will surface errors } }, [refreshInterfaces, snifferStatusQuery, refreshBridges]); // actions const handleStart = async () => { if (!selectedInterface) { message.warning('Please select an interface to start sniffing on.'); return; } setBusy(true); try { await startSniffer({ bridge: selectedInterface }); message.success(`Sniffer started on ${selectedInterface}`); await refreshAll(); } catch (err: any) { console.error('startSniffer error', err); message.error(err?.message ?? 'Failed to start sniffer'); } finally { setBusy(false); } }; const handleStop = async () => { setBusy(true); try { await stopSniffer(); message.success('Sniffer stopped'); await refreshAll(); } catch (err: any) { console.error('stopSniffer error', err); message.error(err?.message ?? 'Failed to stop sniffer'); } finally { setBusy(false); } }; // Quick action per-interface (start/stop) const handleStartOn = async (ifaceName: string) => { setBusy(true); try { await startSniffer({ bridge: ifaceName }); message.success(`Started sniffer on ${ifaceName}`); await refreshAll(); } catch (err: any) { console.error('handleStartOn error', err); message.error(err?.message ?? `Failed to start on ${ifaceName}`); } finally { setBusy(false); } }; const handleStopFromList = async (ifaceName?: string) => { setBusy(true); try { await stopSniffer(); message.success(`Stopped sniffer${ifaceName ? ` (requested from ${ifaceName})` : ''}`); await refreshAll(); } catch (err: any) { console.error('handleStopFromList error', err); message.error(err?.message ?? 'Failed to stop sniffer'); } finally { setBusy(false); } }; return (
Sniffing Start, stop and view AF_PACKET sniffer status Tip: you can also start/stop directly from the list below. {snifferStatusQuery?.isLoading ? (
) : snifferStatusQuery?.isError ? (
Failed to load sniffer status. {(snifferStatusQuery?.error as any)?.message ?? String(snifferStatusQuery?.error)}
) : statusEntries.length === 0 ? (
No status information available.
) : ( { const running = st.running; const exists = st.exists; const up = st.up; return ( } onClick={() => handleStopFromList(name)} disabled={busy} > Stop ) : ( ), ]} > {name} {running ? ( } color="success"> running ) : ( } color="default"> stopped )} {!exists && missing} {exists && !up && down} {exists && up && up} } description={ interface: {name} } /> ); }} /> )}
{interfacesQuery?.isLoading ? ( ) : interfaces.length === 0 ? ( No interfaces found. ) : ( {iface.name}} /> )} {bridgesQuery?.isLoading ? ( ) : bridges.length === 0 ? ( No bridges found. ) : ( {b.name}} /> )}
); }