From c5d6bb95f9c5dd607e9eb8f3fb4665198315c7fd Mon Sep 17 00:00:00 2001 From: Brey Rivera Date: Mon, 31 Mar 2025 19:05:59 -0400 Subject: [PATCH 1/2] Enhance context menu and link creation modal to display available ports for devices --- .../reactflow/overlayui/ContextMenu.tsx | 12 ++++++-- .../reactflow/overlayui/CreateLinkModal.tsx | 30 ++++++++++++------- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/frontend/src/components/reactflow/overlayui/ContextMenu.tsx b/frontend/src/components/reactflow/overlayui/ContextMenu.tsx index 731eb62..71c72a2 100644 --- a/frontend/src/components/reactflow/overlayui/ContextMenu.tsx +++ b/frontend/src/components/reactflow/overlayui/ContextMenu.tsx @@ -84,11 +84,17 @@ export default function ContextMenu({ onClick(); // close the context menu setDisableDelete(true); // Check if the device has any edges - const edgesForDevice = edges.filter(e => e.source === node?.data.deviceData?.name || e.target === node?.data.deviceData?.name); - console.log(edgesForDevice); + const edgesForDevice = edges.filter(e => e.source === node?.data.deviceData?.name || e.target === node?.data.deviceData?.name).map(e => ({ + value: e.id, + label: `(${e.source}) ${e.data?.sourcePort ?? ''} -> (${e.target}) ${e.data?.targetPort ?? ''}`, + firstLabDevice: e.source, + firstLabDevicePort: e.data?.sourcePort ?? '', + secondLabDevice: e.target, + secondLabDevicePort: e.data?.targetPort ?? '', + })); if (edgesForDevice.length > 0) { // attempt to delete all links - const numFailures = await deleteLinkBulk(new Set(currentEdges)); + const numFailures = await deleteLinkBulk(new Set(edgesForDevice)); // only remove the node if all links were successfully deleted if (numFailures === 0) { diff --git a/frontend/src/components/reactflow/overlayui/CreateLinkModal.tsx b/frontend/src/components/reactflow/overlayui/CreateLinkModal.tsx index 7de1fbe..d77faf1 100644 --- a/frontend/src/components/reactflow/overlayui/CreateLinkModal.tsx +++ b/frontend/src/components/reactflow/overlayui/CreateLinkModal.tsx @@ -109,11 +109,16 @@ export default function CreateLinkModal({ deviceData, currentDevicePorts, labDev disabled={!!deviceData?.name} > - {labDevices.filter((device) => device.userId == null || device.userId == user?.id).map((device) => ( - - ))} + {labDevices.filter((device) => device.userId == null || device.userId == user?.id).map((device) => { + const portsArray = device.ports.split(','); + const generatedPorts = portsArray.flatMap(portDef => generatePorts(portDef)); + const hasAvailablePorts = generatedPorts.some(port => !firstDeviceOccupiedPorts.includes(port)); + return ( + + ); + })}
@@ -139,11 +144,16 @@ export default function CreateLinkModal({ deviceData, currentDevicePorts, labDev className="block w-full mt-1 rounded-md bg-[#ffffff] focus:outline-none" > - {filteredLabDevices.filter((device) => (device.userId == null || device.userId == user?.id) && currentNodesInTopology.has(device.name)).map((device) => ( - - ))} + {filteredLabDevices.filter((device) => (device.userId == null || device.userId == user?.id) && currentNodesInTopology.has(device.name)).map((device) => { + const portsArray = device.ports.split(','); + const generatedPorts = portsArray.flatMap(portDef => generatePorts(portDef)); + const hasAvailablePorts = generatedPorts.some(port => !secondDeviceOccupiedPorts.includes(port)); + return ( + + ); + })}
From 18c3b2b91e0ab66f8f929b30f3fd31b210bd2a99 Mon Sep 17 00:00:00 2001 From: Brey Rivera Date: Mon, 31 Mar 2025 19:59:07 -0400 Subject: [PATCH 2/2] Refactor CreateLinkModal to manage occupied ports for second device and initialize on mount --- .../reactflow/overlayui/CreateLinkModal.tsx | 78 ++++++++++++------- 1 file changed, 50 insertions(+), 28 deletions(-) diff --git a/frontend/src/components/reactflow/overlayui/CreateLinkModal.tsx b/frontend/src/components/reactflow/overlayui/CreateLinkModal.tsx index d77faf1..ac92688 100644 --- a/frontend/src/components/reactflow/overlayui/CreateLinkModal.tsx +++ b/frontend/src/components/reactflow/overlayui/CreateLinkModal.tsx @@ -16,37 +16,24 @@ interface CreateLinkModalProps { export default function CreateLinkModal({ deviceData, currentDevicePorts, labDevices, onClose }: CreateLinkModalProps) { const { user } = useAuth(); - const { getEdges, getNodes } = useReactFlow, Edge>(); + const { getEdges } = useReactFlow, Edge>(); const { createLink } = useLinkOperations(); const [selectedFirstDevice, setSelectedFirstDevice] = useState(deviceData?.name ?? ""); const [selectedFirstDevicePort, setSelectedFirstDevicePort] = useState(""); const [selectedSecondDevice, setSelectedSecondDevice] = useState(""); const [selectedSecondDevicePort, setSelectedSecondDevicePort] = useState(""); const [availablePorts, setAvailablePorts] = useState([]); - const [filteredLabDevices] = useState(labDevices); const [firstDeviceOccupiedPorts, setFirstDeviceOccupiedPorts] = useState([]); - const [secondDeviceOccupiedPorts, setSecondDeviceOccupiedPorts] = useState([]); + const [secondDeviceOccupiedPorts, setSecondDeviceOccupiedPorts] = useState>({}); useEscapeKey(onClose); - // get a list of the current nodes in the topology to create a link - const nodes = getNodes(); - const currentNodesInTopology = new Set(nodes.map(n => n.data?.deviceData?.name)); - - // get occupied ports when selectedFirstDevice changes useEffect(() => { const edges = getEdges(); const ports = edges.filter((edge: Edge) => edge.source === selectedFirstDevice || edge.target === selectedFirstDevice).map((edge: Edge) => edge.id.split('-').filter((port: string) => port !== 'edge')); setFirstDeviceOccupiedPorts(ports.flat()); }, [selectedFirstDevice, getEdges]); - // get occupied ports when selectedSecondDevice changes - useEffect(() => { - const edges = getEdges(); - const ports = edges.filter((edge: Edge) => edge.source === selectedSecondDevice || edge.target === selectedSecondDevice).map((edge: Edge) => edge.id.split('-').filter((port: string) => port !== 'edge')); - setSecondDeviceOccupiedPorts(ports.flat()); - }, [selectedSecondDevice, getEdges]); - useEffect(() => { if (deviceData?.name) { setSelectedFirstDevice(deviceData.name); @@ -89,6 +76,31 @@ export default function CreateLinkModal({ deviceData, currentDevicePorts, labDev }); } + useEffect(() => { + const edges = getEdges(); + if (selectedSecondDevice) { + const ports = edges + .filter((edge: Edge) => edge.source === selectedSecondDevice || edge.target === selectedSecondDevice) + .map((edge: Edge) => edge.id.split('-').filter((port: string) => port !== 'edge')); + setSecondDeviceOccupiedPorts((prev) => ({ + ...prev, + [selectedSecondDevice]: ports.flat(), + })); + } else { + // Initialize occupied ports for all devices on mount + const allOccupiedPorts = labDevices.reduce((acc, device) => { + const devicePorts = edges + .filter((edge: Edge) => edge.source === device.name || edge.target === device.name) + .map((edge: Edge) => edge.id.split('-').filter((port: string) => port !== 'edge')) + .flat(); + acc[device.name] = devicePorts; + return acc; + }, {} as Record); + + setSecondDeviceOccupiedPorts(allOccupiedPorts); + } + }, [selectedSecondDevice, getEdges, labDevices]); + return (
@@ -115,7 +127,7 @@ export default function CreateLinkModal({ deviceData, currentDevicePorts, labDev const hasAvailablePorts = generatedPorts.some(port => !firstDeviceOccupiedPorts.includes(port)); return ( ); })} @@ -127,9 +139,10 @@ export default function CreateLinkModal({ deviceData, currentDevicePorts, labDev value={selectedFirstDevicePort} onChange={(e) => setSelectedFirstDevicePort(e.target.value)} className="block w-full mt-1 rounded-md bg-[#ffffff] focus:outline-none" + > - {(deviceData ? currentDevicePorts : filteredLabDevices.map(device => device.ports.split(',').flatMap(portDef => generatePorts(portDef)))).flat().map((port, index) => ( + {(deviceData ? currentDevicePorts : labDevices.map(device => device.ports.split(',').flatMap(portDef => generatePorts(portDef)))).flat().map((port, index) => ( @@ -144,13 +157,18 @@ export default function CreateLinkModal({ deviceData, currentDevicePorts, labDev className="block w-full mt-1 rounded-md bg-[#ffffff] focus:outline-none" > - {filteredLabDevices.filter((device) => (device.userId == null || device.userId == user?.id) && currentNodesInTopology.has(device.name)).map((device) => { + {labDevices.filter((d) => d.name !== deviceData?.name).map((device) => { const portsArray = device.ports.split(','); const generatedPorts = portsArray.flatMap(portDef => generatePorts(portDef)); - const hasAvailablePorts = generatedPorts.some(port => !secondDeviceOccupiedPorts.includes(port)); + const occupiedPorts = secondDeviceOccupiedPorts[device.name] || []; + const hasAvailablePorts = generatedPorts.some(port => !occupiedPorts.includes(port)); return ( - ); })} @@ -161,14 +179,18 @@ export default function CreateLinkModal({ deviceData, currentDevicePorts, labDev