import { FormEvent, useEffect, useMemo, useState } from "react"; import { CheckSquare, Network, RefreshCw, Router, Search, Square } from "lucide-react"; import { api } from "../api/client"; import { Button } from "../components/Button"; import type { SnmpCredentialProfile, SnmpDiscoveredInterface, SnmpDiscoveryItem, SnmpDiscoveryResult } from "../types/api"; interface DiscoveryPageProps { token: string; } export function DiscoveryPage({ token }: DiscoveryPageProps) { const [profiles, setProfiles] = useState([]); const [host, setHost] = useState(""); const [profileId, setProfileId] = useState(""); const [result, setResult] = useState(null); const [selectedItemIds, setSelectedItemIds] = useState>(new Set()); const [loadingProfiles, setLoadingProfiles] = useState(false); const [discovering, setDiscovering] = useState(false); const [message, setMessage] = useState(null); const selectedItems = useMemo( () => (result?.monitorable_items ?? []).filter((item) => selectedItemIds.has(item.item_id)), [result, selectedItemIds] ); const groupedItems = useMemo(() => groupItems(result?.monitorable_items ?? []), [result]); async function refreshProfiles() { setLoadingProfiles(true); try { const nextProfiles = await api.snmpCredentialProfiles(token); setProfiles(nextProfiles); setProfileId((current) => current || nextProfiles[0]?.id || ""); } finally { setLoadingProfiles(false); } } useEffect(() => { refreshProfiles().catch(() => setProfiles([])); }, [token]); async function submit(event: FormEvent) { event.preventDefault(); if (!profileId) return; setDiscovering(true); setMessage(null); setResult(null); setSelectedItemIds(new Set()); try { const discovered = await api.discoverSnmpDevice(token, { host, credential_profile_id: profileId, }); setResult(discovered); } catch (err) { setMessage(err instanceof Error ? err.message : "SNMP discovery failed"); } finally { setDiscovering(false); } } function toggleItem(itemId: string) { setSelectedItemIds((current) => { const next = new Set(current); if (next.has(itemId)) { next.delete(itemId); } else { next.add(itemId); } return next; }); } function selectGroup(items: SnmpDiscoveryItem[]) { setSelectedItemIds((current) => { const next = new Set(current); const allSelected = items.every((item) => next.has(item.item_id)); for (const item of items) { if (allSelected) { next.delete(item.item_id); } else { next.add(item.item_id); } } return next; }); } return (

Discovery

Find SNMP devices and choose friendly items for monitoring.

SNMP Device Discovery

{message ?
{message}
: null} {!profiles.length ?
Add an SNMP credential profile before running discovery.
: null}

Device Summary

{result ? (
Description
{result.description || "No description reported"}
) : (
Run discovery to see device details.
)}

Interfaces

{result?.interfaces.length ? ( result.interfaces.map((item) => (
{item.label}
{interfaceContext(item)}
{formatSpeed(item.speed_bps)}
)) ) : (
{result ? "No interfaces reported." : "No discovery results yet."}
)}

Monitorable Items

{selectedItems.length} selected
{result ? ( groupedItems.map(({ group, items }) => (

{group}

{items.map((item) => ( ))}
)) ) : (
Run discovery to choose monitorable items.
)}
); } function groupItems(items: SnmpDiscoveryItem[]) { const groups = new Map(); for (const item of items) { groups.set(item.group, [...(groups.get(item.group) ?? []), item]); } return Array.from(groups, ([group, groupItems]) => ({ group, items: groupItems })); } function SummaryItem({ label, value }: { label: string; value: string }) { return (
{label}
{value}
); } function Status({ value }: { value: string }) { const classes = value === "up" ? "border-teal-500/40 bg-teal-950/40 text-teal-200" : value === "down" ? "border-red-500/40 bg-red-950/40 text-red-200" : "border-slate-600 bg-slate-900 text-slate-300"; return {value}; } function formatDuration(seconds?: number | null) { if (!seconds) return "Unknown"; const days = Math.floor(seconds / 86400); const hours = Math.floor((seconds % 86400) / 3600); if (days) return `${days}d ${hours}h`; return `${hours}h ${Math.floor((seconds % 3600) / 60)}m`; } function formatSpeed(value?: number | null) { if (!value) return "Unknown speed"; if (value >= 1_000_000_000) return `${value / 1_000_000_000} Gbps`; if (value >= 1_000_000) return `${value / 1_000_000} Mbps`; if (value >= 1_000) return `${value / 1_000} Kbps`; return `${value} bps`; } function friendlyItemType(value: string) { return value.replaceAll("_", " "); } function interfaceContext(item: SnmpDiscoveredInterface) { const details = [item.name, item.description].filter((value, index, values) => value && values.indexOf(value) === index); return details.length ? details.join(" - ") : "No description"; } function formatCapabilities(capabilities: Record) { const labels: Record = { interfaces: "interfaces", cpu: "CPU", memory: "memory", storage: "storage", sensors: "sensors", }; const active = Object.entries(labels) .filter(([key]) => capabilities[key]) .map(([, label]) => label); return active.length ? active.join(", ") : "System only"; }