feat(docker): allow LXC as parent for docker_container

Previously docker_container could only attach to a docker_host via
virtual edge. Extend the rule so an LXC node can also act as parent,
enabling docker_container nesting under LXC without requiring an
intermediate docker_host.

Extracts the virtual-edge parent rule into a pure helper
(resolveVirtualEdgeParent) with unit tests covering all type pairs.

ha-relevant: yes
This commit is contained in:
Pouzor
2026-05-15 22:54:51 +02:00
parent cdc6091bd0
commit 280d5a2ae1
3 changed files with 106 additions and 11 deletions
+9 -11
View File
@@ -4,6 +4,7 @@ import { type Node } from '@xyflow/react'
import { applyDagreLayout } from '@/utils/layout'
import { serializeNode, serializeEdge, deserializeApiNode, deserializeApiEdge, type ApiNode, type ApiEdge } from '@/utils/canvasSerializer'
import { generateUUID } from '@/utils/uuid'
import { resolveVirtualEdgeParent } from '@/utils/virtualEdgeParent'
import { generateMarkdownTable } from '@/utils/exportMarkdown'
import { ExportModal } from '@/components/modals/ExportModal'
import { exportCanvasToYaml, downloadYaml } from '@/utils/exportYaml'
@@ -37,7 +38,6 @@ import type { ZigbeeNode, ZigbeeEdge } from '@/components/zigbee/types'
const STANDALONE = import.meta.env.VITE_STANDALONE === 'true'
const STANDALONE_STORAGE_KEY = 'homelable_canvas'
const CONTAINER_MODE_TYPES = new Set<NodeData['type']>(['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, editingTextId, setEditingTextId, nodes, edges, snapshotHistory, undo, redo, copySelectedNodes, pasteNodes } = useCanvasStore()
@@ -450,16 +450,14 @@ export default function App() {
if (edgeData.type === 'virtual') {
const src = nodes.find((n) => n.id === pendingConnection.source)
const tgt = nodes.find((n) => n.id === pendingConnection.target)
const srcType = src?.data.type as NodeData['type']
const tgtType = tgt?.data.type as NodeData['type']
if ((srcType === 'lxc' || srcType === 'vm') && CONTAINER_MODE_TYPES.has(tgtType)) {
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 })
if (src && tgt) {
const assignment = resolveVirtualEdgeParent(
{ id: src.id, type: src.data.type as NodeData['type'] },
{ id: tgt.id, type: tgt.data.type as NodeData['type'] },
)
if (assignment) {
updateNode(assignment.childId, { parent_id: assignment.parentId })
}
}
}
setPendingConnection(null)