diff --git a/src/views/states/topology/Topology.tsx b/src/views/states/topology/Topology.tsx
index 5bce174..3c043e6 100644
--- a/src/views/states/topology/Topology.tsx
+++ b/src/views/states/topology/Topology.tsx
@@ -14,6 +14,7 @@ import {
VisualizationSurface,
} from '@patternfly/react-topology';
import { V1beta1NodeNetworkState } from '@types';
+import { isEmpty } from '@utils/helpers';
import TopologySidebar from './components/TopologySidebar/TopologySidebar';
import TopologyToolbar from './components/TopologyToolbar/TopologyToolbar';
@@ -39,84 +40,78 @@ const Topology: FC = () => {
);
useEffect(() => {
- if (loaded && !error) {
- const filteredStates =
- selectedNodeFilters.length > 0
- ? states.filter((state) => selectedNodeFilters.includes(state.metadata.name))
- : undefined;
+ if (!loaded || error || isEmpty(states)) return;
- const topologyModel = transformDataToTopologyModel(states, filteredStates);
+ const filteredStates =
+ selectedNodeFilters.length > 0
+ ? states.filter((state) => selectedNodeFilters.includes(state.metadata.name))
+ : undefined;
- if (!visualization) {
- const newVisualization = new Visualization();
- newVisualization.registerLayoutFactory(layoutFactory);
- newVisualization.registerComponentFactory(componentFactory);
- newVisualization.addEventListener(SELECTION_EVENT, setSelectedIds);
- newVisualization.setFitToScreenOnLayout(true);
- newVisualization.fromModel(topologyModel);
- restoreNodePositions(newVisualization);
+ const topologyModel = transformDataToTopologyModel(states, filteredStates);
- setVisualization(newVisualization);
- } else {
- visualization.fromModel(topologyModel);
- restoreNodePositions(visualization);
- }
- }
- }, [states, loaded, error, selectedNodeFilters]);
-
- useEffect(() => {
- if (visualization) {
- visualization.addEventListener(NODE_POSITIONING_EVENT, () =>
- saveNodePositions(visualization),
+ if (!visualization) {
+ const newVisualization = new Visualization();
+ newVisualization.registerLayoutFactory(layoutFactory);
+ newVisualization.registerComponentFactory(componentFactory);
+ newVisualization.addEventListener(SELECTION_EVENT, setSelectedIds);
+ newVisualization.addEventListener(NODE_POSITIONING_EVENT, () =>
+ saveNodePositions(newVisualization),
);
- visualization.addEventListener(GRAPH_POSITIONING_EVENT, () =>
- saveNodePositions(visualization),
+ newVisualization.addEventListener(GRAPH_POSITIONING_EVENT, () =>
+ saveNodePositions(newVisualization),
);
+ newVisualization.setFitToScreenOnLayout(true);
+ newVisualization.fromModel(topologyModel, false);
+ restoreNodePositions(newVisualization);
+ setVisualization(newVisualization);
+ } else {
+ visualization.fromModel(topologyModel);
+ restoreNodePositions(visualization);
}
- }, [visualization]);
+ }, [states, loaded, error, selectedNodeFilters]);
return (
-
- }
- viewToolbar={
-
- }
- controlBar={
- {
- visualization.getGraph().scaleBy(4 / 3);
- }),
- zoomOutCallback: action(() => {
- visualization.getGraph().scaleBy(0.75);
- }),
- fitToScreenCallback: action(() => {
- visualization.getGraph().fit(40);
- }),
- resetViewCallback: action(() => {
- visualization.getGraph().reset();
- visualization.getGraph().layout();
- }),
- legend: false,
- })}
- />
- }
- >
-
+
+
+ }
+ viewToolbar={
+
+ }
+ controlBar={
+ {
+ visualization.getGraph().scaleBy(4 / 3);
+ }),
+ zoomOutCallback: action(() => {
+ visualization.getGraph().scaleBy(0.75);
+ }),
+ fitToScreenCallback: action(() => {
+ visualization.getGraph().fit(40);
+ }),
+ resetViewCallback: action(() => {
+ visualization.getGraph().reset();
+ visualization.getGraph().layout();
+ }),
+ legend: false,
+ })}
+ />
+ }
+ >
-
-
+
+
);
};
diff --git a/src/views/states/topology/components/CustomNode/CustomNode.tsx b/src/views/states/topology/components/CustomNode/CustomNode.tsx
index eab8b2b..a9a5b4d 100644
--- a/src/views/states/topology/components/CustomNode/CustomNode.tsx
+++ b/src/views/states/topology/components/CustomNode/CustomNode.tsx
@@ -18,24 +18,15 @@ type CustomNodeProps = {
WithDragNodeProps &
WithDndDropProps;
-const CustomNode: FC = ({ element, onSelect, selected, ...rest }) => {
+const CustomNode: FC = ({ element, ...rest }) => {
const data = element.getData();
const Icon = data.icon;
const { width, height } = element.getBounds();
const xCenter = (width - ICON_SIZE) / 2;
const yCenter = (height - ICON_SIZE) / 2;
-
return (
-
+
diff --git a/src/views/states/topology/utils/position.ts b/src/views/states/topology/utils/position.ts
index 303f7bd..ba35279 100644
--- a/src/views/states/topology/utils/position.ts
+++ b/src/views/states/topology/utils/position.ts
@@ -8,16 +8,11 @@ export const saveNodePositions = (visualization: Visualization) => {
// Traverse all nodes and their children
graph.getNodes().forEach((node) => {
+ nodePositions[node.getId()] = node.getPosition();
if (node.isGroup()) {
- // Save the group node position
- nodePositions[node.getId()] = node.getPosition();
-
- // Save all child node positions
node.getAllNodeChildren().forEach((childNode) => {
nodePositions[childNode.getId()] = childNode.getPosition();
});
- } else {
- nodePositions[node.getId()] = node.getPosition();
}
});
diff --git a/src/views/states/topology/utils/utils.ts b/src/views/states/topology/utils/utils.ts
index c9716a1..76405b8 100644
--- a/src/views/states/topology/utils/utils.ts
+++ b/src/views/states/topology/utils/utils.ts
@@ -25,7 +25,12 @@ const getStatus = (iface: NodeNetworkConfigurationInterface): NodeStatus => {
};
const getIcon = (iface: NodeNetworkConfigurationInterface) => {
- if (!isEmpty(iface.bridge)) return BridgeIcon;
+ if (
+ iface.bridge ||
+ iface.type === InterfaceType.OVS_BRIDGE ||
+ iface.type === InterfaceType.LINUX_BRIDGE
+ )
+ return BridgeIcon;
if (iface.ethernet || iface.type === InterfaceType.ETHERNET) return EthernetIcon;
if (iface.type === InterfaceType.BOND) return LinkIcon;
return NetworkIcon;
@@ -34,92 +39,152 @@ const getIcon = (iface: NodeNetworkConfigurationInterface) => {
const createNodes = (
nnsName: string,
interfaces: NodeNetworkConfigurationInterface[],
-): NodeModel[] => {
- return interfaces.map((iface) => {
- const icon = getIcon(iface);
- return {
- id: `${nnsName}~${iface.name}`,
- type: ModelKind.node,
- label: iface.name,
- width: NODE_DIAMETER,
- height: NODE_DIAMETER,
- visible: !iface.patch && iface.type !== InterfaceType.LOOPBACK,
- shape: NodeShape.circle,
- status: getStatus(iface),
- data: {
- badge: 'I',
- icon,
- },
- parent: nnsName,
- };
- });
-};
-
-const createEdges = (
- nnsName: string,
- interfaces: NodeNetworkConfigurationInterface[],
-): EdgeModel[] => {
+): NodeModel[] =>
+ interfaces.map((iface) => ({
+ id: `${nnsName}~${iface.name}~${iface.type}`,
+ type: ModelKind.node,
+ label: iface.name,
+ width: NODE_DIAMETER,
+ height: NODE_DIAMETER,
+ visible: !iface.patch && iface.type !== InterfaceType.LOOPBACK,
+ shape: NodeShape.circle,
+ status: getStatus(iface),
+ data: {
+ icon: getIcon(iface),
+ type: iface.type,
+ bridgePorts: iface.bridge?.port,
+ bondPorts: iface['link-aggregation']?.port,
+ vlanBaseInterface: iface.vlan?.['base-iface'],
+ },
+ parent: nnsName,
+ }));
+
+// const createEdges = (
+// nnsName: string,
+// interfaces: NodeNetworkConfigurationInterface[],
+// ): EdgeModel[] => {
+// const edges: EdgeModel[] = [];
+// const patchConnections: { [key: string]: string } = {};
+
+// interfaces.forEach((iface) => {
+// if (iface.patch?.peer) {
+// patchConnections[iface.name] = iface.patch.peer;
+// }
+// });
+
+// interfaces.forEach((iface) => {
+// const nodeId = `${nnsName}~${iface.name}~${iface.type}`;
+
+// if (iface.bridge?.port) {
+// iface.bridge.port.forEach((prt) => {
+// if (patchConnections[prt.name]) {
+// const peerPatch = patchConnections[prt.name];
+// const peerBridge = interfaces.find((intf) =>
+// intf.bridge?.port.some((p) => p.name === peerPatch),
+// );
+// if (peerBridge) {
+// const peerBridgeId = `${nnsName}~${peerBridge.name}`;
+// edges.push({
+// id: `${nodeId}~${peerBridgeId}-edge`,
+// type: ModelKind.edge,
+// source: nodeId,
+// target: peerBridgeId,
+// });
+// }
+// } else if (prt.name && iface.name !== prt.name) {
+// edges.push({
+// id: `${nodeId}~${prt.name}-edge`,
+// type: ModelKind.edge,
+// source: nodeId,
+// target: `${nnsName}~${prt.name}~${iface.type}`,
+// });
+// }
+// });
+// }
+
+// if (iface.vlan?.['base-iface'] && iface.name === iface.vlan?.['base-iface']) {
+// edges.push({
+// id: `${nodeId}~${iface.vlan['base-iface']}-edge`,
+// type: ModelKind.edge,
+// source: nodeId,
+// target: `${nnsName}~${iface.vlan['base-iface']}`,
+// });
+// }
+
+// if (iface['link-aggregation']?.port) {
+// iface['link-aggregation'].port.forEach((prt: string) => {
+// if (iface.name !== prt) {
+// edges.push({
+// id: `${nodeId}~${prt}-edge`,
+// type: ModelKind.edge,
+// source: nodeId,
+// target: `${nnsName}~${prt}`,
+// });
+// }
+// });
+// }
+// });
+
+// return edges;
+// };
+
+const createEdges = (childNodes: NodeModel[]): EdgeModel[] => {
const edges: EdgeModel[] = [];
- const patchConnections: { [key: string]: string } = {};
- interfaces.forEach((iface: NodeNetworkConfigurationInterface) => {
- if (iface.patch?.peer) {
- patchConnections[iface.name] = iface.patch.peer;
- }
- });
+ childNodes.forEach((sourceNode) => {
+ // Find bridge connections
+ if (!isEmpty(sourceNode.data?.bridgePorts)) {
+ sourceNode.data?.bridgePorts.forEach((port) => {
+ const targetNode = childNodes.find(
+ (target) => target.label === port.name && target.id !== sourceNode.id,
+ );
- interfaces.forEach((iface: NodeNetworkConfigurationInterface) => {
- const nodeId = `${nnsName}~${iface.name}`;
-
- if (iface.bridge?.port) {
- iface.bridge.port.forEach((prt) => {
- if (patchConnections[prt.name]) {
- const peerPatch = patchConnections[prt.name];
- const peerBridge = interfaces.find((intf) =>
- intf.bridge?.port.some((p) => p.name === peerPatch),
- );
-
- if (peerBridge) {
- const peerBridgeId = `${nnsName}~${peerBridge.name}`;
- edges.push({
- id: `${nodeId}~${peerBridgeId}-edge`,
- type: ModelKind.edge,
- source: nodeId,
- target: peerBridgeId,
- });
- }
- } else if (prt.name && iface.name !== prt.name) {
+ if (!isEmpty(targetNode)) {
edges.push({
- id: `${nodeId}~${prt.name}-edge`,
+ id: `${sourceNode.id}~${targetNode.id}-edge`,
type: ModelKind.edge,
- source: nodeId,
- target: `${nnsName}~${prt.name}`,
+ source: sourceNode.id,
+ target: targetNode.id,
});
}
});
}
- if (iface.vlan?.['base-iface'] && iface.name === iface.vlan?.['base-iface']) {
- edges.push({
- id: `${nodeId}~${iface.vlan['base-iface']}-edge`,
- type: ModelKind.edge,
- source: nodeId,
- target: `${nnsName}~${iface.vlan['base-iface']}`,
- });
- }
+ // Find bond connections
+ if (!isEmpty(sourceNode.data?.vlanBaseInterface)) {
+ sourceNode.data?.bondPorts.forEach((port) => {
+ const targetNode = childNodes.find(
+ (target) => target.label === port && target.id !== sourceNode.id,
+ );
- if (iface['link-aggregation']?.port) {
- iface['link-aggregation'].port.forEach((prt: string) => {
- if (iface.name !== prt) {
+ if (!isEmpty(targetNode)) {
edges.push({
- id: `${nodeId}~${prt}-edge`,
+ id: `${sourceNode.id}~${targetNode.id}-edge`,
type: ModelKind.edge,
- source: nodeId,
- target: `${nnsName}~${prt}`,
+ source: sourceNode.id,
+ target: targetNode.id,
});
}
});
}
+
+ // Find vlan connections
+ if (!isEmpty(sourceNode.data?.bondPorts)) {
+ const baseInterface = sourceNode.data?.vlanBaseInterface;
+
+ const targetNode = childNodes.find(
+ (target) => target.label === baseInterface && target.id !== sourceNode.id,
+ );
+
+ if (!isEmpty(targetNode)) {
+ edges.push({
+ id: `${sourceNode.id}~${targetNode.id}-edge`,
+ type: ModelKind.edge,
+ source: sourceNode.id,
+ target: targetNode.id,
+ });
+ }
+ }
});
return edges;
@@ -144,7 +209,7 @@ const createGroupNode = (nnsName: string, childNodeIds: string[], visible: boole
export const transformDataToTopologyModel = (
data: V1beta1NodeNetworkState[],
- filteredData?: V1beta1NodeNetworkState[], // Optional filtered data parameter
+ filteredData?: V1beta1NodeNetworkState[],
): Model => {
const nodes: NodeModel[] = [];
const edges: EdgeModel[] = [];
@@ -154,7 +219,6 @@ export const transformDataToTopologyModel = (
const childNodes = createNodes(nnsName, nodeState.status.currentState.interfaces);
- // Determine visibility based on whether filteredData includes this nodeState
const isVisible = filteredData
? filteredData.some((filteredState) => filteredState.metadata.name === nnsName)
: true;
@@ -164,9 +228,10 @@ export const transformDataToTopologyModel = (
childNodes.map((child) => child.id),
isVisible,
);
- const nodeEdges = createEdges(nnsName, nodeState.status.currentState.interfaces);
-
nodes.push(groupNode, ...childNodes);
+
+ const nodeEdges = createEdges(childNodes);
+
edges.push(...nodeEdges);
});