feat: add device registry, DeviceNode, ConnectionEdge for React Flow

Creates the React Flow building blocks for the network diagram editor:
device type registry with icon/color mappings, DeviceNode component with
status indicators and connection handles, ConnectionEdge with per-type
styling, and nodeTypes/edgeTypes registration maps.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-04-04 07:50:10 +00:00
parent 1ec7bbbbd3
commit 354b44844c
5 changed files with 188 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
import { memo } from 'react'
import { Handle, Position, type NodeProps } from '@xyflow/react'
import { cn } from '@/lib/utils'
import { getDeviceRenderConfig } from './deviceRegistry'
import type { DeviceProperties } from '@/types'
export interface DeviceNodeData {
label: string
deviceType: string
category?: string
properties: DeviceProperties
[key: string]: unknown
}
const STATUS_COLORS: Record<string, string> = {
online: 'bg-emerald-400',
offline: 'bg-red-400',
degraded: 'bg-yellow-400',
unknown: 'bg-gray-500',
}
function DeviceNodeComponent({ data, selected }: NodeProps) {
const nodeData = data as unknown as DeviceNodeData
const { icon: Icon, color } = getDeviceRenderConfig(nodeData.deviceType, nodeData.category)
const status = nodeData.properties?.status || 'unknown'
const ip = nodeData.properties?.ip
return (
<div
className={cn(
'relative flex min-w-[120px] flex-col items-center gap-1 rounded-lg border bg-card px-4 py-3',
'border-default transition-colors',
'group',
selected && 'border-accent',
)}
>
<div className={cn('absolute right-2 top-2 h-2 w-2 rounded-full', STATUS_COLORS[status])} />
<Icon size={28} style={{ color }} />
<span className="text-center text-xs font-medium text-heading">{nodeData.label}</span>
{ip && (
<span className="font-mono text-[10px] text-muted-foreground">{ip}</span>
)}
<Handle type="target" position={Position.Top} className="!h-2 !w-2 !border-default !bg-elevated opacity-0 group-hover:opacity-100 transition-opacity" />
<Handle type="source" position={Position.Bottom} className="!h-2 !w-2 !border-default !bg-elevated opacity-0 group-hover:opacity-100 transition-opacity" />
<Handle type="target" position={Position.Left} id="left" className="!h-2 !w-2 !border-default !bg-elevated opacity-0 group-hover:opacity-100 transition-opacity" />
<Handle type="source" position={Position.Right} id="right" className="!h-2 !w-2 !border-default !bg-elevated opacity-0 group-hover:opacity-100 transition-opacity" />
</div>
)
}
export const DeviceNode = memo(DeviceNodeComponent)