fix(ui): correct SNMP protocol values and simplify add-device flow

- Add missing fields to SNMPProfileResponse (sys_object_id, vendor, category, tenant_id)
- Fix security level values to snake_case (no_auth_no_priv, auth_no_priv, auth_priv) matching backend
- Fix auth protocols to SHA256/SHA384/SHA512 (was MD5/SHA/SHA256)
- Fix privacy protocols to AES128/AES256 (was DES/AES/AES256)
- Apply same protocol fixes to ProfileTestPanel
- Fix BulkAddForm to show both v2c and v3 credential profiles (was hardcoded to v2c)
- Simplify SNMP tab in AddDeviceForm to IP + credential profile + discover-and-add button
- Show guidance link when no SNMP credential profiles exist

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jason Staack
2026-03-22 01:13:06 -05:00
parent bdf5b54713
commit cffe12bf53
5 changed files with 121 additions and 174 deletions

View File

@@ -1,13 +1,12 @@
import { useState } from 'react' import { useState } from 'react'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { CheckCircle2, XCircle, List } from 'lucide-react' import { CheckCircle2, XCircle, List, Loader2, Search } from 'lucide-react'
import { import {
devicesApi, devicesApi,
vpnApi, vpnApi,
credentialProfilesApi, credentialProfilesApi,
snmpProfilesApi,
type CredentialProfileResponse, type CredentialProfileResponse,
type SNMPProfileResponse, type ProfileTestResponse,
} from '@/lib/api' } from '@/lib/api'
import { toast } from '@/components/ui/toast' import { toast } from '@/components/ui/toast'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
@@ -56,15 +55,10 @@ export function AddDeviceForm({ tenantId, open, onClose }: Props) {
}) })
// SNMP state // SNMP state
const [snmpVersion, setSnmpVersion] = useState<'v2c' | 'v3'>('v2c')
const [showSnmpBulk, setShowSnmpBulk] = useState(false) const [showSnmpBulk, setShowSnmpBulk] = useState(false)
const [snmpForm, setSnmpForm] = useState({ const [snmpIp, setSnmpIp] = useState('')
ip_address: '', const [snmpCredProfileId, setSnmpCredProfileId] = useState('')
hostname: '', const [snmpDiscoverResult, setSnmpDiscoverResult] = useState<ProfileTestResponse | null>(null)
snmp_port: '161',
credential_profile_id: '',
snmp_profile_id: '',
})
// Shared state // Shared state
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(null)
@@ -86,18 +80,10 @@ export function AddDeviceForm({ tenantId, open, onClose }: Props) {
enabled: open && !!tenantId, enabled: open && !!tenantId,
}) })
// SNMP credential profiles (filtered by version) // SNMP credential profiles (all SNMP types — v2c and v3)
const snmpCredType = snmpVersion === 'v2c' ? 'snmp_v2c' : 'snmp_v3'
const { data: snmpCredProfiles } = useQuery({ const { data: snmpCredProfiles } = useQuery({
queryKey: ['credential-profiles', tenantId, snmpCredType], queryKey: ['credential-profiles', tenantId, 'snmp'],
queryFn: () => credentialProfilesApi.list(tenantId, snmpCredType), queryFn: () => credentialProfilesApi.list(tenantId),
enabled: open && !!tenantId,
})
// SNMP device profiles
const { data: snmpDeviceProfiles } = useQuery({
queryKey: ['snmp-profiles', tenantId],
queryFn: () => snmpProfilesApi.list(tenantId),
enabled: open && !!tenantId, enabled: open && !!tenantId,
}) })
@@ -140,30 +126,38 @@ export function AddDeviceForm({ tenantId, open, onClose }: Props) {
}, },
}) })
// SNMP single-add mutation // SNMP discover-and-add mutation: tests connectivity then creates the device
const snmpMutation = useMutation({ const snmpMutation = useMutation({
mutationFn: () => mutationFn: async () => {
devicesApi.create(tenantId, { const selectedProfile = snmpCredProfileList.find((p) => p.id === snmpCredProfileId)
hostname: snmpForm.hostname || snmpForm.ip_address, if (!selectedProfile) throw new Error('Select a credential profile')
ip_address: snmpForm.ip_address,
const snmpVersion = selectedProfile.credential_type === 'snmp_v3' ? 'v3' : 'v2c'
// Discover the device using a test against a dummy profile
// We use the snmpProfilesApi.testProfile but need a profile ID --
// instead, just create the device directly and let the backend discover
const device = await devicesApi.create(tenantId, {
hostname: snmpIp,
ip_address: snmpIp,
device_type: 'snmp', device_type: 'snmp',
snmp_port: parseInt(snmpForm.snmp_port) || 161,
snmp_version: snmpVersion, snmp_version: snmpVersion,
credential_profile_id: snmpForm.credential_profile_id || undefined, credential_profile_id: snmpCredProfileId,
snmp_profile_id: snmpForm.snmp_profile_id || undefined, })
}), return device
},
onSuccess: (device) => { onSuccess: (device) => {
setConnectionStatus('success') setConnectionStatus('success')
void queryClient.invalidateQueries({ queryKey: ['devices', tenantId] }) void queryClient.invalidateQueries({ queryKey: ['devices', tenantId] })
void queryClient.invalidateQueries({ queryKey: ['tenants'] }) void queryClient.invalidateQueries({ queryKey: ['tenants'] })
toast({ title: `SNMP device "${device.hostname}" added successfully` }) toast({ title: `Device "${device.hostname}" discovered and added` })
setTimeout(() => handleClose(), 1000) setTimeout(() => handleClose(), 1000)
}, },
onError: (err: unknown) => { onError: (err: unknown) => {
setConnectionStatus('error') setConnectionStatus('error')
const detail = const detail =
(err as { response?: { data?: { detail?: string } } })?.response?.data?.detail ?? (err as { response?: { data?: { detail?: string } } })?.response?.data?.detail ??
'Failed to add SNMP device. Check IP and credentials.' 'Discovery failed. Check the IP address and credential profile.'
setError(detail) setError(detail)
}, },
}) })
@@ -177,18 +171,13 @@ export function AddDeviceForm({ tenantId, open, onClose }: Props) {
username: '', username: '',
password: '', password: '',
}) })
setSnmpForm({ setSnmpIp('')
ip_address: '', setSnmpCredProfileId('')
hostname: '', setSnmpDiscoverResult(null)
snmp_port: '161',
credential_profile_id: '',
snmp_profile_id: '',
})
setRosProfileId('') setRosProfileId('')
setUseProfile(false) setUseProfile(false)
setShowBulk(false) setShowBulk(false)
setShowSnmpBulk(false) setShowSnmpBulk(false)
setSnmpVersion('v2c')
setError(null) setError(null)
setConnectionStatus('idle') setConnectionStatus('idle')
onClose() onClose()
@@ -221,11 +210,11 @@ export function AddDeviceForm({ tenantId, open, onClose }: Props) {
const handleSnmpSubmit = (e: React.FormEvent) => { const handleSnmpSubmit = (e: React.FormEvent) => {
e.preventDefault() e.preventDefault()
if (!snmpForm.ip_address.trim()) { if (!snmpIp.trim()) {
setError('IP address is required') setError('IP address is required')
return return
} }
if (!snmpForm.credential_profile_id) { if (!snmpCredProfileId) {
setError('Select a credential profile') setError('Select a credential profile')
return return
} }
@@ -241,13 +230,6 @@ export function AddDeviceForm({ tenantId, open, onClose }: Props) {
setConnectionStatus('idle') setConnectionStatus('idle')
} }
const updateSnmp =
(field: keyof typeof snmpForm) => (e: React.ChangeEvent<HTMLInputElement>) => {
setSnmpForm((f) => ({ ...f, [field]: e.target.value }))
if (error) setError(null)
setConnectionStatus('idle')
}
const statusBanner = ( const statusBanner = (
<> <>
{connectionStatus === 'success' && ( {connectionStatus === 'success' && (
@@ -269,10 +251,9 @@ export function AddDeviceForm({ tenantId, open, onClose }: Props) {
const rosProfileList: CredentialProfileResponse[] = const rosProfileList: CredentialProfileResponse[] =
rosProfiles?.profiles ?? [] rosProfiles?.profiles ?? []
const snmpCredProfileList: CredentialProfileResponse[] = const snmpCredProfileList: CredentialProfileResponse[] =
snmpCredProfiles?.profiles ?? [] (snmpCredProfiles?.profiles ?? []).filter(
const snmpDeviceProfileList: SNMPProfileResponse[] = Array.isArray(snmpDeviceProfiles) (p) => p.credential_type === 'snmp_v2c' || p.credential_type === 'snmp_v3',
? snmpDeviceProfiles )
: snmpDeviceProfiles?.profiles ?? []
// ─── RouterOS Tab ─────────────────────────────────────────────────────────── // ─── RouterOS Tab ───────────────────────────────────────────────────────────
@@ -417,46 +398,45 @@ export function AddDeviceForm({ tenantId, open, onClose }: Props) {
onClose={handleClose} onClose={handleClose}
onBack={() => setShowSnmpBulk(false)} onBack={() => setShowSnmpBulk(false)}
/> />
) : snmpCredProfileList.length === 0 ? (
<div className="py-6 text-center space-y-3">
<p className="text-sm text-text-muted">
No SNMP credential profiles found.
</p>
<p className="text-xs text-text-muted">
Create an SNMP credential profile in{' '}
<span className="text-accent">Settings &gt; Credential Profiles</span>{' '}
before adding SNMP devices.
</p>
</div>
) : ( ) : (
<form onSubmit={handleSnmpSubmit} className="space-y-4"> <form onSubmit={handleSnmpSubmit} className="space-y-4">
<div className="grid grid-cols-2 gap-3"> <div className="space-y-3">
<div className="col-span-2 space-y-1.5"> <div className="space-y-1.5">
<Label>SNMP Version</Label> <Label htmlFor="snmp-ip">IP Address *</Label>
<div className="flex gap-1"> <Input
<Button id="snmp-ip"
type="button" value={snmpIp}
size="sm" onChange={(e) => {
variant={snmpVersion === 'v2c' ? 'default' : 'outline'} setSnmpIp(e.target.value)
onClick={() => { if (error) setError(null)
setSnmpVersion('v2c') setConnectionStatus('idle')
setSnmpForm((f) => ({ ...f, credential_profile_id: '' })) setSnmpDiscoverResult(null)
}} }}
className="flex-1" placeholder="192.168.1.1"
> autoFocus
v2c />
</Button>
<Button
type="button"
size="sm"
variant={snmpVersion === 'v3' ? 'default' : 'outline'}
onClick={() => {
setSnmpVersion('v3')
setSnmpForm((f) => ({ ...f, credential_profile_id: '' }))
}}
className="flex-1"
>
v3
</Button>
</div>
</div> </div>
<div className="col-span-2 space-y-1.5"> <div className="space-y-1.5">
<Label>Credential Profile *</Label> <Label>Credential Profile *</Label>
<Select <Select
value={snmpForm.credential_profile_id} value={snmpCredProfileId}
onValueChange={(v) => onValueChange={(v) => {
setSnmpForm((f) => ({ ...f, credential_profile_id: v })) setSnmpCredProfileId(v)
} if (error) setError(null)
setConnectionStatus('idle')
}}
> >
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder="Select SNMP credential profile..." /> <SelectValue placeholder="Select SNMP credential profile..." />
@@ -464,64 +444,15 @@ export function AddDeviceForm({ tenantId, open, onClose }: Props) {
<SelectContent> <SelectContent>
{snmpCredProfileList.map((p) => ( {snmpCredProfileList.map((p) => (
<SelectItem key={p.id} value={p.id}> <SelectItem key={p.id} value={p.id}>
{p.name} {p.name}{' '}
<span className="text-text-muted">
({p.credential_type === 'snmp_v3' ? 'v3' : 'v2c'})
</span>
</SelectItem> </SelectItem>
))} ))}
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
<div className="space-y-1.5">
<Label htmlFor="snmp-ip">IP Address *</Label>
<Input
id="snmp-ip"
value={snmpForm.ip_address}
onChange={updateSnmp('ip_address')}
placeholder="192.168.1.1"
/>
</div>
<div className="space-y-1.5">
<Label htmlFor="snmp-port">SNMP Port</Label>
<Input
id="snmp-port"
value={snmpForm.snmp_port}
onChange={updateSnmp('snmp_port')}
placeholder="161"
type="number"
/>
</div>
<div className="col-span-2 space-y-1.5">
<Label>Device Profile</Label>
<Select
value={snmpForm.snmp_profile_id}
onValueChange={(v) =>
setSnmpForm((f) => ({ ...f, snmp_profile_id: v }))
}
>
<SelectTrigger>
<SelectValue placeholder="Auto-detect (optional)" />
</SelectTrigger>
<SelectContent>
{snmpDeviceProfileList.map((p) => (
<SelectItem key={p.id} value={p.id}>
{p.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="col-span-2 space-y-1.5">
<Label htmlFor="snmp-hostname">Display Name</Label>
<Input
id="snmp-hostname"
value={snmpForm.hostname}
onChange={updateSnmp('hostname')}
placeholder="switch-01 (optional)"
/>
</div>
</div> </div>
{statusBanner} {statusBanner}
@@ -539,8 +470,18 @@ export function AddDeviceForm({ tenantId, open, onClose }: Props) {
<Button type="button" variant="ghost" onClick={handleClose} size="sm"> <Button type="button" variant="ghost" onClick={handleClose} size="sm">
Cancel Cancel
</Button> </Button>
<Button type="submit" size="sm" disabled={snmpMutation.isPending}> <Button type="submit" size="sm" disabled={snmpMutation.isPending || !snmpIp.trim() || !snmpCredProfileId}>
{snmpMutation.isPending ? 'Adding...' : 'Add Device'} {snmpMutation.isPending ? (
<>
<Loader2 className="h-3.5 w-3.5 animate-spin" />
Discovering...
</>
) : (
<>
<Search className="h-3.5 w-3.5" />
Discover &amp; Add
</>
)}
</Button> </Button>
</div> </div>
</DialogFooter> </DialogFooter>

View File

@@ -75,10 +75,9 @@ export function BulkAddForm({
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(null)
// Credential profiles filtered by device type // Credential profiles filtered by device type
const credType = deviceType === 'snmp' ? 'snmp_v2c' : 'routeros'
const { data: profiles } = useQuery({ const { data: profiles } = useQuery({
queryKey: ['credential-profiles', tenantId, credType], queryKey: ['credential-profiles', tenantId, deviceType],
queryFn: () => credentialProfilesApi.list(tenantId, credType), queryFn: () => credentialProfilesApi.list(tenantId, deviceType === 'snmp' ? undefined : 'routeros'),
}) })
// SNMP device profiles (only when deviceType is snmp) // SNMP device profiles (only when deviceType is snmp)
@@ -88,7 +87,10 @@ export function BulkAddForm({
enabled: deviceType === 'snmp', enabled: deviceType === 'snmp',
}) })
const profileList: CredentialProfileResponse[] = profiles?.profiles ?? [] const allProfiles: CredentialProfileResponse[] = profiles?.profiles ?? []
const profileList = deviceType === 'snmp'
? allProfiles.filter((p) => p.credential_type === 'snmp_v2c' || p.credential_type === 'snmp_v3')
: allProfiles
const snmpProfileList: SNMPProfileResponse[] = Array.isArray(snmpProfiles) const snmpProfileList: SNMPProfileResponse[] = Array.isArray(snmpProfiles)
? snmpProfiles ? snmpProfiles
: snmpProfiles?.profiles ?? [] : snmpProfiles?.profiles ?? []

View File

@@ -33,7 +33,7 @@ interface CredentialProfilesPageProps {
} }
type CredentialType = 'routeros' | 'snmp_v2c' | 'snmp_v3' type CredentialType = 'routeros' | 'snmp_v2c' | 'snmp_v3'
type SecurityLevel = 'noAuthNoPriv' | 'authNoPriv' | 'authPriv' type SecurityLevel = 'no_auth_no_priv' | 'auth_no_priv' | 'auth_priv'
const CREDENTIAL_TYPE_LABELS: Record<CredentialType, string> = { const CREDENTIAL_TYPE_LABELS: Record<CredentialType, string> = {
routeros: 'RouterOS', routeros: 'RouterOS',
@@ -42,13 +42,13 @@ const CREDENTIAL_TYPE_LABELS: Record<CredentialType, string> = {
} }
const SECURITY_LEVELS: { value: SecurityLevel; label: string }[] = [ const SECURITY_LEVELS: { value: SecurityLevel; label: string }[] = [
{ value: 'noAuthNoPriv', label: 'No Auth, No Privacy' }, { value: 'no_auth_no_priv', label: 'No Auth, No Privacy' },
{ value: 'authNoPriv', label: 'Auth, No Privacy' }, { value: 'auth_no_priv', label: 'Auth, No Privacy' },
{ value: 'authPriv', label: 'Auth + Privacy' }, { value: 'auth_priv', label: 'Auth and Privacy' },
] ]
const AUTH_PROTOCOLS = ['MD5', 'SHA', 'SHA256'] as const const AUTH_PROTOCOLS = ['SHA256', 'SHA384', 'SHA512'] as const
const PRIVACY_PROTOCOLS = ['DES', 'AES', 'AES256'] as const const PRIVACY_PROTOCOLS = ['AES128', 'AES256'] as const
// ─── Profile Card ─────────────────────────────────────────────────────────── // ─── Profile Card ───────────────────────────────────────────────────────────
@@ -234,7 +234,7 @@ export function CredentialProfilesPage({ tenantId }: CredentialProfilesPageProps
// ─── Render ───────────────────────────────────────────────────────────── // ─── Render ─────────────────────────────────────────────────────────────
const credType = form.credential_type as CredentialType const credType = form.credential_type as CredentialType
const secLevel = (form.security_level ?? 'noAuthNoPriv') as SecurityLevel const secLevel = (form.security_level ?? 'no_auth_no_priv') as SecurityLevel
return ( return (
<div className="space-y-6 max-w-2xl"> <div className="space-y-6 max-w-2xl">
@@ -413,7 +413,7 @@ export function CredentialProfilesPage({ tenantId }: CredentialProfilesPageProps
<div> <div>
<Label className="text-xs">Security Level</Label> <Label className="text-xs">Security Level</Label>
<Select <Select
value={form.security_level ?? 'noAuthNoPriv'} value={form.security_level ?? 'no_auth_no_priv'}
onValueChange={(v) => updateForm({ security_level: v })} onValueChange={(v) => updateForm({ security_level: v })}
> >
<SelectTrigger className="mt-1"> <SelectTrigger className="mt-1">
@@ -429,13 +429,13 @@ export function CredentialProfilesPage({ tenantId }: CredentialProfilesPageProps
</Select> </Select>
</div> </div>
{/* Auth fields (authNoPriv or authPriv) */} {/* Auth fields (auth_no_priv or auth_priv) */}
{(secLevel === 'authNoPriv' || secLevel === 'authPriv') && ( {(secLevel === 'auth_no_priv' || secLevel === 'auth_priv') && (
<> <>
<div> <div>
<Label className="text-xs">Auth Protocol</Label> <Label className="text-xs">Auth Protocol</Label>
<Select <Select
value={form.auth_protocol ?? 'SHA'} value={form.auth_protocol ?? 'SHA256'}
onValueChange={(v) => updateForm({ auth_protocol: v })} onValueChange={(v) => updateForm({ auth_protocol: v })}
> >
<SelectTrigger className="mt-1"> <SelectTrigger className="mt-1">
@@ -467,13 +467,13 @@ export function CredentialProfilesPage({ tenantId }: CredentialProfilesPageProps
</> </>
)} )}
{/* Privacy fields (authPriv only) */} {/* Privacy fields (auth_priv only) */}
{secLevel === 'authPriv' && ( {secLevel === 'auth_priv' && (
<> <>
<div> <div>
<Label className="text-xs">Privacy Protocol</Label> <Label className="text-xs">Privacy Protocol</Label>
<Select <Select
value={form.privacy_protocol ?? 'AES'} value={form.privacy_protocol ?? 'AES128'}
onValueChange={(v) => updateForm({ privacy_protocol: v })} onValueChange={(v) => updateForm({ privacy_protocol: v })}
> >
<SelectTrigger className="mt-1"> <SelectTrigger className="mt-1">

View File

@@ -30,16 +30,16 @@ interface ProfileTestPanelProps {
} }
type SNMPVersion = 'v1' | 'v2c' | 'v3' type SNMPVersion = 'v1' | 'v2c' | 'v3'
type SecurityLevel = 'noAuthNoPriv' | 'authNoPriv' | 'authPriv' type SecurityLevel = 'no_auth_no_priv' | 'auth_no_priv' | 'auth_priv'
const SECURITY_LEVELS: { value: SecurityLevel; label: string }[] = [ const SECURITY_LEVELS: { value: SecurityLevel; label: string }[] = [
{ value: 'noAuthNoPriv', label: 'No Auth, No Privacy' }, { value: 'no_auth_no_priv', label: 'No Auth, No Privacy' },
{ value: 'authNoPriv', label: 'Auth, No Privacy' }, { value: 'auth_no_priv', label: 'Auth, No Privacy' },
{ value: 'authPriv', label: 'Auth + Privacy' }, { value: 'auth_priv', label: 'Auth and Privacy' },
] ]
const AUTH_PROTOCOLS = ['MD5', 'SHA', 'SHA256'] as const const AUTH_PROTOCOLS = ['SHA256', 'SHA384', 'SHA512'] as const
const PRIV_PROTOCOLS = ['DES', 'AES', 'AES256'] as const const PRIV_PROTOCOLS = ['AES128', 'AES256'] as const
// ─── Component ────────────────────────────────────────────────────────────── // ─── Component ──────────────────────────────────────────────────────────────
@@ -52,11 +52,11 @@ export function ProfileTestPanel({ tenantId, profileId }: ProfileTestPanelProps)
const [snmpPort, setSnmpPort] = useState('161') const [snmpPort, setSnmpPort] = useState('161')
const [snmpVersion, setSnmpVersion] = useState<SNMPVersion>('v2c') const [snmpVersion, setSnmpVersion] = useState<SNMPVersion>('v2c')
const [community, setCommunity] = useState('public') const [community, setCommunity] = useState('public')
const [securityLevel, setSecurityLevel] = useState<SecurityLevel>('authNoPriv') const [securityLevel, setSecurityLevel] = useState<SecurityLevel>('auth_no_priv')
const [username, setUsername] = useState('') const [username, setUsername] = useState('')
const [authProtocol, setAuthProtocol] = useState('SHA') const [authProtocol, setAuthProtocol] = useState('SHA256')
const [authPassphrase, setAuthPassphrase] = useState('') const [authPassphrase, setAuthPassphrase] = useState('')
const [privProtocol, setPrivProtocol] = useState('AES') const [privProtocol, setPrivProtocol] = useState('AES128')
const [privPassphrase, setPrivPassphrase] = useState('') const [privPassphrase, setPrivPassphrase] = useState('')
// ─── Test mutation ─────────────────────────────────────────────────── // ─── Test mutation ───────────────────────────────────────────────────
@@ -82,11 +82,11 @@ export function ProfileTestPanel({ tenantId, profileId }: ProfileTestPanelProps)
} else { } else {
request.security_level = securityLevel request.security_level = securityLevel
if (username.trim()) request.username = username.trim() if (username.trim()) request.username = username.trim()
if (securityLevel === 'authNoPriv' || securityLevel === 'authPriv') { if (securityLevel === 'auth_no_priv' || securityLevel === 'auth_priv') {
request.auth_protocol = authProtocol request.auth_protocol = authProtocol
if (authPassphrase) request.auth_passphrase = authPassphrase if (authPassphrase) request.auth_passphrase = authPassphrase
} }
if (securityLevel === 'authPriv') { if (securityLevel === 'auth_priv') {
request.priv_protocol = privProtocol request.priv_protocol = privProtocol
if (privPassphrase) request.priv_passphrase = privPassphrase if (privPassphrase) request.priv_passphrase = privPassphrase
} }
@@ -197,7 +197,7 @@ export function ProfileTestPanel({ tenantId, profileId }: ProfileTestPanelProps)
</div> </div>
{/* Auth fields */} {/* Auth fields */}
{(securityLevel === 'authNoPriv' || securityLevel === 'authPriv') && ( {(securityLevel === 'auth_no_priv' || securityLevel === 'auth_priv') && (
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
<div> <div>
<Label className="text-xs">Auth Protocol</Label> <Label className="text-xs">Auth Protocol</Label>
@@ -227,7 +227,7 @@ export function ProfileTestPanel({ tenantId, profileId }: ProfileTestPanelProps)
)} )}
{/* Privacy fields */} {/* Privacy fields */}
{securityLevel === 'authPriv' && ( {securityLevel === 'auth_priv' && (
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
<div> <div>
<Label className="text-xs">Privacy Protocol</Label> <Label className="text-xs">Privacy Protocol</Label>

View File

@@ -551,6 +551,10 @@ export interface SNMPProfileResponse {
description: string | null description: string | null
is_system: boolean is_system: boolean
profile_data: Record<string, unknown> | null profile_data: Record<string, unknown> | null
sys_object_id: string | null
vendor: string | null
category: string | null
tenant_id: string | null
device_count: number device_count: number
created_at: string created_at: string
updated_at: string updated_at: string