From 47353a68cd4203ab54cae6bcd4ad1ce62bd84eaa Mon Sep 17 00:00:00 2001 From: chihlasm Date: Mon, 13 Apr 2026 04:07:52 +0000 Subject: [PATCH] feat: drag-to-resize device nodes + BrickWallFire for firewall MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - NodeResizer on DeviceNode (same pattern as group nodes); icon scales proportionally with node width, clamped 16–60px - Removes S/M/L static picker — resize is now direct manipulation - firewall: ShieldAlert → BrickWallFire Co-Authored-By: Claude Sonnet 4.6 --- .../components/network/nodes/DeviceNode.tsx | 95 ++++++++++--------- .../network/nodes/deviceRegistry.ts | 4 +- .../network/panels/PropertiesPanel.tsx | 26 +---- 3 files changed, 54 insertions(+), 71 deletions(-) diff --git a/frontend/src/components/network/nodes/DeviceNode.tsx b/frontend/src/components/network/nodes/DeviceNode.tsx index a1be5308..b14a8416 100644 --- a/frontend/src/components/network/nodes/DeviceNode.tsx +++ b/frontend/src/components/network/nodes/DeviceNode.tsx @@ -1,5 +1,5 @@ import { memo } from 'react' -import { Position, type NodeProps } from '@xyflow/react' +import { Position, NodeResizer, type NodeProps } from '@xyflow/react' import { BaseNode, BaseNodeHeader, BaseNodeHeaderTitle, BaseNodeContent } from '../ui/base-node' import { BaseHandle } from '../ui/base-handle' import { NodeStatusIndicator, type NodeStatus } from '../ui/node-status-indicator' @@ -7,14 +7,10 @@ import { NodeTooltip, NodeTooltipTrigger, NodeTooltipContent } from '../ui/node- import { getDeviceRenderConfig } from './deviceRegistry' import type { DeviceProperties } from '@/types' -export type IconSize = 'sm' | 'md' | 'lg' -export const ICON_SIZE_PX: Record = { sm: 18, md: 28, lg: 42 } - export interface DeviceNodeData { label: string deviceType: string category?: string - iconSize?: IconSize properties: DeviceProperties [key: string]: unknown } @@ -29,55 +25,66 @@ function TooltipRow({ label, value }: { label: string; value: string | null | un ) } -function DeviceNodeComponent({ data }: NodeProps) { +function DeviceNodeComponent({ data, selected, width }: NodeProps) { const nodeData = data as unknown as DeviceNodeData const { icon: Icon, color } = getDeviceRenderConfig(nodeData.deviceType, nodeData.category) const status = (nodeData.properties?.status || 'unknown') as NodeStatus const ip = nodeData.properties?.ip const props = nodeData.properties || {} - const iconPx = ICON_SIZE_PX[nodeData.iconSize ?? 'md'] + + // Scale icon proportionally: 28px at default 120px wide, clamped 16–60px + const iconPx = Math.round(Math.max(16, Math.min(60, ((width ?? 120) / 120) * 28))) const hasTooltipContent = props.hostname || props.ip || props.vendor || props.model || props.role || props.notes return ( - - - - - - - - {nodeData.label} - - - {ip && ( - - {ip} - - )} - - - - - - - {hasTooltipContent && ( - -
- - - {(props.vendor || props.model) && ( - + <> + + + + + + + + + {nodeData.label} + + + {ip && ( + + {ip} + )} - - {props.notes && ( - 100 ? props.notes.slice(0, 100) + '...' : props.notes} /> - )} -
-
- )} -
-
+ + + + + + + {hasTooltipContent && ( + +
+ + + {(props.vendor || props.model) && ( + + )} + + {props.notes && ( + 100 ? props.notes.slice(0, 100) + '...' : props.notes} /> + )} +
+
+ )} + + + ) } diff --git a/frontend/src/components/network/nodes/deviceRegistry.ts b/frontend/src/components/network/nodes/deviceRegistry.ts index 672b528a..c92dbbee 100644 --- a/frontend/src/components/network/nodes/deviceRegistry.ts +++ b/frontend/src/components/network/nodes/deviceRegistry.ts @@ -1,6 +1,6 @@ import type { LucideIcon } from 'lucide-react' import { - Router, Network, ShieldAlert, Wifi, Server, Monitor, Boxes, Package, Cloud, + Router, Network, BrickWallFire, Wifi, Server, Monitor, Boxes, Package, Cloud, Printer, Smartphone, HardDrive, Gauge, Database, CloudCog, Cpu, Tablet, Laptop, BatteryCharging, RectangleVertical, Cable, Camera, KeyRound, Globe, Video, PlugZap, Radio, @@ -35,7 +35,7 @@ const SYSTEM_DEVICE_ICONS: Record = { 'load-balancer': { icon: Gauge, color: NETWORK_COLOR }, // Security - 'firewall': { icon: ShieldAlert, color: SECURITY_COLOR }, + 'firewall': { icon: BrickWallFire, color: SECURITY_COLOR }, 'badge-reader': { icon: KeyRound, color: SECURITY_COLOR }, // Compute diff --git a/frontend/src/components/network/panels/PropertiesPanel.tsx b/frontend/src/components/network/panels/PropertiesPanel.tsx index 4043328d..bdc72c0a 100644 --- a/frontend/src/components/network/panels/PropertiesPanel.tsx +++ b/frontend/src/components/network/panels/PropertiesPanel.tsx @@ -3,7 +3,7 @@ import { Trash2, Minus, Spline, GitBranch, BringToFront, SendToBack } from 'luci import { cn } from '@/lib/utils' import type { DeviceProperties, DiagramEdge } from '@/types' import type { Node, Edge } from '@xyflow/react' -import type { DeviceNodeData, IconSize } from '../nodes/DeviceNode' +import type { DeviceNodeData } from '../nodes/DeviceNode' interface PropertiesPanelProps { selectedNode: Node | null @@ -272,30 +272,6 @@ export function PropertiesPanel({ - {/* Icon size */} -
- Icon Size -
- {(['sm', 'md', 'lg'] as IconSize[]).map(size => { - const active = (nodeData.iconSize ?? 'md') === size - return ( - - ) - })} -
-
- {/* Layering */}
Layer