diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 8ec3d6b..6b10007 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -152,7 +152,7 @@ export default function App() { const handleAddNode = useCallback((data: Partial) => { snapshotHistory() const id = generateUUID() - const isContainerNode = CONTAINER_MODE_TYPES.has(data.type as NodeData['type']) + const isContainerNode = data.container_mode === true const parentNode = data.parent_id ? nodes.find((n) => n.id === data.parent_id) : null // Children position is relative to parent; place near top-left with padding const position = parentNode @@ -249,7 +249,7 @@ export default function App() { } // Sync virtual edge when parent_id changes on an LXC/VM node const nodeType = data.type ?? existingNode?.data.type - if ((nodeType === 'lxc' || nodeType === 'vm') && 'parent_id' in data) { + if ((nodeType === 'lxc' || nodeType === 'vm' || nodeType === 'docker_container') && 'parent_id' in data) { const oldParentId = existingNode?.data.parent_id ?? null const newParentId = data.parent_id ?? null if (oldParentId !== newParentId) { @@ -331,6 +331,10 @@ export default function App() { updateNode(pendingConnection.source, { parent_id: pendingConnection.target }) } else if (CONTAINER_MODE_TYPES.has(srcType) && (tgtType === 'lxc' || tgtType === 'vm')) { updateNode(pendingConnection.target, { parent_id: pendingConnection.source }) + } else if (srcType === 'docker_container' && tgtType === 'docker_host') { + updateNode(pendingConnection.source, { parent_id: pendingConnection.target }) + } else if (tgtType === 'docker_container' && srcType === 'docker_host') { + updateNode(pendingConnection.target, { parent_id: pendingConnection.source }) } } setPendingConnection(null) @@ -426,7 +430,7 @@ export default function App() { title="Add Node" parentContainerNodes={nodes .filter((n) => CONTAINER_MODE_TYPES.has(n.data.type) && n.data.container_mode) - .map((n) => ({ id: n.id, label: n.data.label }))} + .map((n) => ({ id: n.id, label: n.data.label, nodeType: n.data.type }))} /> {/* key forces re-mount when editing a different node, resetting form state */} @@ -439,7 +443,7 @@ export default function App() { title="Edit Node" 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 }))} + .map((n) => ({ id: n.id, label: n.data.label, nodeType: n.data.type }))} /> export const PrinterNode = (props: N) => export const ComputerNode = (props: N) => export const CplNode = (props: N) => -export const DockerHostNode = (props: N) => +export const DockerHostNode = (props: N) => +export const DockerContainerNode = (props: N) => export const GenericNode = (props: N) => diff --git a/frontend/src/components/canvas/nodes/nodeTypes.ts b/frontend/src/components/canvas/nodes/nodeTypes.ts index 7f06bbd..6b33ac4 100644 --- a/frontend/src/components/canvas/nodes/nodeTypes.ts +++ b/frontend/src/components/canvas/nodes/nodeTypes.ts @@ -1,4 +1,4 @@ -import { IspNode, RouterNode, SwitchNode, ServerNode, VmNode, LxcNode, NasNode, IotNode, ApNode, CameraNode, PrinterNode, ComputerNode, CplNode, DockerHostNode, GenericNode } from './index' +import { IspNode, RouterNode, SwitchNode, ServerNode, VmNode, LxcNode, NasNode, IotNode, ApNode, CameraNode, PrinterNode, ComputerNode, CplNode, DockerHostNode, DockerContainerNode, GenericNode } from './index' import { ProxmoxGroupNode } from './ProxmoxGroupNode' import { GroupRectNode } from './GroupRectNode' import { GroupNode } from './GroupNode' @@ -19,6 +19,7 @@ export const nodeTypes = { computer: ComputerNode, cpl: CplNode, docker_host: DockerHostNode, + docker_container: DockerContainerNode, generic: GenericNode, groupRect: GroupRectNode, group: GroupNode, diff --git a/frontend/src/components/modals/NodeModal.tsx b/frontend/src/components/modals/NodeModal.tsx index b27b089..f8cf3f1 100644 --- a/frontend/src/components/modals/NodeModal.tsx +++ b/frontend/src/components/modals/NodeModal.tsx @@ -11,7 +11,7 @@ import { ICON_REGISTRY, ICON_CATEGORIES, NODE_TYPE_DEFAULT_ICONS } from '@/utils const NODE_TYPE_GROUPS: { label: string; types: NodeType[] }[] = [ { label: 'Hardware', types: ['isp', 'router', 'switch', 'server', 'nas', 'ap', 'printer'] }, - { label: 'Virtualization', types: ['proxmox', 'vm', 'lxc', 'docker_host'] }, + { label: 'Virtualization', types: ['proxmox', 'vm', 'lxc', 'docker_host', 'docker_container'] }, { label: 'IoT', types: ['iot', 'camera', 'cpl'] }, { label: 'Generic', types: ['computer', 'generic', 'groupRect'] }, ] @@ -49,7 +49,7 @@ interface NodeModalProps { onSubmit: (data: Partial) => void initial?: Partial title?: string - parentContainerNodes?: { id: string; label: string }[] + parentContainerNodes?: { id: string; label: string; nodeType?: NodeType }[] } // NodeModal is always mounted with a key that changes on open/edit, so useState @@ -79,6 +79,10 @@ export function NodeModal({ open, onClose, onSubmit, initial, title = 'Add Node' onClose() } + const filteredParentNodes = form.type === 'docker_container' + ? parentContainerNodes.filter((n) => n.nodeType === 'docker_host') + : parentContainerNodes + return ( !o && onClose()}> @@ -260,7 +264,7 @@ export function NodeModal({ open, onClose, onSubmit, initial, title = 'Add Node' {/* Parent container */} - {form.type !== 'groupRect' && form.type !== 'group' && parentContainerNodes.length > 0 && ( + {form.type !== 'groupRect' && form.type !== 'group' && filteredParentNodes.length > 0 && (