// src/components/Sniffing.tsx import { CheckCircleOutlined, ExclamationCircleOutlined, PlayCircleOutlined, PlusOutlined, ReloadOutlined, StopOutlined, } from '@ant-design/icons'; import { Button, Card, Form, List, Modal, notification, Radio, Select, Space, Spin, Tag, Tooltip, Typography, } from 'antd'; import { ReactElement, useMemo, useState } from 'react'; import { startSniffer, stopSniffer, stopSnifferByInterface } from '../api/apiClient'; import { BridgeInfo, InterfaceInfo } from '../types/network'; import { InterfaceSnifferStatus } from '../types/sniffer'; const { Text } = Typography; const { Option } = Select; interface SnifferManagerProps { interfaces: InterfaceInfo[]; bridges: BridgeInfo[]; statusMap: Record; refreshAll: () => Promise; refreshStatus: () => Promise; loading: boolean; statusLoading: boolean; } export default function SnifferManager(props: SnifferManagerProps): ReactElement { // local UI state // modal / form const [isModalOpen, setIsModalOpen] = useState(false); const [startMode, setStartMode] = useState<'interface' | 'bridge'>('interface'); const [form] = Form.useForm(); // derived entries const statusEntries = useMemo( () => Object.entries(props.statusMap) as [string, InterfaceSnifferStatus][], [props.statusMap], ); // open/close modal const onOpenStartModal = () => { form.resetFields(); setStartMode('interface'); setIsModalOpen(true); }; const onCloseModal = () => setIsModalOpen(false); // start submit const handleStartSubmit = async (values: any) => { const { target } = values; if (!target) { notification.warning({ message: 'Warning', description: 'Please select a target to start sniffing on.' }); return; } try { const payload = startMode === 'interface' ? { interface: target } : { bridge: target }; const res = await startSniffer(payload); notification.success({ message: 'Sniffer started', description: `Sniffer started on ${target} (session ${res.session_id})`, }); await props.refreshAll(); setIsModalOpen(false); } catch (err: any) { console.error('startSniffer error', err); notification.error(err?.message ?? 'Failed to start sniffer'); } finally { } }; // stop all const handleStopAll = async () => { try { await stopSniffer(); notification.success({ message: 'All sniffers stopped', }); await props.refreshStatus(); } catch (err: any) { console.error('stopSniffer error', err); notification.error(err?.message ?? 'Failed to stop sniffers'); } finally { } }; // stop per-interface const handleStopFromList = async (ifaceName: string, session_id?: string | null) => { try { // prefer stop by interface await stopSnifferByInterface(ifaceName); notification.success({ message: 'Sniffer stopped', description: `Sniffer stopped on interface ${ifaceName}`, }); await props.refreshStatus(); return; } catch (err: any) { console.error('stopSnifferByInterface error', err); // try stop by session as fallback if (session_id) { try { await stopSniffer({ session_id }); notification.success({ message: 'Sniffer stopped', description: `Sniffer stopped on interface ${ifaceName} (session ${session_id})`, }); await props.refreshStatus(); return; } catch (err2: any) { console.error('stopSniffer by session fallback failed', err2); } } notification.error({ message: 'Failed to stop sniffer', description: err?.message ?? 'Failed to stop sniffer', }); } finally { } }; return (
) : ( ), ]} > {name} {running ? ( } color="success"> running ) : ( } color="default"> stopped )} {!exists && missing} {exists && !up && down} {exists && up && up} {session_id && ( {session_label ? `${session_label}` : 'session'}:{' '} {session_id.slice(0, 8)} )} } description={interface: {name}} /> ); }} /> )} {/* Start sniffer modal */} form.submit()} confirmLoading={props.loading} okText="Start" >
{ setStartMode(e.target.value); form.setFieldsValue({ target: undefined }); }} > Interface Bridge
); }