diff --git a/frontend/src/components/network/hooks/useDiagramCommands.ts b/frontend/src/components/network/hooks/useDiagramCommands.ts index 4d57e08b..0e728ffb 100644 --- a/frontend/src/components/network/hooks/useDiagramCommands.ts +++ b/frontend/src/components/network/hooks/useDiagramCommands.ts @@ -123,7 +123,7 @@ export function useDiagramCommands({ const canDistribute = selectedNodes.length >= 3 // ── Grouping ─────────────────────────────────────────────────────────── - const groupSelection = useCallback(() => { + const groupSelection = useCallback((groupType: string = 'custom') => { if (selectedNodes.length < 2) return pushHistory(nodes, edges) const PADDING = 24 @@ -137,7 +137,7 @@ export function useDiagramCommands({ type: 'group', position: { x: minX, y: minY }, style: { width: maxX - minX, height: maxY - minY }, - data: { label: 'Group', groupType: 'custom' }, + data: { label: groupType.charAt(0).toUpperCase() + groupType.slice(1), groupType }, selected: false, } setNodes(prev => [ diff --git a/frontend/src/components/network/panels/PropertiesPanel.tsx b/frontend/src/components/network/panels/PropertiesPanel.tsx index d319a4da..229da3b7 100644 --- a/frontend/src/components/network/panels/PropertiesPanel.tsx +++ b/frontend/src/components/network/panels/PropertiesPanel.tsx @@ -4,6 +4,7 @@ import { AlignStartVertical, AlignCenterHorizontal, AlignEndVertical, AlignStartHorizontal, AlignCenterVertical, AlignEndHorizontal, AlignHorizontalSpaceAround, AlignVerticalSpaceAround, + BoxSelect, Ungroup, } from 'lucide-react' import { cn } from '@/lib/utils' import type { DeviceProperties, DiagramEdge } from '@/types' @@ -31,6 +32,10 @@ interface PropertiesPanelProps { onDistributeV: () => void canAlign: boolean canDistribute: boolean + canGroup: boolean + canUngroup: boolean + onGroupSelection: (groupType: string) => void + onUngroupSelection: () => void } type NodeStatus = 'online' | 'offline' | 'degraded' | 'unknown' @@ -84,6 +89,14 @@ function SectionDivider({ label }: { label: string }) { ) } +const GROUP_TYPES = [ + { value: 'subnet', label: 'Subnet' }, + { value: 'vlan', label: 'VLAN' }, + { value: 'site', label: 'Site' }, + { value: 'dmz', label: 'DMZ' }, + { value: 'custom', label: 'Custom' }, +] + export function PropertiesPanel({ selectedNode, selectedEdge, @@ -105,8 +118,13 @@ export function PropertiesPanel({ onDistributeV, canAlign, canDistribute, + canGroup, + canUngroup, + onGroupSelection, + onUngroupSelection, }: PropertiesPanelProps) { const [deleteConfirm, setDeleteConfirm] = useState(false) + const [pendingGroupType, setPendingGroupType] = useState('subnet') // Reset confirm state whenever the selection changes // eslint-disable-next-line react-hooks/set-state-in-effect @@ -178,6 +196,38 @@ export function PropertiesPanel({ )} + {(canGroup || canUngroup) && ( +
+
Grouping
+ {canGroup && ( +
+ + +
+ )} + {canUngroup && ( + + )} +
+ )} ) diff --git a/frontend/src/pages/NetworkDiagrams/DiagramEditor.tsx b/frontend/src/pages/NetworkDiagrams/DiagramEditor.tsx index 5259f39a..a810cd93 100644 --- a/frontend/src/pages/NetworkDiagrams/DiagramEditor.tsx +++ b/frontend/src/pages/NetworkDiagrams/DiagramEditor.tsx @@ -399,11 +399,22 @@ function DiagramEditorInner() { const handlePaneContextMenu = useCallback((event: MouseEvent | React.MouseEvent) => { event.preventDefault() - setContextMenu({ - type: 'canvas', - position: { x: event.clientX, y: event.clientY }, - }) - }, []) + // Group nodes pass pointer events through to children, so right-clicking a group + // may fire onPaneContextMenu instead of onNodeContextMenu. If nodes are selected, + // show the node context menu so group/align/ungroup options are accessible. + const selected = getNodes().filter(n => n.selected) + if (selected.length > 0) { + setContextMenu({ + type: 'node', + position: { x: event.clientX, y: event.clientY }, + }) + } else { + setContextMenu({ + type: 'canvas', + position: { x: event.clientX, y: event.clientY }, + }) + } + }, [getNodes]) const closeContextMenu = useCallback(() => { setContextMenu(null) @@ -735,6 +746,10 @@ function DiagramEditorInner() { onDistributeV={diagramCommands.distributeVertically} canAlign={diagramCommands.canAlign} canDistribute={diagramCommands.canDistribute} + canGroup={diagramCommands.canGroup} + canUngroup={diagramCommands.canUngroup} + onGroupSelection={diagramCommands.groupSelection} + onUngroupSelection={diagramCommands.ungroupSelection} /> {contextMenu && (