21 KiB
Network Diagrams — Design Spec
Date: 2026-04-04 Status: Approved Scope: Standalone network diagram builder (Phase 1 — no FlowPilot integration)
Overview
A fully functional Network Diagram builder for MSP engineers. Engineers can manually build network topology diagrams with drag-and-drop device nodes and connections, or use AI to generate diagrams from plain English descriptions. Diagrams are team-scoped and filterable by client.
User-facing label: "Network Maps" in the sidebar.
Key Design Decisions
| Decision | Choice | Rationale |
|---|---|---|
| Device types | Database-driven with system defaults | MSPs serve different verticals with different gear. Extensibility from day one avoids a refactor. |
| Device type rendering | DB stores identity + metadata; frontend maps icons/colors by slug | Clean separation. Custom types get category-based default icons. Icon picker deferred to future. |
| Connection types | Fixed defaults + free-text "Custom..." option | Connection types are more standardized than devices. 6 defaults cover 95% of cases. |
| AI generation | Replace + Merge (Add to Diagram) modes | Iterative building is the natural workflow: "generate base" then "add VPN to branch office." |
| Export formats | PNG + PDF + JSON | PNG for sharing, PDF for formal client docs, JSON for backup/import/portability. |
| Client filter | Combobox/autocomplete | Type to filter, click to select from distinct client_name values. |
| State management | Local React state (not Zustand) | Matches AssistantChatPage pattern. Editor page owns nodes/edges/meta. |
| Scope | Standalone tool only | Future integration with FlowPilot sessions documented but not built. |
Data Model
device_types table
Stores system-default and team-custom device types.
CREATE TABLE device_types (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
slug VARCHAR(50) NOT NULL,
label VARCHAR(100) NOT NULL,
category VARCHAR(50) NOT NULL, -- network, compute, storage, cloud, endpoint, infrastructure, security
is_system BOOLEAN NOT NULL DEFAULT FALSE,
team_id UUID REFERENCES teams(id) ON DELETE CASCADE,
sort_order INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT uq_device_types_slug_team UNIQUE NULLS NOT DISTINCT (slug, team_id)
);
CREATE INDEX idx_device_types_team ON device_types(team_id);
is_system = true, team_id = NULLfor built-in typesis_system = false, team_id = <uuid>for team-custom typesslugis the key used in diagram node data and the frontend rendering registry
System device type seed data
| Slug | Label | Category |
|---|---|---|
router |
Router | network |
switch |
Switch | network |
firewall |
Firewall | network |
access-point |
Access Point | network |
load-balancer |
Load Balancer | network |
server |
Server | compute |
workstation |
Workstation | compute |
vm |
Virtual Machine | compute |
container |
Container | compute |
nas |
NAS | storage |
san |
SAN | storage |
cloud-storage |
Cloud Storage | storage |
cloud |
Cloud | cloud |
aws |
AWS | cloud |
azure |
Azure | cloud |
gcp |
Google Cloud | cloud |
printer |
Printer | endpoint |
phone |
Phone | endpoint |
iot |
IoT Device | endpoint |
camera |
Camera | endpoint |
tablet |
Tablet | endpoint |
laptop |
Laptop | endpoint |
ups |
UPS | infrastructure |
pdu |
PDU | infrastructure |
rack |
Rack | infrastructure |
patch-panel |
Patch Panel | infrastructure |
nvr |
NVR | security |
badge-reader |
Badge Reader | security |
network_diagrams table
CREATE TABLE network_diagrams (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
team_id UUID NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
client_name VARCHAR(255),
asset_name VARCHAR(255),
description TEXT,
nodes JSONB NOT NULL DEFAULT '[]',
edges JSONB NOT NULL DEFAULT '[]',
thumbnail_url TEXT,
is_archived BOOLEAN DEFAULT FALSE,
created_by UUID REFERENCES users(id),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_network_diagrams_team ON network_diagrams(team_id);
CREATE INDEX idx_network_diagrams_client ON network_diagrams(team_id, client_name);
JSONB Node Structure
{
"id": "unique-string",
"type": "router",
"label": "Core Router",
"position": { "x": 400, "y": 200 },
"properties": {
"hostname": "core-rtr-01",
"ip": "10.0.0.1",
"subnet": "10.0.0.0/24",
"vendor": "Cisco",
"model": "ISR 4331",
"role": "Core gateway",
"vlan": "1",
"notes": "",
"status": "online"
}
}
typefield stores the device typeslug(string, not enum)statusis one of:unknown,online,offline,degraded
JSONB Edge Structure
{
"id": "unique-string",
"source": "node-id",
"target": "node-id",
"label": "Uplink to Core",
"connectionType": "ethernet",
"speed": "1 Gbps",
"notes": "Port Gi0/1 -> Gi0/24"
}
connectionTypeis a free string. Standard defaults:ethernet,fiber,wifi,vpn,vlan,wanspeedandnotesare additional edge metadata fields (not in original spec, added for MSP usefulness)
JSON Export Schema
{
"schemaVersion": 1,
"name": "Office Network",
"client_name": "Acme Corp",
"description": "Main office topology",
"nodes": [...],
"edges": [...],
"exportedAt": "2026-04-04T12:00:00Z"
}
API Endpoints
Device Types
All endpoints require authentication via get_current_active_user.
| Method | Path | Description |
|---|---|---|
GET |
/api/v1/device-types/ |
List all (system + team custom), ordered by category then sort_order |
POST |
/api/v1/device-types/ |
Create custom type for team |
PUT |
/api/v1/device-types/{id} |
Update custom type (team-owned only) |
DELETE |
/api/v1/device-types/{id} |
Delete custom type (team-owned only, fails if is_system) |
Network Diagrams
All endpoints team-scoped via get_current_active_user. Team ownership verified on every single-resource operation.
| Method | Path | Description |
|---|---|---|
POST |
/api/v1/network-diagrams/ |
Create diagram |
GET |
/api/v1/network-diagrams/ |
List for team (exclude archived). Query params: client_name, search |
GET |
/api/v1/network-diagrams/{id} |
Get single diagram |
PUT |
/api/v1/network-diagrams/{id} |
Update diagram |
DELETE |
/api/v1/network-diagrams/{id} |
Soft delete (set is_archived=True) |
POST |
/api/v1/network-diagrams/{id}/duplicate |
Duplicate diagram (new name: "Copy of {name}") |
POST |
/api/v1/network-diagrams/import |
Import from JSON file |
GET |
/api/v1/network-diagrams/{id}/export |
Export as JSON |
POST |
/api/v1/network-diagrams/ai-generate |
AI diagram generation |
GET |
/api/v1/network-diagrams/clients |
List distinct client_name values for team (powers combobox) |
AI Generate Endpoint
Request:
{
"description": "Small office with a firewall, core switch, 3 access points, and a file server",
"client_name": "Acme Corp",
"mode": "replace",
"existingBounds": null
}
For merge mode:
{
"description": "Add a VPN tunnel to a branch office with 2 workstations",
"client_name": "Acme Corp",
"mode": "merge",
"existingBounds": { "minX": 0, "maxX": 800, "minY": 0, "maxY": 600 }
}
System prompt includes:
- The generation rules from the original spec (JSON-only response, topology layout guidelines)
- The list of available device type slugs (system + team custom)
- For merge mode: existing node positions and instruction to place new nodes in available space
Response:
{
"nodes": [...],
"edges": [...],
"suggestedName": "Acme Corp - Main Office",
"notes": "Assumed site-to-site VPN for branch office connection"
}
Error handling:
- JSON parse failure: 422 "AI generated an invalid response, please try again"
- Unknown device type slug: silently fall back to closest category match
- Timeout: 504 "Generation took too long"
Frontend Architecture
New Files
frontend/src/
├── api/
│ ├── deviceTypes.ts # Device types API client
│ └── networkDiagrams.ts # Network diagrams API client
├── components/network/
│ ├── nodes/
│ │ ├── deviceRegistry.ts # Slug → { icon, color } mapping + category defaults
│ │ ├── DeviceNode.tsx # React Flow custom node
│ │ └── nodeTypes.ts # React Flow node type registry
│ ├── edges/
│ │ └── ConnectionEdge.tsx # Custom edge with connection-type styling
│ ├── panels/
│ │ ├── DeviceToolbar.tsx # Left panel — categorized, searchable, draggable
│ │ ├── PropertiesPanel.tsx # Right panel — node/edge property editor
│ │ └── AIAssistPanel.tsx # Bottom collapsible — AI generation
│ ├── NetworkCanvas.tsx # React Flow wrapper
│ └── DiagramHeader.tsx # Top bar — name, save, export
├── pages/NetworkDiagrams/
│ ├── index.tsx # List/dashboard page
│ └── DiagramEditor.tsx # Full editor page (assembles all panels)
└── types/
└── networkDiagram.ts # TypeScript interfaces
Modified Files
frontend/src/components/layout/Sidebar.tsx— add "Network Maps" nav itemfrontend/src/router.tsx— add routesfrontend/src/api/index.ts— export new API modulesfrontend/src/types/index.ts— export new typesbackend/app/api/router.py— register new routersbackend/app/models/__init__.py— export new models (if pattern requires)
Page Layouts
List Page (/network-diagrams)
┌──────────────────────────────────────────────────────────────┐
│ Network Maps [Import] [New Diagram]│
│ Visual network topology documentation for your clients │
├──────────────────────────────────────────────────────────────┤
│ [🔍 Search diagrams...] [Client ▾ combobox filter] │
├──────────────────────────────────────────────────────────────┤
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Diagram │ │ Diagram │ │ Diagram │ │
│ │ Card │ │ Card │ │ Card │ │
│ │ │ │ │ │ │ │
│ │ name │ │ name │ │ name │ │
│ │ client │ │ client │ │ client │ │
│ │ 12 devs │ │ 8 devs │ │ 5 devs │ │
│ │ ⋯ menu │ │ ⋯ menu │ │ ⋯ menu │ │
│ └──────────┘ └──────────┘ └──────────┘ │
└──────────────────────────────────────────────────────────────┘
Card three-dot menu: Open, Duplicate, Export JSON, Export PNG, Export PDF, Archive
Editor Page (/network-diagrams/new, /network-diagrams/:id)
┌─────────────────────────────────────────────────────────────┐
│ ← Network Maps │ Diagram Name [Acme Corp] │ Save │ Export│
├──────────┬──────────────────────────────┬───────────────────┤
│ [🔍] │ │ Properties Panel │
│ Network │ │ (when selected) │
│ ├ Router│ React Flow Canvas │ │
│ ├ Switch│ (dot background) │ Hostname [____] │
│ └ ... │ (minimap bottom-right) │ IP [____] │
│ Compute │ (controls top-left) │ Subnet [____] │
│ ├ Server│ │ Vendor [____] │
│ └ ... │ │ ... │
│ ... │ │ │
│ [+Custom│ │ [Delete Device] │
│ Type] │ │ │
├──────────┴──────────────────────────────┴───────────────────┤
│ [✨ AI Generate] (collapsed) ──or── expanded textarea │
└─────────────────────────────────────────────────────────────┘
Component Details
DeviceToolbar (left, 200px)
- Search box at top — filters across all categories
- Collapsible category sections (Network, Compute, Storage, Cloud, Endpoint, Infrastructure, Security)
- Each device: draggable card with icon + label
- HTML5 drag-and-drop —
onDragStartsets device type slug in dataTransfer - "+ Custom Type" button at bottom — opens inline form (slug, label, category dropdown)
- Fetches device types from API on mount
bg-sidebarbackground,border-defaultright border
DeviceNode (React Flow custom node)
- Renders device icon from registry (resolved by slug)
- Label below icon
- IP address below label (JetBrains Mono, muted color) if set
- Status dot: green=online, red=offline, yellow=degraded, gray=unknown
- Connection handles (top, bottom, left, right) — visible on hover
- Selected: border changes to accent blue
bg-card,border-default, 8px radius, min-width 120px
ConnectionEdge (React Flow custom edge)
- Renders as smoothstep edge
- Visual style varies by connectionType:
ethernet: solid blue linefiber: solid green, slightly thickerwifi: dotted purplevpn: dashed yellowvlan: solid graywan: dashed red- custom: solid default with label
- Shows connection label on the edge
PropertiesPanel (right, 260px)
- Node selected: hostname, IP, subnet, vendor, model, role, VLAN, notes (text inputs), status (styled dropdown)
- Edge selected: label, connectionType (dropdown with "Custom..." option), speed, notes
- Nothing selected: "Select a device to edit its properties"
- Changes update node/edge data immediately (controlled inputs)
- "Delete" button at bottom (red accent)
bg-sidebar,border-defaultleft border
AIAssistPanel (bottom, collapsible)
- Collapsed: "AI Generate" button in a slim bar
- Expanded: textarea, mode toggle ("Generate New" / "Add to Diagram"), "Generate" button
- Replace mode warning: "This will replace your current diagram. Save first if needed."
- Loading: animated pulse + "Generating your network diagram..."
- Success: replaces/merges canvas, shows toast with suggested name
- Error: inline error message
DiagramHeader (top, 56px)
- Back button:
← Network Mapsnavigates to list page - Inline-editable diagram name (click to edit, Enter to confirm)
- Client name badge (pill) if set
- Save button (primary, "Saving..." state)
- Export dropdown: PNG, PDF, JSON
- Last saved timestamp (muted, right side)
bg-card,border-defaultbottom border
State Management
DiagramEditor page owns all state via useState:
nodes/edges— managed by React Flow'suseNodesState/useEdgesStatediagramMeta— name, client_name, asset_name, descriptionisDirty— tracks unsaved changeslastSavedAt— timestamp for header displayselectedNode/selectedEdge— drives PropertiesPanel
Auto-save: setInterval at 30 seconds, only fires if isDirty is true. Clears dirty flag on save. Does not fire for new unsaved diagrams (must explicitly save first to create the record).
Export Implementation
- PNG: Client-side using the Canvas API. Render the React Flow viewport to an offscreen canvas via
ReactFlow.toObject()+ manual SVG-to-canvas rendering, thencanvas.toBlob()for download. No external library needed. - PDF: Client-side using
window.print()with a print-specific stylesheet. Opens a print-optimized view of the diagram with metadata header (name, client, description, date, device count) and device legend. The user selects "Save as PDF" in the browser print dialog. No external library needed. - JSON: API call to
GET /network-diagrams/{id}/export, triggers file download
Import Implementation
- "Import" button on list page opens file picker (
.jsononly) - Uploads to
POST /network-diagrams/import - Backend validates schema version and structure
- Creates new diagram for the team
- Response includes warnings for unknown device type slugs
- On success, navigates to the new diagram's editor
Navigation Integration
Sidebar
Add "Network Maps" to both sidebar modes:
Rail groups (icon-only mode): Add as a child under the Flows group:
{
href: '/trees', icon: GitBranch, label: 'Flows', shortLabel: 'Flows',
children: [
{ href: '/trees', label: 'Flow Library' },
{ href: '/trees?type=procedural', label: 'Projects' },
{ href: '/network-diagrams', label: 'Network Maps' }, // NEW
{ href: '/step-library', label: 'Solutions Library' },
{ href: '/review-queue', label: 'Review Queue' },
],
}
Pinned sections: Add under KNOWLEDGE:
{
title: 'KNOWLEDGE',
items: [
{ href: '/trees', icon: GitBranch, label: 'Flow Library', ... },
{ href: '/network-diagrams', icon: Network, label: 'Network Maps', shortLabel: 'NetMap' }, // NEW
{ href: '/scripts', icon: Code2, label: 'Scripts', ... },
...
],
}
Routes
const NetworkDiagramsPage = lazyWithRetry(() => import('@/pages/NetworkDiagrams'))
const DiagramEditorPage = lazyWithRetry(() => import('@/pages/NetworkDiagrams/DiagramEditor'))
// Inside ProtectedRoute/AppLayout children:
{ path: 'network-diagrams', element: page(NetworkDiagramsPage) },
{ path: 'network-diagrams/new', element: page(DiagramEditorPage) },
{ path: 'network-diagrams/:id', element: page(DiagramEditorPage) },
Future Integration Roadmap
These features are documented for future implementation. They are NOT in scope for Phase 1.
Phase 2: FlowPilot Context Integration
When a FlowPilot session has a client_name matching a saved network diagram, the diagram can be surfaced as read-only reference context. The AI copilot gains topology awareness — it can reference specific devices, connections, and network segments when guiding troubleshooting.
Phase 3: Live Status Overlay
Device status indicators updated from PSA/monitoring integrations (ConnectWise, Datto RMM, etc.). Green/red/yellow dots reflect real-time device state. Engineers see at-a-glance which devices are down before starting troubleshooting.
Phase 4: Session-Linked Diagrams
Attach a diagram to a FlowPilot session. During troubleshooting, highlight the device being investigated. Mark devices as "checked" or "problem found." Session resolution includes a diagram snapshot showing the problem path.
Phase 5: Custom Device Type Icons
Icon picker UI for team-custom device types. Optional SVG upload for vendor logos or specialized equipment icons (with SVG sanitization for security).
Phase 6: Diagram Templates
Pre-built topology templates as starting points: Small Office, Branch + HQ, Data Center, Remote Worker, MSP NOC. Templates include placeholder devices with typical connections.
Phase 7: Collaborative Editing
Multiple engineers editing the same diagram simultaneously. Requires WebSocket infrastructure (Yjs or similar CRDT). Cursor presence, conflict resolution, real-time sync.
Quality Requirements
- Every file fully typed — no
any, no untyped props - No inline styles — Tailwind classes only, using design system CSS variable tokens
- No placeholder TODOs — build the real implementation
- All API calls handle loading, error, and success states
- Team-scoping enforced on every backend endpoint
- Ownership verification on every GET/PUT/DELETE by ID
- Editor handles both "new diagram" and "edit existing" modes
- Auto-save only fires when dirty
- No new npm packages — use only what's installed
- Follow existing code style exactly