/** * SnmpPanel -- SNMP configuration panel. * * Enable/disable SNMP (/snmp). * Community strings management (/snmp/community). * Trap target configuration. * Contact, location, engine-id settings. */ import { useState, useCallback } from 'react' import { Plus, Pencil, Trash2, Radio } from 'lucide-react' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Dialog, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription, } from '@/components/ui/dialog' import { SafetyToggle } from './SafetyToggle' import { ChangePreviewModal } from './ChangePreviewModal' import { useConfigBrowse, useConfigPanel } from '@/hooks/useConfigPanel' import { cn } from '@/lib/utils' import type { ConfigPanelProps } from '@/lib/configPanelTypes' export function SnmpPanel({ tenantId, deviceId, active }: ConfigPanelProps) { const snmp = useConfigBrowse(tenantId, deviceId, '/snmp', { enabled: active }) const communities = useConfigBrowse(tenantId, deviceId, '/snmp/community', { enabled: active }) const panel = useConfigPanel(tenantId, deviceId, 'snmp') const [previewOpen, setPreviewOpen] = useState(false) const [settingsOpen, setSettingsOpen] = useState(false) const [communityOpen, setCommunityOpen] = useState(false) const [editEntry, setEditEntry] = useState | null>(null) const [settingsForm, setSettingsForm] = useState>({}) const [communityForm, setCommunityForm] = useState>({}) const snmpData = snmp.entries[0] ?? {} const isEnabled = snmpData['enabled'] === 'true' || snmpData['enabled'] === 'yes' const handleToggleSnmp = useCallback(() => { panel.addChange({ operation: 'set', path: '/snmp', properties: { enabled: isEnabled ? 'no' : 'yes' }, description: `${isEnabled ? 'Disable' : 'Enable'} SNMP`, }) }, [isEnabled, panel]) const handleEditSettings = useCallback(() => { setSettingsForm({ contact: snmpData['contact'] || '', location: snmpData['location'] || '', 'engine-id': snmpData['engine-id'] || '', 'trap-target': snmpData['trap-target'] || '', 'trap-community': snmpData['trap-community'] || '', 'trap-version': snmpData['trap-version'] || '1', }) setSettingsOpen(true) }, [snmpData]) const handleSaveSettings = useCallback(() => { const props: Record = {} Object.entries(settingsForm).forEach(([key, value]) => { if (value !== (snmpData[key] || '')) props[key] = value }) if (Object.keys(props).length === 0) { setSettingsOpen(false); return } panel.addChange({ operation: 'set', path: '/snmp', properties: props, description: `Update SNMP settings (${Object.keys(props).join(', ')})`, }) setSettingsOpen(false) }, [settingsForm, snmpData, panel]) const handleAddCommunity = useCallback(() => { setEditEntry(null) setCommunityForm({ name: '', addresses: '0.0.0.0/0', 'read-access': 'yes', 'write-access': 'no', security: 'none', 'authentication-protocol': 'MD5', 'encryption-protocol': 'DES', }) setCommunityOpen(true) }, []) const handleEditCommunity = useCallback((entry: Record) => { setEditEntry(entry) setCommunityForm({ name: entry['name'] || '', addresses: entry['addresses'] || '', 'read-access': entry['read-access'] || 'yes', 'write-access': entry['write-access'] || 'no', security: entry['security'] || 'none', 'authentication-protocol': entry['authentication-protocol'] || 'MD5', 'encryption-protocol': entry['encryption-protocol'] || 'DES', }) setCommunityOpen(true) }, []) const handleSaveCommunity = useCallback(() => { if (!communityForm['name']) return if (editEntry) { const props: Record = {} Object.entries(communityForm).forEach(([key, value]) => { if (value !== editEntry[key]) props[key] = value }) if (Object.keys(props).length === 0) { setCommunityOpen(false); return } panel.addChange({ operation: 'set', path: '/snmp/community', entryId: editEntry['.id'], properties: props, description: `Update SNMP community "${communityForm['name']}"`, }) } else { panel.addChange({ operation: 'add', path: '/snmp/community', properties: communityForm, description: `Add SNMP community "${communityForm['name']}"`, }) } setCommunityOpen(false) }, [communityForm, editEntry, panel]) const handleDeleteCommunity = useCallback((entry: Record) => { panel.addChange({ operation: 'remove', path: '/snmp/community', entryId: entry['.id'], properties: {}, description: `Remove SNMP community "${entry['name']}"`, }) }, [panel]) if (snmp.isLoading) { return
Loading SNMP settings...
} if (snmp.error) { return
Failed to load.
} return (
{/* SNMP Status + Settings */}
SNMP Service
{/* Communities */}
SNMP Communities
{communities.entries.length === 0 ? (
No SNMP communities configured.
) : (
{communities.entries.map((entry) => ( ))}
Name Addresses Read Write Security Actions
{entry['name']} {entry['addresses'] || '0.0.0.0/0'} {entry['read-access'] === 'yes' ? 'Y' : 'N'} {entry['write-access'] === 'yes' ? 'Y' : 'N'} {entry['security'] || 'none'}
)}
{/* SNMP Settings dialog */} SNMP Settings Configure SNMP service settings. Changes are staged.
setSettingsForm((f) => ({ ...f, contact: e.target.value }))} className="h-8 text-sm" />
setSettingsForm((f) => ({ ...f, location: e.target.value }))} className="h-8 text-sm" />
setSettingsForm((f) => ({ ...f, 'engine-id': e.target.value }))} className="h-8 text-sm font-mono" />
setSettingsForm((f) => ({ ...f, 'trap-target': e.target.value }))} placeholder="192.168.1.100" className="h-8 text-sm font-mono" />
setSettingsForm((f) => ({ ...f, 'trap-community': e.target.value }))} className="h-8 text-sm" />
{/* Community dialog */} {editEntry ? 'Edit Community' : 'Add Community'} Configure SNMP community string. Changes are staged.
setCommunityForm((f) => ({ ...f, name: e.target.value }))} placeholder="public" className="h-8 text-sm" />
setCommunityForm((f) => ({ ...f, addresses: e.target.value }))} placeholder="0.0.0.0/0" className="h-8 text-sm font-mono" />
{ panel.applyChanges(); setPreviewOpen(false) }} isApplying={panel.isApplying} />
) } function InfoRow({ label, value }: { label: string; value?: string }) { return (
{label} {value || '—'}
) }