From 04069e080a721b07aa99a9e514c36584314144de Mon Sep 17 00:00:00 2001 From: findthelorax Date: Thu, 16 Apr 2026 10:10:30 -0400 Subject: [PATCH 1/3] feature: double-click on a node to open an edit modal --- frontend/src/App.tsx | 1 + frontend/src/components/canvas/CanvasContainer.tsx | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index a36b9d0..c3ff8df 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -400,6 +400,7 @@ export default function App() { handleEditNode(node.id)} onNodeDragStart={snapshotHistory} onOpenPending={(deviceId) => { setHighlightPendingId(undefined) diff --git a/frontend/src/components/canvas/CanvasContainer.tsx b/frontend/src/components/canvas/CanvasContainer.tsx index 3847dc6..cee03e8 100644 --- a/frontend/src/components/canvas/CanvasContainer.tsx +++ b/frontend/src/components/canvas/CanvasContainer.tsx @@ -25,11 +25,12 @@ import type { NodeData, EdgeData } from '@/types' interface CanvasContainerProps { onConnect?: (connection: Connection) => void onEdgeDoubleClick?: (edge: Edge) => void + onNodeDoubleClick?: (node: Node) => void onNodeDragStart?: () => void onOpenPending?: (deviceId: string) => void } -export function CanvasContainer({ onConnect: onConnectProp, onEdgeDoubleClick, onNodeDragStart, onOpenPending }: CanvasContainerProps) { +export function CanvasContainer({ onConnect: onConnectProp, onEdgeDoubleClick, onNodeDoubleClick, onNodeDragStart, onOpenPending }: CanvasContainerProps) { const [lassoMode, setLassoMode] = useState(true) const { nodes, edges, @@ -68,6 +69,10 @@ export function CanvasContainer({ onConnect: onConnectProp, onEdgeDoubleClick, o onEdgeDoubleClick?.(edge) }, [onEdgeDoubleClick]) + const handleNodeDoubleClick = useCallback((_: React.MouseEvent, node: Node) => { + onNodeDoubleClick?.(node) + }, [onNodeDoubleClick]) + return (
Date: Thu, 16 Apr 2026 11:44:24 -0400 Subject: [PATCH 2/3] feature: extend zoom out and border width math --- frontend/src/components/canvas/CanvasContainer.tsx | 2 ++ frontend/src/components/canvas/nodes/BaseNode.tsx | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/canvas/CanvasContainer.tsx b/frontend/src/components/canvas/CanvasContainer.tsx index 3847dc6..7428492 100644 --- a/frontend/src/components/canvas/CanvasContainer.tsx +++ b/frontend/src/components/canvas/CanvasContainer.tsx @@ -89,6 +89,8 @@ export function CanvasContainer({ onConnect: onConnectProp, onEdgeDoubleClick, o panActivationKeyCode="Space" selectionMode={SelectionMode.Partial} multiSelectionKeyCode={['Meta', 'Control']} + minZoom={0.25} + maxZoom={2.5} snapToGrid snapGrid={[8, 8]} colorMode={theme.colors.reactFlowColorMode} diff --git a/frontend/src/components/canvas/nodes/BaseNode.tsx b/frontend/src/components/canvas/nodes/BaseNode.tsx index 0bacd34..5be7dcf 100644 --- a/frontend/src/components/canvas/nodes/BaseNode.tsx +++ b/frontend/src/components/canvas/nodes/BaseNode.tsx @@ -1,5 +1,5 @@ import { createElement, useEffect } from 'react' -import { Handle, Position, NodeResizer, useUpdateNodeInternals, type NodeProps, type Node } from '@xyflow/react' +import { Handle, Position, NodeResizer, useUpdateNodeInternals, useViewport, type NodeProps, type Node } from '@xyflow/react' import { Cpu, MemoryStick, HardDrive, type LucideIcon } from 'lucide-react' import type { NodeData } from '@/types' import { resolveNodeColors } from '@/utils/nodeColors' @@ -24,6 +24,9 @@ export function BaseNode({ id, data, selected, icon: typeIcon, width, height }: const updateNodeInternals = useUpdateNodeInternals() useEffect(() => { updateNodeInternals(id) }, [data.bottom_handles, id, updateNodeInternals]) + const { zoom } = useViewport() + const borderWidth = Math.max(1, 1 / zoom) + const activeTheme = useThemeStore((s) => s.activeTheme) const hideIp = useCanvasStore((s) => s.hideIp) const theme = THEMES[activeTheme] @@ -44,7 +47,7 @@ export function BaseNode({ id, data, selected, icon: typeIcon, width, height }: style={{ background: colors.background, borderColor: colors.border, - borderWidth: 1, + borderWidth, boxShadow: isOnline && selected ? `0 0 0 1px ${colors.border}, 0 0 10px ${colors.border}2e, 0 0 3px ${colors.border}1a` : isOnline From 548da952c5e9bb0db70362391fa355593e1b6efe Mon Sep 17 00:00:00 2001 From: findthelorax Date: Thu, 16 Apr 2026 12:58:40 -0400 Subject: [PATCH 3/3] feature: added a Container Mode toggle to the Virtualization group of node types --- frontend/src/App.tsx | 17 +++--- .../src/components/canvas/nodes/nodeTypes.ts | 2 +- frontend/src/components/modals/NodeModal.tsx | 54 ++++++++++--------- .../modals/__tests__/NodeModal.test.tsx | 43 +++++++-------- .../src/stores/__tests__/canvasStore.test.ts | 15 ++++++ frontend/src/stores/canvasStore.ts | 40 ++++++++++++-- frontend/src/types/index.ts | 4 +- frontend/src/utils/__tests__/themes.test.ts | 4 +- frontend/src/utils/canvasSerializer.ts | 11 ++-- frontend/src/utils/nodeIcons.ts | 2 +- frontend/src/utils/themes.ts | 10 ++-- 11 files changed, 129 insertions(+), 73 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index a36b9d0..68233c4 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -33,6 +33,7 @@ import type { NodeData, EdgeData } from '@/types' const STANDALONE = import.meta.env.VITE_STANDALONE === 'true' const STANDALONE_STORAGE_KEY = 'homelable_canvas' +const CONTAINER_MODE_TYPES = new Set(['proxmox', 'vm', 'lxc', 'docker_host']) export default function App() { const { loadCanvas, markSaved, markUnsaved, selectedNodeId, selectedNodeIds, addNode, updateNode, deleteNode, onConnect, updateEdge, deleteEdge, setProxmoxContainerMode, setNodeZIndex, editingGroupRectId, setEditingGroupRectId, nodes, edges, snapshotHistory, undo, redo, copySelectedNodes, pasteNodes } = useCanvasStore() @@ -102,8 +103,8 @@ export default function App() { // Build a map of proxmox container mode to know if children should be nested const proxmoxContainerMap = new Map( (apiNodes as ApiNode[]) - .filter((n) => n.type === 'proxmox' || n.type === 'group') - .map((n) => [n.id, n.type === 'group' ? true : n.container_mode !== false]) + .filter((n) => n.type === 'group' || n.container_mode === true) + .map((n) => [n.id, true]) ) const rfNodes = (apiNodes as ApiNode[]).map((n) => deserializeApiNode(n, proxmoxContainerMap)) const rfEdges = (apiEdges as ApiEdge[]).map(deserializeApiEdge) @@ -241,8 +242,8 @@ export default function App() { snapshotHistory() const existingNode = nodes.find((n) => n.id === editNodeId) updateNode(editNodeId, data) - // If proxmox container_mode changed, apply structural changes (children parentId, node dimensions) - if (data.type === 'proxmox' && typeof data.container_mode === 'boolean') { + // If container_mode changed, apply structural changes (children parentId, node dimensions) + if (typeof data.container_mode === 'boolean') { setProxmoxContainerMode(editNodeId, data.container_mode) } // Sync virtual edge when parent_id changes on an LXC/VM node @@ -421,7 +422,9 @@ export default function App() { onClose={() => setAddNodeOpen(false)} onSubmit={handleAddNode} title="Add Node" - proxmoxNodes={nodes.filter((n) => n.type === 'proxmox').map((n) => ({ id: n.id, label: n.data.label }))} + parentContainerNodes={nodes + .filter((n) => CONTAINER_MODE_TYPES.has(n.data.type) && n.data.container_mode) + .map((n) => ({ id: n.id, label: n.data.label }))} /> {/* key forces re-mount when editing a different node, resetting form state */} @@ -432,7 +435,9 @@ export default function App() { onSubmit={handleUpdateNode} initial={editNode?.data} title="Edit Node" - proxmoxNodes={nodes.filter((n) => n.type === 'proxmox').map((n) => ({ id: n.id, label: n.data.label }))} + parentContainerNodes={nodes + .filter((n) => n.id !== editNodeId && CONTAINER_MODE_TYPES.has(n.data.type) && n.data.container_mode) + .map((n) => ({ id: n.id, label: n.data.label }))} /> = { type: 'server', @@ -26,7 +27,7 @@ const DEFAULT_DATA: Partial = { status: 'unknown', check_method: 'ping', services: [], - container_mode: true, + container_mode: false, custom_colors: undefined, custom_icon: undefined, } @@ -37,14 +38,12 @@ interface NodeModalProps { onSubmit: (data: Partial) => void initial?: Partial title?: string - proxmoxNodes?: { id: string; label: string }[] + parentContainerNodes?: { id: string; label: string }[] } -const CHILD_TYPES: NodeType[] = ['vm', 'lxc'] - // NodeModal is always mounted with a key that changes on open/edit, so useState -// initial value is enough — no need for a reset effect. -export function NodeModal({ open, onClose, onSubmit, initial, title = 'Add Node', proxmoxNodes = [] }: NodeModalProps) { +// initial value is enough - no need for a reset effect. +export function NodeModal({ open, onClose, onSubmit, initial, title = 'Add Node', parentContainerNodes = [] }: NodeModalProps) { const [form, setForm] = useState>({ ...DEFAULT_DATA, ...initial }) const [iconSearch, setIconSearch] = useState('') const [iconPickerOpen, setIconPickerOpen] = useState(false) @@ -60,7 +59,12 @@ export function NodeModal({ open, onClose, onSubmit, initial, title = 'Add Node' return } setLabelError(false) - onSubmit(form) + const selectedType = (form.type ?? 'generic') as NodeType + const canUseContainerMode = CONTAINER_MODE_TYPES.includes(selectedType) + onSubmit({ + ...form, + container_mode: canUseContainerMode ? !!form.container_mode : false, + }) onClose() } @@ -82,9 +86,9 @@ export function NodeModal({ open, onClose, onSubmit, initial, title = 'Add Node' {NODE_TYPE_GROUPS.map((group, i) => ( - <> - {i > 0 && } - + + {i > 0 && } + {group.label} @@ -94,7 +98,7 @@ export function NodeModal({ open, onClose, onSubmit, initial, title = 'Add Node' ))} - + ))} @@ -134,7 +138,7 @@ export function NodeModal({ open, onClose, onSubmit, initial, title = 'Add Node'
- {/* Inline icon picker — full width, shown below the type+icon row */} + {/* Inline icon picker - full width, shown below the type+icon row */} {iconPickerOpen && (
- {/* Parent Proxmox (VM / LXC only) */} - {CHILD_TYPES.includes(form.type as NodeType) && proxmoxNodes.length > 0 && ( + {/* Parent container */} + {form.type !== 'groupRect' && form.type !== 'group' && parentContainerNodes.length > 0 && (
- +
diff --git a/frontend/src/components/modals/__tests__/NodeModal.test.tsx b/frontend/src/components/modals/__tests__/NodeModal.test.tsx index 4b24085..a60bafa 100644 --- a/frontend/src/components/modals/__tests__/NodeModal.test.tsx +++ b/frontend/src/components/modals/__tests__/NodeModal.test.tsx @@ -219,15 +219,20 @@ describe('NodeModal', () => { expect(screen.queryByTitle('Router')).toBeNull() }) - // ── Container mode (proxmox only) ───────────────────────────────────── + // ── Container mode ───────────────────────────────────────────────────── it('shows Container Mode toggle for proxmox type', () => { renderModal({ initial: { ...BASE, type: 'proxmox' } }) expect(screen.getByText('Container Mode')).toBeDefined() }) - it('hides Container Mode for non-proxmox types', () => { - renderModal({ initial: BASE }) + it('hides Container Mode for server type', () => { + renderModal({ initial: { ...BASE, type: 'server' } }) + expect(screen.queryByText('Container Mode')).toBeNull() + }) + + it('hides Container Mode for groupRect type', () => { + renderModal({ initial: { ...BASE, type: 'groupRect' } }) expect(screen.queryByText('Container Mode')).toBeNull() }) @@ -238,33 +243,25 @@ describe('NodeModal', () => { expect((onSubmit.mock.calls[0][0] as Partial).container_mode).toBe(false) }) - // ── Parent Proxmox (vm / lxc only) ─────────────────────────────────── + // ── Parent container ────────────────────────────────────────────────── - it('shows Parent Proxmox for vm with proxmoxNodes', () => { + it('shows Parent Container when options are provided', () => { renderModal({ - initial: { ...BASE, type: 'vm' }, - proxmoxNodes: [{ id: 'px1', label: 'PVE-01' }], + initial: { ...BASE, type: 'server' }, + parentContainerNodes: [{ id: 'c1', label: 'Container 01' }], }) - expect(screen.getByText('Parent Proxmox')).toBeDefined() - expect(screen.getByText('PVE-01')).toBeDefined() + expect(screen.getByText('Parent Container')).toBeDefined() + expect(screen.getByText('Container 01')).toBeDefined() }) - it('shows Parent Proxmox for lxc with proxmoxNodes', () => { - renderModal({ - initial: { ...BASE, type: 'lxc' }, - proxmoxNodes: [{ id: 'px1', label: 'PVE-01' }], - }) - expect(screen.getByText('Parent Proxmox')).toBeDefined() + it('hides Parent Container for groupRect type', () => { + renderModal({ initial: { ...BASE, type: 'groupRect' }, parentContainerNodes: [{ id: 'c1', label: 'Container 01' }] }) + expect(screen.queryByText('Parent Container')).toBeNull() }) - it('hides Parent Proxmox for server type', () => { - renderModal({ initial: BASE, proxmoxNodes: [{ id: 'px1', label: 'PVE-01' }] }) - expect(screen.queryByText('Parent Proxmox')).toBeNull() - }) - - it('hides Parent Proxmox for vm when no proxmoxNodes', () => { - renderModal({ initial: { ...BASE, type: 'vm' } }) - expect(screen.queryByText('Parent Proxmox')).toBeNull() + it('hides Parent Container when no container options are available', () => { + renderModal({ initial: { ...BASE, type: 'server' } }) + expect(screen.queryByText('Parent Container')).toBeNull() }) // ── Appearance ──────────────────────────────────────────────────────── diff --git a/frontend/src/stores/__tests__/canvasStore.test.ts b/frontend/src/stores/__tests__/canvasStore.test.ts index f436f69..65af38e 100644 --- a/frontend/src/stores/__tests__/canvasStore.test.ts +++ b/frontend/src/stores/__tests__/canvasStore.test.ts @@ -49,6 +49,21 @@ describe('canvasStore', () => { expect(hasUnsavedChanges).toBe(true) }) + it('addNode nests under parent only when parent is in container mode', () => { + const parent = { ...makeNode('p1', { container_mode: false }), position: { x: 100, y: 100 } } + const child = { ...makeNode('c1', { parent_id: 'p1' }), position: { x: 150, y: 180 } } + useCanvasStore.getState().addNode(parent) + useCanvasStore.getState().addNode(child) + const childNode = useCanvasStore.getState().nodes.find((n) => n.id === 'c1') + expect(childNode?.parentId).toBeUndefined() + + useCanvasStore.getState().updateNode('p1', { container_mode: true }) + useCanvasStore.getState().setProxmoxContainerMode('p1', true) + const nested = useCanvasStore.getState().nodes.find((n) => n.id === 'c1') + expect(nested?.parentId).toBe('p1') + expect(nested?.extent).toBe('parent') + }) + it('updateNode updates data fields', () => { useCanvasStore.getState().addNode(makeNode('n1', { label: 'old' })) useCanvasStore.getState().updateNode('n1', { label: 'new', ip: '10.0.0.1' }) diff --git a/frontend/src/stores/canvasStore.ts b/frontend/src/stores/canvasStore.ts index 9df76ad..0804236 100644 --- a/frontend/src/stores/canvasStore.ts +++ b/frontend/src/stores/canvasStore.ts @@ -172,8 +172,18 @@ export const useCanvasStore = create((set) => ({ addNode: (node) => set((state) => { - const enriched = node.data.parent_id - ? { ...node, parentId: node.data.parent_id, extent: 'parent' as const } + const parent = node.data.parent_id ? state.nodes.find((n) => n.id === node.data.parent_id) : null + const shouldNestInParent = !!(parent?.data.container_mode) + const enriched = node.data.parent_id && shouldNestInParent + ? { + ...node, + parentId: node.data.parent_id, + extent: 'parent' as const, + position: { + x: Math.max(10, node.position.x - parent.position.x), + y: Math.max(10, node.position.y - parent.position.y), + }, + } : node // Parents must come before children in the array (React Flow requirement) const withoutNew = state.nodes.filter((n) => n.id !== node.id) @@ -283,14 +293,38 @@ export const useCanvasStore = create((set) => ({ setProxmoxContainerMode: (proxmoxId, enabled) => set((state) => { + const parentNode = state.nodes.find((n) => n.id === proxmoxId) let nodes = state.nodes.map((n) => { if (n.id === proxmoxId) { const withMode = { ...n, data: { ...n.data, container_mode: enabled } } + if (n.data.type !== 'proxmox') return withMode return enabled - ? { ...withMode, width: 300, height: 200 } + ? { ...withMode, width: n.width ?? 300, height: n.height ?? 200 } : { ...withMode, width: undefined, height: undefined } } if (n.data.parent_id === proxmoxId) { + if (enabled && parentNode) { + return { + ...n, + parentId: proxmoxId, + extent: 'parent' as const, + position: { + x: Math.max(10, n.position.x - parentNode.position.x), + y: Math.max(10, n.position.y - parentNode.position.y), + }, + } + } + if (!enabled && parentNode) { + return { + ...n, + parentId: undefined, + extent: undefined, + position: { + x: parentNode.position.x + n.position.x, + y: parentNode.position.y + n.position.y, + }, + } + } return enabled ? { ...n, parentId: proxmoxId, extent: 'parent' as const } : { ...n, parentId: undefined, extent: undefined } diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index 2481349..5caea99 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -13,7 +13,7 @@ export type NodeType = | 'printer' | 'computer' | 'cpl' - | 'docker' + | 'docker_host' | 'generic' | 'groupRect' | 'group' @@ -126,7 +126,7 @@ export const NODE_TYPE_LABELS: Record = { printer: 'Printer', computer: 'Computer', cpl: 'CPL / Powerline', - docker: 'Docker Host', + docker_host: 'Docker Host', generic: 'Generic Device', groupRect: 'Group Rectangle', group: 'Node Group', diff --git a/frontend/src/utils/__tests__/themes.test.ts b/frontend/src/utils/__tests__/themes.test.ts index aa06ea5..e186106 100644 --- a/frontend/src/utils/__tests__/themes.test.ts +++ b/frontend/src/utils/__tests__/themes.test.ts @@ -4,7 +4,7 @@ import type { NodeType, EdgeType, NodeStatus } from '@/types' const NODE_TYPES: NodeType[] = [ 'isp', 'router', 'switch', 'server', 'proxmox', 'vm', 'lxc', - 'nas', 'iot', 'ap', 'camera', 'printer', 'computer', 'cpl', 'docker', 'generic', 'groupRect', + 'nas', 'iot', 'ap', 'camera', 'printer', 'computer', 'cpl', 'docker_host', 'generic', 'groupRect', ] const EDGE_TYPES: EdgeType[] = ['ethernet', 'wifi', 'iot', 'vlan', 'virtual', 'cluster'] const STATUS_TYPES: NodeStatus[] = ['online', 'offline', 'pending', 'unknown'] @@ -84,7 +84,7 @@ describe('THEMES', () => { expect(d.nodeAccents.server.border).toBe('#a855f7') expect(d.nodeAccents.isp.border).toBe('#00d4ff') expect(d.nodeAccents.proxmox.border).toBe('#ff6e00') - expect(d.nodeAccents.docker.border).toBe('#2496ED') + expect(d.nodeAccents.docker_host.border).toBe('#2496ED') expect(d.nodeCardBackground).toBe('#21262d') expect(d.nodeIconBackground).toBe('#161b22') expect(d.canvasBackground).toBe('#0d1117') diff --git a/frontend/src/utils/canvasSerializer.ts b/frontend/src/utils/canvasSerializer.ts index 68b20d7..1fcd78b 100644 --- a/frontend/src/utils/canvasSerializer.ts +++ b/frontend/src/utils/canvasSerializer.ts @@ -134,6 +134,7 @@ export function deserializeApiNode( n: ApiNode, proxmoxContainerMap: Map, ): Node { + const normalizedType = n.type === 'docker' ? 'docker_host' : n.type if (n.type === 'groupRect') { const w = (n.custom_colors?.width as number | undefined) ?? 360 const h = (n.custom_colors?.height as number | undefined) ?? 240 @@ -152,15 +153,15 @@ export function deserializeApiNode( const parentIsContainer = n.parent_id ? (proxmoxContainerMap.get(n.parent_id) ?? false) : false return { id: n.id, - type: n.type, + type: normalizedType, position: { x: n.pos_x, y: n.pos_y }, - data: n as unknown as NodeData, + data: { ...n, type: normalizedType } as unknown as NodeData, ...(n.parent_id && parentIsContainer ? { parentId: n.parent_id, extent: 'parent' as const } : {}), - ...(n.type === 'proxmox' && n.container_mode !== false + ...(normalizedType === 'proxmox' && n.container_mode !== false ? { width: n.width ?? 300, height: n.height ?? 200 } : {}), - ...(n.width && n.type !== 'proxmox' ? { width: n.width } : {}), - ...(n.height && n.type !== 'proxmox' ? { height: n.height } : {}), + ...(n.width && normalizedType !== 'proxmox' ? { width: n.width } : {}), + ...(n.height && normalizedType !== 'proxmox' ? { height: n.height } : {}), } } diff --git a/frontend/src/utils/nodeIcons.ts b/frontend/src/utils/nodeIcons.ts index 6dbf5f5..74504c2 100644 --- a/frontend/src/utils/nodeIcons.ts +++ b/frontend/src/utils/nodeIcons.ts @@ -132,7 +132,7 @@ export const NODE_TYPE_DEFAULT_ICONS: Record = { printer: Printer, computer: Monitor, cpl: PlugZap, - docker: Anchor, + docker_host: Anchor, generic: Circle, group: Circle, groupRect: Circle, diff --git a/frontend/src/utils/themes.ts b/frontend/src/utils/themes.ts index 23d0be0..7f86382 100644 --- a/frontend/src/utils/themes.ts +++ b/frontend/src/utils/themes.ts @@ -56,7 +56,7 @@ export const THEMES: Record = { printer: { border: '#8b949e', icon: '#8b949e' }, computer: { border: '#a855f7', icon: '#a855f7' }, cpl: { border: '#e3b341', icon: '#e3b341' }, - docker: { border: '#2496ED', icon: '#2496ED' }, + docker_host: { border: '#2496ED', icon: '#2496ED' }, generic: { border: '#8b949e', icon: '#8b949e' }, groupRect:{ border: '#00d4ff', icon: '#00d4ff' }, group: { border: '#00d4ff', icon: '#00d4ff' }, @@ -111,7 +111,7 @@ export const THEMES: Record = { printer: { border: '#94a3b8', icon: '#94a3b8' }, computer: { border: '#c084fc', icon: '#c084fc' }, cpl: { border: '#fbbf24', icon: '#fbbf24' }, - docker: { border: '#2496ED', icon: '#2496ED' }, + docker_host: { border: '#2496ED', icon: '#2496ED' }, generic: { border: '#94a3b8', icon: '#94a3b8' }, groupRect:{ border: '#22d3ee', icon: '#22d3ee' }, group: { border: '#22d3ee', icon: '#22d3ee' }, @@ -166,7 +166,7 @@ export const THEMES: Record = { printer: { border: '#6b7280', icon: '#6b7280' }, computer: { border: '#7c3aed', icon: '#7c3aed' }, cpl: { border: '#b45309', icon: '#b45309' }, - docker: { border: '#2496ED', icon: '#2496ED' }, + docker_host: { border: '#2496ED', icon: '#2496ED' }, generic: { border: '#6b7280', icon: '#6b7280' }, groupRect:{ border: '#0284c7', icon: '#0284c7' }, group: { border: '#0284c7', icon: '#0284c7' }, @@ -221,7 +221,7 @@ export const THEMES: Record = { printer: { border: '#8888ff', icon: '#8888ff' }, computer: { border: '#ff00ff', icon: '#ff00ff' }, cpl: { border: '#ffff00', icon: '#ffff00' }, - docker: { border: '#00aaff', icon: '#00aaff' }, + docker_host: { border: '#00aaff', icon: '#00aaff' }, generic: { border: '#8888ff', icon: '#8888ff' }, groupRect:{ border: '#00ffff', icon: '#00ffff' }, group: { border: '#00ffff', icon: '#00ffff' }, @@ -276,7 +276,7 @@ export const THEMES: Record = { printer: { border: '#005500', icon: '#005500' }, computer: { border: '#008822', icon: '#008822' }, cpl: { border: '#66ff33', icon: '#66ff33' }, - docker: { border: '#00cc88', icon: '#00cc88' }, + docker_host: { border: '#00cc88', icon: '#00cc88' }, generic: { border: '#006600', icon: '#006600' }, groupRect:{ border: '#00ff41', icon: '#00ff41' }, group: { border: '#00ff41', icon: '#00ff41' },