feat: network diagrams UX overhaul — icons, empty canvas, properties panel

- Colorize: semantic category colors for all device types (network=blue,
  security=orange, compute=emerald, endpoint=amber, storage=violet,
  cloud=cyan, infra=steel); better icons (Router, ShieldAlert, Boxes,
  Package, Gauge, PlugZap, Video, Radio); MiniMap uses category colors
- Onboard: centered AI generate prompt on empty canvas with 5 MSP-specific
  example chips, ⌘↵ shortcut, spinner; AIAssistPanel only shown with nodes
- Arrange: properties panel — status badge grid at top, fields grouped into
  Network (IP/Subnet/VLAN) and Hardware (Hostname/Vendor/Model/Role) sections
- Delight: segmented topology color bar on listing cards; backend returns
  category_counts via single extra query on list endpoint
- Harden: real PNG export via html-to-image + getNodesBounds/getViewportForBounds
- Polish: ChevronDown replaces unicode ▾, click-outside for client filter,
  consistent spinner in empty prompt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-04-05 00:55:03 +00:00
parent 0dc2801916
commit dd95b8892c
11 changed files with 446 additions and 108 deletions

View File

@@ -1,9 +1,9 @@
import type { LucideIcon } from 'lucide-react'
import {
Network, Layers, Shield, Wifi, Server, Monitor, Box, Cloud,
Printer, Smartphone, HardDrive, Scale, Database, CloudCog,
Cpu, Tablet, Laptop, BatteryCharging, LayoutGrid, RectangleVertical,
Cable, Camera, KeyRound, Globe,
Router, Layers, ShieldAlert, Wifi, Server, Monitor, Boxes, Package, Cloud,
Printer, Smartphone, HardDrive, Gauge, Database, CloudCog,
Cpu, Tablet, Laptop, BatteryCharging, RectangleVertical,
Cable, Camera, KeyRound, Globe, Video, PlugZap, Radio,
} from 'lucide-react'
export interface DeviceRenderConfig {
@@ -11,49 +11,78 @@ export interface DeviceRenderConfig {
color: string
}
// Category-semantic color palette — each color carries meaning:
// Network (blue) — backbone connectivity layer
// Security (orange) — critical/protective elements
// Compute (emerald)— running workloads and VMs
// Endpoint (amber) — user-facing devices
// Storage (violet) — data at rest
// Cloud (cyan) — external/internet-connected
// Infra (steel) — physical/passive hardware
export const NETWORK_COLOR = '#60a5fa'
export const SECURITY_COLOR = '#f97316'
export const COMPUTE_COLOR = '#34d399'
export const ENDPOINT_COLOR = '#fbbf24'
export const STORAGE_COLOR = '#a78bfa'
export const CLOUD_COLOR = '#67e8f9'
export const INFRA_COLOR = '#94a3b8'
const SYSTEM_DEVICE_ICONS: Record<string, DeviceRenderConfig> = {
'router': { icon: Network, color: 'var(--color-accent)' },
'switch': { icon: Layers, color: 'var(--color-text-muted-foreground)' },
'firewall': { icon: Shield, color: 'var(--color-accent)' },
'access-point': { icon: Wifi, color: 'var(--color-text-muted-foreground)' },
'load-balancer': { icon: Scale, color: 'var(--color-text-muted-foreground)' },
'server': { icon: Server, color: 'var(--color-text-muted-foreground)' },
'workstation': { icon: Monitor, color: 'var(--color-text-muted-foreground)' },
'vm': { icon: Box, color: 'var(--color-text-muted-foreground)' },
'container': { icon: Cpu, color: 'var(--color-text-muted-foreground)' },
'nas': { icon: Database, color: 'var(--color-text-muted-foreground)' },
'san': { icon: HardDrive, color: 'var(--color-text-muted-foreground)' },
'cloud-storage': { icon: CloudCog, color: 'var(--color-text-muted-foreground)' },
'cloud': { icon: Cloud, color: 'var(--color-text-muted-foreground)' },
'aws': { icon: Cloud, color: 'var(--color-text-muted-foreground)' },
'azure': { icon: Cloud, color: 'var(--color-text-muted-foreground)' },
'gcp': { icon: Cloud, color: 'var(--color-text-muted-foreground)' },
'isp': { icon: Globe, color: 'var(--color-accent)' },
'printer': { icon: Printer, color: 'var(--color-text-muted-foreground)' },
'phone': { icon: Smartphone, color: 'var(--color-text-muted-foreground)' },
'iot': { icon: HardDrive, color: 'var(--color-text-muted-foreground)' },
'camera': { icon: Camera, color: 'var(--color-text-muted-foreground)' },
'tablet': { icon: Tablet, color: 'var(--color-text-muted-foreground)' },
'laptop': { icon: Laptop, color: 'var(--color-text-muted-foreground)' },
'ups': { icon: BatteryCharging, color: 'var(--color-text-muted-foreground)' },
'pdu': { icon: LayoutGrid, color: 'var(--color-text-muted-foreground)' },
'rack': { icon: RectangleVertical, color: 'var(--color-text-muted-foreground)' },
'patch-panel': { icon: Cable, color: 'var(--color-text-muted-foreground)' },
'nvr': { icon: Camera, color: 'var(--color-text-muted-foreground)' },
'badge-reader': { icon: KeyRound, color: 'var(--color-text-muted-foreground)' },
// Network layer
'router': { icon: Router, color: NETWORK_COLOR },
'switch': { icon: Layers, color: NETWORK_COLOR },
'access-point': { icon: Wifi, color: NETWORK_COLOR },
'load-balancer': { icon: Gauge, color: NETWORK_COLOR },
// Security
'firewall': { icon: ShieldAlert, color: SECURITY_COLOR },
'badge-reader': { icon: KeyRound, color: SECURITY_COLOR },
// Compute
'server': { icon: Server, color: COMPUTE_COLOR },
'vm': { icon: Boxes, color: COMPUTE_COLOR },
'container': { icon: Package, color: COMPUTE_COLOR },
// Storage
'nas': { icon: Database, color: STORAGE_COLOR },
'san': { icon: HardDrive, color: STORAGE_COLOR },
'cloud-storage': { icon: CloudCog, color: STORAGE_COLOR },
// Cloud / Internet
'cloud': { icon: Cloud, color: CLOUD_COLOR },
'aws': { icon: Cloud, color: CLOUD_COLOR },
'azure': { icon: Cloud, color: CLOUD_COLOR },
'gcp': { icon: Cloud, color: CLOUD_COLOR },
'isp': { icon: Globe, color: CLOUD_COLOR },
// Endpoints
'workstation': { icon: Monitor, color: ENDPOINT_COLOR },
'laptop': { icon: Laptop, color: ENDPOINT_COLOR },
'tablet': { icon: Tablet, color: ENDPOINT_COLOR },
'phone': { icon: Smartphone, color: ENDPOINT_COLOR },
'printer': { icon: Printer, color: ENDPOINT_COLOR },
// Infrastructure / physical
'ups': { icon: BatteryCharging, color: INFRA_COLOR },
'pdu': { icon: PlugZap, color: INFRA_COLOR },
'rack': { icon: RectangleVertical, color: INFRA_COLOR },
'patch-panel': { icon: Cable, color: INFRA_COLOR },
'camera': { icon: Camera, color: INFRA_COLOR },
'nvr': { icon: Video, color: INFRA_COLOR },
'iot': { icon: Radio, color: INFRA_COLOR },
}
const CATEGORY_DEFAULTS: Record<string, DeviceRenderConfig> = {
'network': { icon: Network, color: 'var(--color-text-muted-foreground)' },
'compute': { icon: Server, color: 'var(--color-text-muted-foreground)' },
'storage': { icon: Database, color: 'var(--color-text-muted-foreground)' },
'cloud': { icon: Cloud, color: 'var(--color-text-muted-foreground)' },
'endpoint': { icon: Monitor, color: 'var(--color-text-muted-foreground)' },
'infrastructure': { icon: LayoutGrid, color: 'var(--color-text-muted-foreground)' },
'security': { icon: Shield, color: 'var(--color-text-muted-foreground)' },
'network': { icon: Router, color: NETWORK_COLOR },
'compute': { icon: Server, color: COMPUTE_COLOR },
'storage': { icon: Database, color: STORAGE_COLOR },
'cloud': { icon: Cloud, color: CLOUD_COLOR },
'endpoint': { icon: Monitor, color: ENDPOINT_COLOR },
'infrastructure': { icon: PlugZap, color: INFRA_COLOR },
'security': { icon: ShieldAlert, color: SECURITY_COLOR },
}
const FALLBACK: DeviceRenderConfig = { icon: Box, color: 'var(--color-text-muted-foreground)' }
const FALLBACK: DeviceRenderConfig = { icon: Cpu, color: INFRA_COLOR }
export function getDeviceRenderConfig(slug: string, category?: string): DeviceRenderConfig {
if (SYSTEM_DEVICE_ICONS[slug]) return SYSTEM_DEVICE_ICONS[slug]
@@ -62,13 +91,23 @@ export function getDeviceRenderConfig(slug: string, category?: string): DeviceRe
}
export const CATEGORY_LABELS: Record<string, string> = {
'network': 'Network',
'compute': 'Compute',
'storage': 'Storage',
'cloud': 'Cloud',
'endpoint': 'Endpoints',
'network': 'Network',
'compute': 'Compute',
'storage': 'Storage',
'cloud': 'Cloud',
'endpoint': 'Endpoints',
'infrastructure': 'Infrastructure',
'security': 'Security',
'security': 'Security',
}
export const CATEGORY_COLORS: Record<string, string> = {
'network': NETWORK_COLOR,
'compute': COMPUTE_COLOR,
'storage': STORAGE_COLOR,
'cloud': CLOUD_COLOR,
'endpoint': ENDPOINT_COLOR,
'infrastructure': INFRA_COLOR,
'security': SECURITY_COLOR,
}
export const CATEGORY_ORDER = ['network', 'compute', 'storage', 'cloud', 'endpoint', 'infrastructure', 'security']