PortGrid - Modern Network Port Visualization
Modern Network Port Visualization
I built PortGrid from scratch to solve a real problem in my production environment. The existing tool (Switchmap) was dead - last updated in 2016, Perl dependencies rotting, and worst of all, it ran redundant SNMP polls against switches that LibreNMS was already monitoring. I designed and developed a modern replacement that pulls directly from the LibreNMS API, eliminating duplicate network load while providing real-time port visualization, instant search, and drag-and-drop device organization.
This is my project. I identified the problem, architected the solution, wrote every line of code, and deployed it to production. The full source is available at github.com/solomonneas/portgrid.
This project is open source and available on GitHub
Switchmap was the go-to tool for network port visualization. Perl-based, it generated static HTML pages showing port status and connected devices. But the last commit was 2016. Perl dependencies rotted. Modern switches weren't fully supported. The web interface looked like it came from 2005.
Here's what bothered me: LibreNMS already polls every switch via SNMP. It knows port status, MAC tables, LLDP neighbors - everything. Why run a second poller (Switchmap) against the same devices? That's duplicate network load and duplicate maintenance. The data already exists.
Switchmap regenerated HTML files on a cron schedule. If a port went down between runs, you wouldn't know for an hour. No search. No filtering. No device organization. Just a directory listing of static HTML files per switch.
- Perl + SNMP polling (redundant with LibreNMS)
- Static HTML generation via cron
- No search or filtering capability
- No device grouping or organization
- Unstyled table output
- Last updated: 2016
- Built on LibreNMS API (zero polling overhead)
- 60-second auto-refresh I implemented
- Instant search I designed from scratch
- Drag-and-drop grouping with dnd-kit
- Modern UI with dark/light mode
- Actively maintained: 2026
Modern JavaScript ecosystem with React 19 and Next.js 15 for optimal developer experience and performance:
Modern React framework with API routes for backend proxy to LibreNMS
Latest React with concurrent features for responsive port table rendering
Sortable, paginated port grids with 50+ ports per device without lag
60-second stale time with auto-refetch for near real-time port status
Device grouping via drag-to-combine UI for organizing switch views
REST API for ports, devices, and LLDP/CDP neighbor discovery
01 LibreNMS API Adapter
Parallel Data Fetching
// Fetch ports, devices, and links in parallel const [portsRes, devicesRes, linksRes] = await Promise.all([ fetch(`${baseUrl}/api/v0/ports?columns=${portColumns}`), fetch(`${baseUrl}/api/v0/devices?columns=${deviceColumns}`), fetch(`${baseUrl}/api/v0/links`).catch(() => null) ]); Device Filtering (Glob Patterns)
# Environment variables DEVICE_EXCLUDE=test*,spare*,10.0.0.* DEVICE_INCLUDE=core-*,access-* // Glob matching with wildcard support function matchPattern(value: string, pattern: string) const regex = pattern.replace(/\*/g, '.*'); return new RegExp(`^${regex}$`, 'i').test(value);
LibreNMS requires explicit ?columns= parameter to include fields like device_id.
Without it, many fields are omitted from the response. The links endpoint is optional and gracefully
degrades if LLDP/CDP data is unavailable.
02 Real-Time Updates with TanStack Query
Query Configuration
// QueryProvider setup const queryClient = new QueryClient( defaultOptions: queries: staleTime: 60 * 1000, // 60 seconds refetchInterval: 60 * 1000, // Auto-refresh , , ); usePorts Hook
export function usePorts() return useQuery( queryKey: ["ports"], queryFn: async () => const res = await fetch("/api/ports"); return res.json(); , ); 03 Drag-and-Drop Device Grouping
Group Logic (dnd-kit)
// Case 1: Both ungrouped → Create new group // Case 2: Drag ungrouped → grouped → Add to group // Case 3: Drag grouped → ungrouped → Add to group // Case 4: Different groups → Move to target // DragOverlay uses plain div (not AccordionItem) // to avoid Radix context errors localStorage Persistence
// Device groups structure interface DeviceGroup id: string; name: string; deviceIds: number[]; // Key: portgrid-device-groups Groups auto-dissolve when only one device remains. The DragOverlay component renders outside the Accordion context, so using AccordionItem causes React context errors - solved by using a plain div for the overlay content.
04 Hydration-Safe localStorage
Mounted State Pattern
const [mounted, setMounted] = useState(false); const [notes, setNotes] = useState(); useEffect(() => // Only access localStorage after mount const stored = localStorage.getItem(STORAGE_KEY); if (stored) setNotes(JSON.parse(stored)); setMounted(true); , []); // Only persist when mounted useEffect(() => if (mounted) localStorage.setItem(STORAGE_KEY, JSON.stringify(notes)); , [notes, mounted]); React Server Components run on the server where localStorage doesn't exist. The mounted state pattern prevents hydration mismatches by deferring localStorage access until the component is client-side.
The installer script automates the entire deployment process on Proxmox VE. Run from the Proxmox host shell:
bash -c "$(curl -fsSL https://raw.githubusercontent.com/solomonneas/portgrid/main/scripts/install-lxc.sh)" Environment Configuration
# /opt/portgrid/.env.local DATA_SOURCE=librenms LIBRENMS_URL=https://librenms.example.com LIBRENMS_API_TOKEN=your-api-token-here # Optional: Device filtering DEVICE_EXCLUDE=test*,spare* DEVICE_INCLUDE=core-*,access-* - Next.js API routes
- React server/client components
- REST API integration
- TanStack Query for server state
- Custom hooks (usePortNotes, usePorts)
- Hydration-safe localStorage
- TanStack Table with pagination
- Status color coding
- Sortable columns
- Drag-and-drop with dnd-kit
- Inline editing components
- Dark mode with next-themes
- Proxmox LXC automation
- Systemd service creation
- Environment-based configuration
I've open-sourced the entire project. Every component, every hook, every line - written by me. MIT licensed so others facing the same Switchmap problem can use it too.
$ git clone https://github.com/solomonneas/portgrid.git 32 commits- ✓ LibreNMS API integration complete
- ✓ Real-time port status with auto-refresh
- ✓ Instant search across all fields
- ✓ Drag-and-drop device grouping
- ✓ Inline port notes with localStorage persistence
- ✓ Dark/light mode with system detection
- ✓ Proxmox LXC installer script
- ✓ Production deployment at Polk State