feat: drop node onto container node to nest it

Dropping a top-level node over any container_mode node (proxmox,
docker_host, ...) now pops a confirm modal that nests it as a child
(sets parentId), mirroring the existing drop-onto-group flow.

- store: addToContainer(containerId, childId)
- CanvasContainer: detect container_mode intersection on drag stop
  (group still wins if both intersect)
- generalize ConfirmAddToGroupModal with a container variant

ha-relevant: yes
This commit is contained in:
Pouzor
2026-06-11 14:59:25 +02:00
parent b776b6b8f1
commit 2058e453ff
7 changed files with 221 additions and 17 deletions
@@ -5,14 +5,14 @@ import { ConfirmAddToGroupModal } from '../ConfirmAddToGroupModal'
describe('ConfirmAddToGroupModal', () => {
it('renders nothing when closed', () => {
render(
<ConfirmAddToGroupModal open={false} nodeLabel="Router" groupLabel="DMZ" onConfirm={vi.fn()} onCancel={vi.fn()} />,
<ConfirmAddToGroupModal open={false} nodeLabel="Router" targetLabel="DMZ" onConfirm={vi.fn()} onCancel={vi.fn()} />,
)
expect(screen.queryByText('Add to group')).toBeNull()
})
it('shows node and group labels when open', () => {
render(
<ConfirmAddToGroupModal open nodeLabel="Router" groupLabel="DMZ" onConfirm={vi.fn()} onCancel={vi.fn()} />,
<ConfirmAddToGroupModal open nodeLabel="Router" targetLabel="DMZ" onConfirm={vi.fn()} onCancel={vi.fn()} />,
)
expect(screen.getByText('Router')).toBeDefined()
expect(screen.getByText('DMZ')).toBeDefined()
@@ -21,7 +21,7 @@ describe('ConfirmAddToGroupModal', () => {
it('calls onConfirm when the confirm button is clicked', () => {
const onConfirm = vi.fn()
render(
<ConfirmAddToGroupModal open nodeLabel="Router" groupLabel="DMZ" onConfirm={onConfirm} onCancel={vi.fn()} />,
<ConfirmAddToGroupModal open nodeLabel="Router" targetLabel="DMZ" onConfirm={onConfirm} onCancel={vi.fn()} />,
)
fireEvent.click(screen.getByRole('button', { name: /add to group/i }))
expect(onConfirm).toHaveBeenCalledOnce()
@@ -30,9 +30,17 @@ describe('ConfirmAddToGroupModal', () => {
it('calls onCancel when the cancel button is clicked', () => {
const onCancel = vi.fn()
render(
<ConfirmAddToGroupModal open nodeLabel="Router" groupLabel="DMZ" onConfirm={vi.fn()} onCancel={onCancel} />,
<ConfirmAddToGroupModal open nodeLabel="Router" targetLabel="DMZ" onConfirm={vi.fn()} onCancel={onCancel} />,
)
fireEvent.click(screen.getByRole('button', { name: /cancel/i }))
expect(onCancel).toHaveBeenCalledOnce()
})
it('uses container wording when variant is container', () => {
render(
<ConfirmAddToGroupModal open variant="container" nodeLabel="VM" targetLabel="Proxmox" onConfirm={vi.fn()} onCancel={vi.fn()} />,
)
expect(screen.getByRole('button', { name: /add to container/i })).toBeDefined()
expect(screen.queryByText('Add to group')).toBeNull()
})
})