feat: edge edit mode + proxmox container mode toggle

- Double-click any link to open Edit Link modal (type, label, VLAN ID, delete)
- Add updateEdge / deleteEdge actions to canvasStore
- Add container_mode field to proxmox nodes (backend model, schemas, migration)
- NodeModal shows container toggle for proxmox type (defaults ON)
- ProxmoxGroupNode renders as regular BaseNode when container_mode is OFF
- setProxmoxContainerMode store action handles structural changes atomically
  (children parentId/extent, node dimensions)
- CanvasContainer filters edges between container proxmox and its children
- Canvas load respects container_mode when assigning parentId/extent
This commit is contained in:
Pouzor
2026-03-07 14:15:08 +01:00
parent 9eba62c5b5
commit 4fb9a8ee45
11 changed files with 190 additions and 30 deletions
@@ -1,4 +1,4 @@
import { useCallback } from 'react'
import { useCallback, useMemo } from 'react'
import {
ReactFlow,
Background,
@@ -17,9 +17,10 @@ import type { NodeData, EdgeData } from '@/types'
interface CanvasContainerProps {
onConnect?: (connection: Connection) => void
onEdgeDoubleClick?: (edge: Edge<EdgeData>) => void
}
export function CanvasContainer({ onConnect: onConnectProp }: CanvasContainerProps) {
export function CanvasContainer({ onConnect: onConnectProp, onEdgeDoubleClick }: CanvasContainerProps) {
const {
nodes, edges,
onNodesChange, onEdgesChange,
@@ -34,16 +35,38 @@ export function CanvasContainer({ onConnect: onConnectProp }: CanvasContainerPro
setSelectedNode(null)
}, [setSelectedNode])
const handleEdgeDoubleClick = useCallback((_: React.MouseEvent, edge: Edge<EdgeData>) => {
onEdgeDoubleClick?.(edge)
}, [onEdgeDoubleClick])
// Hide edges between a container-mode proxmox and its direct children
const visibleEdges = useMemo(() => {
const containerIds = new Set(
nodes
.filter((n) => n.type === 'proxmox' && n.data.container_mode !== false)
.map((n) => n.id)
)
const childParentMap = new Map(
nodes.filter((n) => n.data.parent_id).map((n) => [n.id, n.data.parent_id as string])
)
return (edges as Edge<EdgeData>[]).filter((e) => {
if (containerIds.has(e.source) && childParentMap.get(e.target) === e.source) return false
if (containerIds.has(e.target) && childParentMap.get(e.source) === e.target) return false
return true
})
}, [nodes, edges])
return (
<div className="w-full h-full">
<ReactFlow
nodes={nodes}
edges={edges as Edge<EdgeData>[]}
edges={visibleEdges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnectProp}
onNodeClick={onNodeClick}
onPaneClick={onPaneClick}
onEdgeDoubleClick={handleEdgeDoubleClick}
nodeTypes={nodeTypes}
edgeTypes={edgeTypes}
snapToGrid
@@ -1,6 +1,7 @@
import { Handle, Position, NodeResizer, type NodeProps, type Node } from '@xyflow/react'
import { Layers } from 'lucide-react'
import type { NodeData, NodeStatus } from '@/types'
import { BaseNode } from './BaseNode'
const STATUS_COLORS: Record<NodeStatus, string> = {
online: '#39d353',
@@ -11,7 +12,14 @@ const STATUS_COLORS: Record<NodeStatus, string> = {
const GLOW = '#ff6e00'
export function ProxmoxGroupNode({ data, selected }: NodeProps<Node<NodeData>>) {
export function ProxmoxGroupNode(props: NodeProps<Node<NodeData>>) {
const { data, selected } = props
// Render as a regular node when container mode is disabled
if (data.container_mode === false) {
return <BaseNode {...props} icon={Layers} glowColor={GLOW} />
}
const statusColor = STATUS_COLORS[data.status]
const isOnline = data.status === 'online'