From 142113904a666149932fa361dd9896f1b4f2eb6a Mon Sep 17 00:00:00 2001 From: kunal-511 Date: Sat, 6 Dec 2025 17:20:46 +0530 Subject: [PATCH 1/6] Fix Wec connectors layout Signed-off-by: kunal-511 --- frontend/src/components/WecsTopology.tsx | 206 +++++++++--------- .../components/wds_topology/FlowCanvas.tsx | 22 ++ 2 files changed, 129 insertions(+), 99 deletions(-) diff --git a/frontend/src/components/WecsTopology.tsx b/frontend/src/components/WecsTopology.tsx index 1e35ddf67..37a8e2bb7 100644 --- a/frontend/src/components/WecsTopology.tsx +++ b/frontend/src/components/WecsTopology.tsx @@ -258,15 +258,18 @@ const getLayoutedElements = ( nodes: CustomNode[], edges: CustomEdge[], direction = 'LR', - prevNodes: React.MutableRefObject, - currentZoom: number + prevNodes: React.MutableRefObject ) => { - const scaleFactor = Math.max(0.5, Math.min(2.0, currentZoom)); - const NODE_WIDTH = 146 * scaleFactor; - const NODE_HEIGHT = 30 * scaleFactor; - const NODE_SEP = 40 * scaleFactor; - const RANK_SEP = 100 * scaleFactor; - const CHILD_SPACING = NODE_HEIGHT + 30 * scaleFactor; + // Use fixed layout values - let ReactFlow handle zoom visually + const NODE_WIDTH = 146; + const NODE_HEIGHT = 30; + const NODE_SEP = 40; + const RANK_SEP = 100; + const CHILD_SPACING = NODE_HEIGHT + 30; + + if (nodes.length === 0) { + return { nodes: [], edges: [] }; + } // Step 1: Initial Dagre layout const dagreGraph = new dagre.graphlib.Graph(); @@ -276,14 +279,17 @@ const getLayoutedElements = ( const nodeMap = new Map(); const newNodes: CustomNode[] = []; - const shouldRecalculate = true; - if (!shouldRecalculate && Math.abs(nodes.length - prevNodes.current.length) <= 5) { + // recalculate only if node count changes significantly or if this is first render + const shouldRecalculate = prevNodes.current.length === 0 || + Math.abs(nodes.length - prevNodes.current.length) > 0; + + if (!shouldRecalculate) { prevNodes.current.forEach(node => nodeMap.set(node.id, node)); } nodes.forEach(node => { const cachedNode = nodeMap.get(node.id); - if (!cachedNode || !isEqual(cachedNode, node) || shouldRecalculate) { + if (!cachedNode || shouldRecalculate) { dagreGraph.setNode(node.id, { width: NODE_WIDTH, height: NODE_HEIGHT }); newNodes.push(node); } else { @@ -291,11 +297,15 @@ const getLayoutedElements = ( } }); - edges.forEach(edge => { - dagreGraph.setEdge(edge.source, edge.target); - }); + if (shouldRecalculate) { + edges.forEach(edge => { + dagreGraph.setEdge(edge.source, edge.target); + }); - dagre.layout(dagreGraph); + dagre.layout(dagreGraph); + } else { + return { nodes: prevNodes.current, edges }; + } const layoutedNodes = newNodes.map(node => { const dagreNode = dagreGraph.node(node.id); @@ -303,8 +313,8 @@ const getLayoutedElements = ( ? { ...node, position: { - x: dagreNode.x - NODE_WIDTH / 2 + 50 * scaleFactor, - y: dagreNode.y - NODE_HEIGHT / 2 + 50 * scaleFactor, + x: dagreNode.x - NODE_WIDTH / 2 + 50, + y: dagreNode.y - NODE_HEIGHT / 2 + 50, }, } : node; @@ -540,7 +550,6 @@ const WecsTreeview = () => { const [isExpanded, setIsExpanded] = useState(true); const [isFullscreen, setIsFullscreen] = useState(false); const nodeCache = useRef>(new Map()); - const edgeCache = useRef>(new Map()); const edgeIdCounter = useRef(0); const prevNodes = useRef([]); const renderStartTime = useRef(0); @@ -556,39 +565,27 @@ const WecsTreeview = () => { renderStartTime.current = performance.now(); }, []); - // Add effect to update node styles when theme or zoom changes const updateNodeStyles = useCallback(() => { - if (nodes.length > 0) { - setNodes(currentNodes => { - return currentNodes.map(node => { - return { - ...node, - style: { - ...getScaledNodeStyle(currentZoom), - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - backgroundColor: theme === 'dark' ? 'rgba(51, 51, 51, 0)' : 'rgba(255, 255, 255, 0)', - color: theme === 'dark' ? 'rgba(255, 255, 255, 0)' : 'rgba(0, 0, 0, 0)', - border: '1px solid rgba(0, 0, 0, 0)', - transition: 'all 0.2s ease-in-out', - }, - }; - }); - }); - - // Update edge styles for the current theme - setEdges(currentEdges => { - return currentEdges.map(edge => ({ - ...edge, + setNodes(currentNodes => { + if (currentNodes.length === 0) return currentNodes; + + return currentNodes.map(node => { + return { + ...node, style: { - ...edge.style, - stroke: theme === 'dark' ? 'rgba(255, 255, 255, 0)' : 'rgba(0, 0, 0, 0)', + ...getScaledNodeStyle(currentZoom), + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + backgroundColor: theme === 'dark' ? 'rgba(51, 51, 51, 0)' : 'rgba(255, 255, 255, 0)', + color: theme === 'dark' ? 'rgba(255, 255, 255, 0)' : 'rgba(0, 0, 0, 0)', + border: '1px solid rgba(0, 0, 0, 0)', + transition: 'all 0.2s ease-in-out', }, - })); + }; }); - } - }, [nodes.length, currentZoom, theme, getScaledNodeStyle]); + }); + }, [currentZoom, theme, getScaledNodeStyle]); useEffect(() => { updateNodeStyles(); @@ -824,37 +821,32 @@ const WecsTreeview = () => { if (parent && stateRef.current.isExpanded) { const uniqueSuffix = resourceData?.metadata?.uid || edgeIdCounter.current++; const edgeId = `edge-${parent}-${id}-${uniqueSuffix}`; - const cachedEdge = edgeCache.current.get(edgeId); - if (!cachedEdge) { - const edge = { - id: edgeId, - source: parent, - target: id, - type: edgeType, - animated: true, - style: { stroke: theme === 'dark' ? '#ccc' : '#a3a3a3', strokeDasharray: '2,2' }, - markerEnd: { - type: MarkerType.ArrowClosed, - color: theme === 'dark' ? '#ccc' : '#a3a3a3', - }, - }; - newEdges.push(edge); - edgeCache.current.set(edgeId, edge); - } else { - // Update cached edge styles for the current theme - const markerEnd: { type: MarkerType; color?: string; width?: number; height?: number } = { - type: cachedEdge.markerEnd?.type || MarkerType.ArrowClosed, - color: theme === 'dark' ? '#ccc' : '#a3a3a3', - }; - - const updatedEdge = { - ...cachedEdge, - style: { stroke: theme === 'dark' ? '#ccc' : '#a3a3a3', strokeDasharray: '2,2' }, - markerEnd, - type: edgeType, - }; - newEdges.push(updatedEdge); - } + const edge = { + id: edgeId, + source: parent, + target: id, + type: edgeType, + animated: true, + style: { + stroke: theme === 'dark' ? 'url(#edge-gradient-dark)' : 'url(#edge-gradient-light)', + strokeWidth: 2, + opacity: 0.8, + transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)', + filter: + theme === 'dark' + ? 'drop-shadow(0 2px 4px rgba(0,0,0,0.3))' + : 'drop-shadow(0 1px 2px rgba(0,0,0,0.1))', + strokeLinecap: 'round' as const, + strokeLinejoin: 'round' as const, + }, + markerEnd: { + type: MarkerType.ArrowClosed, + width: 12, + height: 12, + color: theme === 'dark' ? '#64748b' : '#94a3b8', + }, + }; + newEdges.push(edge); } }, [getTimeAgo, handleClosePanel, handleMenuOpen, theme, currentZoom, getScaledNodeStyle, edgeType] @@ -868,15 +860,16 @@ const WecsTreeview = () => { setIsTransforming(false); return; } - const clusterTimestampMap = await fetchAllClusterTimestamps(data); + + requestAnimationFrame(async () => { + const clusterTimestampMap = await fetchAllClusterTimestamps(data); - // Clear caches when theme changes to ensure proper styling - nodeCache.current.clear(); - edgeCache.current.clear(); - edgeIdCounter.current = 0; + // Clear node cache to ensure fresh nodes with updated styles + nodeCache.current.clear(); + edgeIdCounter.current = 0; - const newNodes: CustomNode[] = []; - const newEdges: CustomEdge[] = []; + const newNodes: CustomNode[] = []; + const newEdges: CustomEdge[] = []; if (!stateRef.current.isExpanded) { data.forEach(cluster => { @@ -1266,27 +1259,42 @@ const WecsTreeview = () => { }); } - const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements( - newNodes, - newEdges, - 'LR', - prevNodes, - currentZoom - ); - if (!isEqual(nodes, layoutedNodes)) setNodes(layoutedNodes); - if (!isEqual(edges, layoutedEdges)) setEdges(layoutedEdges); - prevNodes.current = layoutedNodes; - setIsTransforming(false); + const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements( + newNodes, + newEdges, + 'LR', + prevNodes + ); + + if (!isEqual(nodes, layoutedNodes)) { + setNodes(layoutedNodes); + requestAnimationFrame(() => { + if (!isEqual(edges, layoutedEdges)) setEdges(layoutedEdges); + }); + } else if (!isEqual(edges, layoutedEdges)) { + setEdges(layoutedEdges); + } + + prevNodes.current = layoutedNodes; + setIsTransforming(false); + }); }, - [createNode, nodes, edges, fetchAllClusterTimestamps, currentZoom, edgeType] + [createNode, fetchAllClusterTimestamps, edgeType] ); // Memoize the data processing to avoid unnecessary re-renders const memoizedWecsData = useMemo(() => wecsData, [wecsData]); // Memoize node and edge rendering to prevent unnecessary re-renders - const memoizedNodes = useMemo(() => nodes, [nodes]); - const memoizedEdges = useMemo(() => edges, [edges]); + const memoizedNodes = useMemo(() => { + if (nodes.length === 0) return []; + return nodes; + }, [nodes]); + + const memoizedEdges = useMemo(() => { + if (edges.length === 0) return []; + return edges; + }, [edges]); useEffect(() => { if (memoizedWecsData !== null && !isEqual(memoizedWecsData, prevWecsData.current)) { diff --git a/frontend/src/components/wds_topology/FlowCanvas.tsx b/frontend/src/components/wds_topology/FlowCanvas.tsx index 9911d2a4e..7b689dc85 100644 --- a/frontend/src/components/wds_topology/FlowCanvas.tsx +++ b/frontend/src/components/wds_topology/FlowCanvas.tsx @@ -325,12 +325,34 @@ export const FlowCanvas = memo(({ nodes, edges, theme }) => { /* Keep edges visible and interactive */ .react-flow__edge { pointer-events: all !important; + opacity: 0; + animation: fadeInEdge 0.3s ease-in forwards; + } + + @keyframes fadeInEdge { + from { + opacity: 0; + } + to { + opacity: 1; + } } /* Ensure edge paths remain visible */ .react-flow__edge-path { stroke-width: 2; stroke-dasharray: none; + will-change: auto; + } + + .react-flow__edges { + will-change: transform; + transform: translateZ(0); + } + + .react-flow__nodes { + will-change: transform; + transform: translateZ(0); } /* Ensure menu buttons are always clickable */ From c5bf8c9932f06d8469c92e0dd76859843cf83988 Mon Sep 17 00:00:00 2001 From: kunal-511 Date: Sat, 6 Dec 2025 17:21:21 +0530 Subject: [PATCH 2/6] fix formatting Signed-off-by: kunal-511 --- frontend/src/components/WecsTopology.tsx | 690 +++++++++++------------ 1 file changed, 345 insertions(+), 345 deletions(-) diff --git a/frontend/src/components/WecsTopology.tsx b/frontend/src/components/WecsTopology.tsx index 37a8e2bb7..5249fe49b 100644 --- a/frontend/src/components/WecsTopology.tsx +++ b/frontend/src/components/WecsTopology.tsx @@ -280,9 +280,9 @@ const getLayoutedElements = ( const newNodes: CustomNode[] = []; // recalculate only if node count changes significantly or if this is first render - const shouldRecalculate = prevNodes.current.length === 0 || - Math.abs(nodes.length - prevNodes.current.length) > 0; - + const shouldRecalculate = + prevNodes.current.length === 0 || Math.abs(nodes.length - prevNodes.current.length) > 0; + if (!shouldRecalculate) { prevNodes.current.forEach(node => nodeMap.set(node.id, node)); } @@ -568,7 +568,7 @@ const WecsTreeview = () => { const updateNodeStyles = useCallback(() => { setNodes(currentNodes => { if (currentNodes.length === 0) return currentNodes; - + return currentNodes.map(node => { return { ...node, @@ -860,7 +860,7 @@ const WecsTreeview = () => { setIsTransforming(false); return; } - + requestAnimationFrame(async () => { const clusterTimestampMap = await fetchAllClusterTimestamps(data); @@ -871,196 +871,222 @@ const WecsTreeview = () => { const newNodes: CustomNode[] = []; const newEdges: CustomEdge[] = []; - if (!stateRef.current.isExpanded) { - data.forEach(cluster => { - const clusterId = `cluster:${cluster.cluster}`; - const timestamp = clusterTimestampMap.get(cluster.cluster) || ''; - - createNode( - clusterId, - cluster.cluster, - 'cluster', - 'Active', - timestamp, - undefined, - { - apiVersion: 'v1', - kind: 'Cluster', - metadata: { name: cluster.cluster, namespace: '', creationTimestamp: timestamp }, - status: { phase: 'Active' }, - }, - null, - newNodes, - newEdges - ); - }); - } else { - data.forEach(cluster => { - const clusterId = `cluster:${cluster.cluster}`; - const timestamp = clusterTimestampMap.get(cluster.cluster) || ''; - - createNode( - clusterId, - cluster.cluster, - 'cluster', - 'Active', - timestamp, - undefined, - { - apiVersion: 'v1', - kind: 'Cluster', - metadata: { name: cluster.cluster, namespace: '', creationTimestamp: timestamp }, - status: { phase: 'Active' }, - }, - null, - newNodes, - newEdges - ); - - if (cluster.namespaces && Array.isArray(cluster.namespaces)) { - cluster.namespaces.forEach(namespace => { - const namespaceId = `ns:${cluster.cluster}:${namespace.namespace}`; - createNode( - namespaceId, - namespace.namespace, - 'namespace', - 'Active', - '', - namespace.namespace, - { - apiVersion: 'v1', - kind: 'Namespace', - metadata: { - name: namespace.namespace, - namespace: namespace.namespace, - creationTimestamp: '', + if (!stateRef.current.isExpanded) { + data.forEach(cluster => { + const clusterId = `cluster:${cluster.cluster}`; + const timestamp = clusterTimestampMap.get(cluster.cluster) || ''; + + createNode( + clusterId, + cluster.cluster, + 'cluster', + 'Active', + timestamp, + undefined, + { + apiVersion: 'v1', + kind: 'Cluster', + metadata: { name: cluster.cluster, namespace: '', creationTimestamp: timestamp }, + status: { phase: 'Active' }, + }, + null, + newNodes, + newEdges + ); + }); + } else { + data.forEach(cluster => { + const clusterId = `cluster:${cluster.cluster}`; + const timestamp = clusterTimestampMap.get(cluster.cluster) || ''; + + createNode( + clusterId, + cluster.cluster, + 'cluster', + 'Active', + timestamp, + undefined, + { + apiVersion: 'v1', + kind: 'Cluster', + metadata: { name: cluster.cluster, namespace: '', creationTimestamp: timestamp }, + status: { phase: 'Active' }, + }, + null, + newNodes, + newEdges + ); + + if (cluster.namespaces && Array.isArray(cluster.namespaces)) { + cluster.namespaces.forEach(namespace => { + const namespaceId = `ns:${cluster.cluster}:${namespace.namespace}`; + createNode( + namespaceId, + namespace.namespace, + 'namespace', + 'Active', + '', + namespace.namespace, + { + apiVersion: 'v1', + kind: 'Namespace', + metadata: { + name: namespace.namespace, + namespace: namespace.namespace, + creationTimestamp: '', + }, + status: { phase: 'Active' }, }, - status: { phase: 'Active' }, - }, - clusterId, - newNodes, - newEdges - ); - - if (namespace.resourceTypes && Array.isArray(namespace.resourceTypes)) { - if (stateRef.current.isCollapsed) { - const resourceGroups: Record = {}; + clusterId, + newNodes, + newEdges + ); - namespace.resourceTypes.forEach(resourceType => { - // Skip Event type resources - if (resourceType.kind.toLowerCase() === 'event') return; - - resourceType.resources.forEach(resource => { - const kindLower = resourceType.kind.toLowerCase(); - if (!resourceGroups[kindLower]) { - resourceGroups[kindLower] = []; - } - resourceGroups[kindLower].push(resource.raw); - }); - }); + if (namespace.resourceTypes && Array.isArray(namespace.resourceTypes)) { + if (stateRef.current.isCollapsed) { + const resourceGroups: Record = {}; - Object.entries(resourceGroups).forEach(([kindLower, items]) => { - const count = items.length; - const groupId = `ns:${cluster.cluster}:${namespace.namespace}:${kindLower}:group`; - const status = items.some(item => item.status?.phase === 'Running') - ? 'Active' - : 'Inactive'; - const label = `${count} ${kindLower}${count !== 1 ? 's' : ''}`; - - createNode( - groupId, - label, - kindLower, - status, - items[0]?.metadata.creationTimestamp, - namespace.namespace, - items[0], - namespaceId, - newNodes, - newEdges - ); - }); - } else { - // Process all resource types for the expanded view - // First collect all ReplicaSet names that are children of deployments - const childReplicaSets = new Set(); + namespace.resourceTypes.forEach(resourceType => { + // Skip Event type resources + if (resourceType.kind.toLowerCase() === 'event') return; - namespace.resourceTypes.forEach(resourceType => { - if (resourceType.kind.toLowerCase() === 'deployment') { resourceType.resources.forEach(resource => { - if ( - resource && - resource.replicaSets && - Array.isArray(resource.replicaSets) - ) { - resource.replicaSets.forEach(rs => { - if (rs && rs.name) { - childReplicaSets.add(rs.name); - } - }); + const kindLower = resourceType.kind.toLowerCase(); + if (!resourceGroups[kindLower]) { + resourceGroups[kindLower] = []; } + resourceGroups[kindLower].push(resource.raw); }); - } - }); + }); - // Now process all resources while filtering ReplicaSets that are children - namespace.resourceTypes.forEach(resourceType => { - // Skip Event type resources - if (resourceType.kind.toLowerCase() === 'event') return; - - const kindLower = resourceType.kind.toLowerCase(); - - resourceType.resources.forEach((resource, index) => { - if (!resource || typeof resource !== 'object' || !resource.raw) return; - const rawResource = resource.raw; - if ( - !rawResource.metadata || - typeof rawResource.metadata !== 'object' || - !rawResource.metadata.name - ) - return; - - // Skip ReplicaSets that are already children of Deployments - if ( - kindLower === 'replicaset' && - childReplicaSets.has(rawResource.metadata.name) - ) { - return; - } + Object.entries(resourceGroups).forEach(([kindLower, items]) => { + const count = items.length; + const groupId = `ns:${cluster.cluster}:${namespace.namespace}:${kindLower}:group`; + const status = items.some(item => item.status?.phase === 'Running') + ? 'Active' + : 'Inactive'; + const label = `${count} ${kindLower}${count !== 1 ? 's' : ''}`; - const resourceId = `${kindLower}:${cluster.cluster}:${namespace.namespace}:${rawResource.metadata.name}:${index}`; - const status = rawResource.status?.phase || 'Active'; createNode( - resourceId, - rawResource.metadata.name, + groupId, + label, kindLower, status, - rawResource.metadata.creationTimestamp, + items[0]?.metadata.creationTimestamp, namespace.namespace, - rawResource, + items[0], namespaceId, newNodes, newEdges ); + }); + } else { + // Process all resource types for the expanded view + // First collect all ReplicaSet names that are children of deployments + const childReplicaSets = new Set(); + + namespace.resourceTypes.forEach(resourceType => { + if (resourceType.kind.toLowerCase() === 'deployment') { + resourceType.resources.forEach(resource => { + if ( + resource && + resource.replicaSets && + Array.isArray(resource.replicaSets) + ) { + resource.replicaSets.forEach(rs => { + if (rs && rs.name) { + childReplicaSets.add(rs.name); + } + }); + } + }); + } + }); + + // Now process all resources while filtering ReplicaSets that are children + namespace.resourceTypes.forEach(resourceType => { + // Skip Event type resources + if (resourceType.kind.toLowerCase() === 'event') return; + + const kindLower = resourceType.kind.toLowerCase(); + + resourceType.resources.forEach((resource, index) => { + if (!resource || typeof resource !== 'object' || !resource.raw) return; + const rawResource = resource.raw; + if ( + !rawResource.metadata || + typeof rawResource.metadata !== 'object' || + !rawResource.metadata.name + ) + return; + + // Skip ReplicaSets that are already children of Deployments + if ( + kindLower === 'replicaset' && + childReplicaSets.has(rawResource.metadata.name) + ) { + return; + } + + const resourceId = `${kindLower}:${cluster.cluster}:${namespace.namespace}:${rawResource.metadata.name}:${index}`; + const status = rawResource.status?.phase || 'Active'; + createNode( + resourceId, + rawResource.metadata.name, + kindLower, + status, + rawResource.metadata.creationTimestamp, + namespace.namespace, + rawResource, + namespaceId, + newNodes, + newEdges + ); - if (kindLower === 'deployment' && rawResource.spec) { - if (resource.replicaSets && Array.isArray(resource.replicaSets)) { - resource.replicaSets.forEach((rs, rsIndex) => { - const replicaSetId = `replicaset:${cluster.cluster}:${namespace.namespace}:${rs.name}:${rsIndex}`; - createNode( - replicaSetId, - rs.name, - 'replicaset', - rs.raw.status?.phase || status, - rs.raw.metadata.creationTimestamp, - namespace.namespace, - rs.raw, - resourceId, - newNodes, - newEdges - ); - if (rs.pods && Array.isArray(rs.pods)) { - rs.pods.forEach((pod, podIndex) => { + if (kindLower === 'deployment' && rawResource.spec) { + if (resource.replicaSets && Array.isArray(resource.replicaSets)) { + resource.replicaSets.forEach((rs, rsIndex) => { + const replicaSetId = `replicaset:${cluster.cluster}:${namespace.namespace}:${rs.name}:${rsIndex}`; + createNode( + replicaSetId, + rs.name, + 'replicaset', + rs.raw.status?.phase || status, + rs.raw.metadata.creationTimestamp, + namespace.namespace, + rs.raw, + resourceId, + newNodes, + newEdges + ); + if (rs.pods && Array.isArray(rs.pods)) { + rs.pods.forEach((pod, podIndex) => { + const podId = `pod:${cluster.cluster}:${namespace.namespace}:${pod.name}:${podIndex}`; + createNode( + podId, + pod.name, + 'pod', + pod.raw.status?.phase || status, + pod.raw.metadata.creationTimestamp, + namespace.namespace, + pod.raw, + replicaSetId, + newNodes, + newEdges + ); + }); + } + }); + } + } else if (kindLower === 'replicaset' && rawResource.spec) { + if ( + resource.replicaSets && + Array.isArray(resource.replicaSets) && + resource.replicaSets.length > 0 + ) { + const pods = resource.replicaSets[0].pods; + if (pods && Array.isArray(pods)) { + pods.forEach((pod, podIndex) => { const podId = `pod:${cluster.cluster}:${namespace.namespace}:${pod.name}:${podIndex}`; createNode( podId, @@ -1070,23 +1096,17 @@ const WecsTreeview = () => { pod.raw.metadata.creationTimestamp, namespace.namespace, pod.raw, - replicaSetId, + resourceId, newNodes, newEdges ); }); } - }); - } - } else if (kindLower === 'replicaset' && rawResource.spec) { - if ( - resource.replicaSets && - Array.isArray(resource.replicaSets) && - resource.replicaSets.length > 0 - ) { - const pods = resource.replicaSets[0].pods; - if (pods && Array.isArray(pods)) { - pods.forEach((pod, podIndex) => { + } + } else if (kindLower === 'statefulset' && rawResource.spec) { + // Display actual pods from the data + if (resource.pods && Array.isArray(resource.pods)) { + resource.pods.forEach((pod, podIndex) => { const podId = `pod:${cluster.cluster}:${namespace.namespace}:${pod.name}:${podIndex}`; createNode( podId, @@ -1102,162 +1122,142 @@ const WecsTreeview = () => { ); }); } + } else if (kindLower === 'daemonset' && rawResource.spec) { + // Display actual pods from the data + if (resource.pods && Array.isArray(resource.pods)) { + resource.pods.forEach((pod, podIndex) => { + const podId = `pod:${cluster.cluster}:${namespace.namespace}:${pod.name}:${podIndex}`; + createNode( + podId, + pod.name, + 'pod', + pod.raw.status?.phase || status, + pod.raw.metadata.creationTimestamp, + namespace.namespace, + pod.raw, + resourceId, + newNodes, + newEdges + ); + }); + } + } else if (kindLower === 'replicationcontroller' && rawResource.spec) { + // Display actual pods from the data + if (resource.pods && Array.isArray(resource.pods)) { + resource.pods.forEach((pod, podIndex) => { + const podId = `pod:${cluster.cluster}:${namespace.namespace}:${pod.name}:${podIndex}`; + createNode( + podId, + pod.name, + 'pod', + pod.raw.status?.phase || status, + pod.raw.metadata.creationTimestamp, + namespace.namespace, + pod.raw, + resourceId, + newNodes, + newEdges + ); + }); + } + } else if (kindLower === 'cronjob' && rawResource.spec) { + // Display actual jobs and pods from the data if they exist + // No hardcoding + } else if (kindLower === 'job' && rawResource.spec) { + // Display actual pods from the data + if (resource.pods && Array.isArray(resource.pods)) { + resource.pods.forEach((pod, podIndex) => { + const podId = `pod:${cluster.cluster}:${namespace.namespace}:${pod.name}:${podIndex}`; + createNode( + podId, + pod.name, + 'pod', + pod.raw.status?.phase || status, + pod.raw.metadata.creationTimestamp, + namespace.namespace, + pod.raw, + resourceId, + newNodes, + newEdges + ); + }); + } + } else if (kindLower === 'service' && rawResource.spec) { + // Create Endpoints node for the Service + createNode( + `${resourceId}:endpoints`, + `endpoints-${rawResource.metadata.name}`, + 'endpoints', + status, + undefined, + namespace.namespace, + rawResource, + resourceId, + newNodes, + newEdges + ); + } else if (kindLower === 'ingress' && rawResource.spec) { + // Only show the Ingress without creating services automatically + // Services will be shown if they exist in the actual data + } else if (kindLower === 'configmap') { + // Create Volume nodes for ConfigMap + createNode( + `${resourceId}:volume`, + `volume-${rawResource.metadata.name}`, + 'volume', + status || 'Unknown', + undefined, + namespace.namespace, + rawResource, + resourceId, + newNodes, + newEdges + ); + } else if (kindLower === 'secret') { + createNode( + `${resourceId}:envvar`, + `envvar-${rawResource.metadata.name}`, + 'envvar', + status, + undefined, + namespace.namespace, + rawResource, + resourceId, + newNodes, + newEdges + ); + } else if (kindLower === 'persistentvolumeclaim' && rawResource.spec) { + // Only show the PVC without creating a PV automatically + } else if (kindLower === 'storageclass' && rawResource.spec) { + // Only show the StorageClass without creating a PV automatically + } else if (kindLower === 'horizontalpodautoscaler' && rawResource.spec) { + // Only show the HPA without creating target resources automatically + // Target resources will be shown if they exist in the actual data + } else if (kindLower === 'rolebinding' && rawResource.roleRef) { + // Only show the RoleBinding without creating roles automatically + // Roles will be shown if they exist in the actual data + } else if (kindLower === 'clusterrolebinding' && rawResource.roleRef) { + // Only show the ClusterRoleBinding without creating cluster roles automatically + // ClusterRoles will be shown if they exist in the actual data + } else if (kindLower === 'poddisruptionbudget' && rawResource.spec) { + // Intentionally empty + } else if (kindLower === 'networkpolicy' && rawResource.spec) { + // Intentionally empty + } else if ( + kindLower === 'ingressclass' || + kindLower === 'mutatingwebhookconfiguration' || + kindLower === 'validatingwebhookconfiguration' + ) { + // Intentionally empty } - } else if (kindLower === 'statefulset' && rawResource.spec) { - // Display actual pods from the data - if (resource.pods && Array.isArray(resource.pods)) { - resource.pods.forEach((pod, podIndex) => { - const podId = `pod:${cluster.cluster}:${namespace.namespace}:${pod.name}:${podIndex}`; - createNode( - podId, - pod.name, - 'pod', - pod.raw.status?.phase || status, - pod.raw.metadata.creationTimestamp, - namespace.namespace, - pod.raw, - resourceId, - newNodes, - newEdges - ); - }); - } - } else if (kindLower === 'daemonset' && rawResource.spec) { - // Display actual pods from the data - if (resource.pods && Array.isArray(resource.pods)) { - resource.pods.forEach((pod, podIndex) => { - const podId = `pod:${cluster.cluster}:${namespace.namespace}:${pod.name}:${podIndex}`; - createNode( - podId, - pod.name, - 'pod', - pod.raw.status?.phase || status, - pod.raw.metadata.creationTimestamp, - namespace.namespace, - pod.raw, - resourceId, - newNodes, - newEdges - ); - }); - } - } else if (kindLower === 'replicationcontroller' && rawResource.spec) { - // Display actual pods from the data - if (resource.pods && Array.isArray(resource.pods)) { - resource.pods.forEach((pod, podIndex) => { - const podId = `pod:${cluster.cluster}:${namespace.namespace}:${pod.name}:${podIndex}`; - createNode( - podId, - pod.name, - 'pod', - pod.raw.status?.phase || status, - pod.raw.metadata.creationTimestamp, - namespace.namespace, - pod.raw, - resourceId, - newNodes, - newEdges - ); - }); - } - } else if (kindLower === 'cronjob' && rawResource.spec) { - // Display actual jobs and pods from the data if they exist - // No hardcoding - } else if (kindLower === 'job' && rawResource.spec) { - // Display actual pods from the data - if (resource.pods && Array.isArray(resource.pods)) { - resource.pods.forEach((pod, podIndex) => { - const podId = `pod:${cluster.cluster}:${namespace.namespace}:${pod.name}:${podIndex}`; - createNode( - podId, - pod.name, - 'pod', - pod.raw.status?.phase || status, - pod.raw.metadata.creationTimestamp, - namespace.namespace, - pod.raw, - resourceId, - newNodes, - newEdges - ); - }); - } - } else if (kindLower === 'service' && rawResource.spec) { - // Create Endpoints node for the Service - createNode( - `${resourceId}:endpoints`, - `endpoints-${rawResource.metadata.name}`, - 'endpoints', - status, - undefined, - namespace.namespace, - rawResource, - resourceId, - newNodes, - newEdges - ); - } else if (kindLower === 'ingress' && rawResource.spec) { - // Only show the Ingress without creating services automatically - // Services will be shown if they exist in the actual data - } else if (kindLower === 'configmap') { - // Create Volume nodes for ConfigMap - createNode( - `${resourceId}:volume`, - `volume-${rawResource.metadata.name}`, - 'volume', - status || 'Unknown', - undefined, - namespace.namespace, - rawResource, - resourceId, - newNodes, - newEdges - ); - } else if (kindLower === 'secret') { - createNode( - `${resourceId}:envvar`, - `envvar-${rawResource.metadata.name}`, - 'envvar', - status, - undefined, - namespace.namespace, - rawResource, - resourceId, - newNodes, - newEdges - ); - } else if (kindLower === 'persistentvolumeclaim' && rawResource.spec) { - // Only show the PVC without creating a PV automatically - } else if (kindLower === 'storageclass' && rawResource.spec) { - // Only show the StorageClass without creating a PV automatically - } else if (kindLower === 'horizontalpodautoscaler' && rawResource.spec) { - // Only show the HPA without creating target resources automatically - // Target resources will be shown if they exist in the actual data - } else if (kindLower === 'rolebinding' && rawResource.roleRef) { - // Only show the RoleBinding without creating roles automatically - // Roles will be shown if they exist in the actual data - } else if (kindLower === 'clusterrolebinding' && rawResource.roleRef) { - // Only show the ClusterRoleBinding without creating cluster roles automatically - // ClusterRoles will be shown if they exist in the actual data - } else if (kindLower === 'poddisruptionbudget' && rawResource.spec) { - // Intentionally empty - } else if (kindLower === 'networkpolicy' && rawResource.spec) { - // Intentionally empty - } else if ( - kindLower === 'ingressclass' || - kindLower === 'mutatingwebhookconfiguration' || - kindLower === 'validatingwebhookconfiguration' - ) { - // Intentionally empty - } + }); }); - }); + } } - } - }); - } - }); - } + }); + } + }); + } const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements( newNodes, @@ -1265,7 +1265,7 @@ const WecsTreeview = () => { 'LR', prevNodes ); - + if (!isEqual(nodes, layoutedNodes)) { setNodes(layoutedNodes); requestAnimationFrame(() => { @@ -1274,7 +1274,7 @@ const WecsTreeview = () => { } else if (!isEqual(edges, layoutedEdges)) { setEdges(layoutedEdges); } - + prevNodes.current = layoutedNodes; setIsTransforming(false); }); @@ -1290,7 +1290,7 @@ const WecsTreeview = () => { if (nodes.length === 0) return []; return nodes; }, [nodes]); - + const memoizedEdges = useMemo(() => { if (edges.length === 0) return []; return edges; From 0f6c23924889646ab7c660f4209d25cafbf0b456 Mon Sep 17 00:00:00 2001 From: kunal-511 Date: Sat, 6 Dec 2025 17:48:29 +0530 Subject: [PATCH 3/6] Fix edge style change Signed-off-by: kunal-511 --- frontend/src/components/WecsTopology.tsx | 70 +++++++++++++----------- 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/frontend/src/components/WecsTopology.tsx b/frontend/src/components/WecsTopology.tsx index 5249fe49b..ac7951e6a 100644 --- a/frontend/src/components/WecsTopology.tsx +++ b/frontend/src/components/WecsTopology.tsx @@ -573,7 +573,7 @@ const WecsTreeview = () => { return { ...node, style: { - ...getScaledNodeStyle(currentZoom), + ...node.style, display: 'flex', alignItems: 'center', justifyContent: 'space-between', @@ -585,12 +585,24 @@ const WecsTreeview = () => { }; }); }); - }, [currentZoom, theme, getScaledNodeStyle]); + }, [theme]); useEffect(() => { updateNodeStyles(); }, [updateNodeStyles]); + // Update edge types when edgeType changes + useEffect(() => { + if (edges.length > 0) { + setEdges(currentEdges => + currentEdges.map(edge => ({ + ...edge, + type: edgeType, + })) + ); + } + }, [edgeType]); + useEffect(() => { const timer = setTimeout(() => { setMinimumLoadingTimeElapsed(true); @@ -741,6 +753,21 @@ const WecsTreeview = () => { ].includes(parentType); } + // Fixed node styles + const nodeStyle = { + padding: '2px 12px', + fontSize: '6px', + width: '146px', + height: '30px', + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + backgroundColor: theme === 'dark' ? 'rgba(51, 51, 51, 0)' : 'rgba(255, 255, 255, 0)', + color: theme === 'dark' ? 'rgba(255, 255, 255, 0)' : 'rgba(0, 0, 0, 0)', + border: '1px solid rgba(0, 0, 0, 0)', + transition: 'all 0.2s ease-in-out', + }; + const node = cachedNode || ({ @@ -788,31 +815,13 @@ const WecsTreeview = () => { isDeploymentOrJobPod, }, position: { x: 0, y: 0 }, - style: { - ...getScaledNodeStyle(currentZoom), - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - backgroundColor: theme === 'dark' ? 'rgba(51, 51, 51, 0)' : 'rgba(255, 255, 255, 0)', - color: theme === 'dark' ? 'rgba(255, 255, 255, 0)' : 'rgba(0, 0, 0, 0)', - border: '1px solid rgba(0, 0, 0, 0)', - transition: 'all 0.2s ease-in-out', - }, + style: nodeStyle, sourcePosition: Position.Right, targetPosition: Position.Left, } as CustomNode); if (cachedNode) { - node.style = { - ...getScaledNodeStyle(currentZoom), - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - backgroundColor: theme === 'dark' ? 'rgba(51, 51, 51, 0)' : 'rgba(255, 255, 255, 0)', - color: theme === 'dark' ? 'rgba(255, 255, 255, 0)' : 'rgba(0, 0, 0, 0)', - border: '1px solid rgba(0, 0, 0, 0)', - transition: 'all 0.2s ease-in-out', - }; + node.style = nodeStyle; } if (!cachedNode) nodeCache.current.set(id, node); @@ -845,11 +854,15 @@ const WecsTreeview = () => { height: 12, color: theme === 'dark' ? '#64748b' : '#94a3b8', }, + data: { + status: 'default' as 'default' | 'active' | 'success' | 'warning' | 'error', + animated: true, + }, }; newEdges.push(edge); } }, - [getTimeAgo, handleClosePanel, handleMenuOpen, theme, currentZoom, getScaledNodeStyle, edgeType] + [getTimeAgo, handleClosePanel, handleMenuOpen, theme, edgeType] ); const transformDataToTree = useCallback( @@ -1279,23 +1292,18 @@ const WecsTreeview = () => { setIsTransforming(false); }); }, - [createNode, fetchAllClusterTimestamps, edgeType] + [createNode, fetchAllClusterTimestamps] ); // Memoize the data processing to avoid unnecessary re-renders const memoizedWecsData = useMemo(() => wecsData, [wecsData]); - // Memoize node and edge rendering to prevent unnecessary re-renders + // Memoize node rendering to prevent unnecessary re-renders const memoizedNodes = useMemo(() => { if (nodes.length === 0) return []; return nodes; }, [nodes]); - const memoizedEdges = useMemo(() => { - if (edges.length === 0) return []; - return edges; - }, [edges]); - useEffect(() => { if (memoizedWecsData !== null && !isEqual(memoizedWecsData, prevWecsData.current)) { const processData = async () => { @@ -1629,7 +1637,7 @@ const WecsTreeview = () => { From ed4f151fa171baf835823dfc4dc6b68676a6a178 Mon Sep 17 00:00:00 2001 From: kunal-511 Date: Sat, 6 Dec 2025 17:50:36 +0530 Subject: [PATCH 4/6] fix formatting Signed-off-by: kunal-511 --- frontend/src/components/WecsTopology.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/frontend/src/components/WecsTopology.tsx b/frontend/src/components/WecsTopology.tsx index ac7951e6a..642a60021 100644 --- a/frontend/src/components/WecsTopology.tsx +++ b/frontend/src/components/WecsTopology.tsx @@ -44,7 +44,6 @@ import { isEqual } from 'lodash'; import { useTranslation } from 'react-i18next'; import { useWebSocket } from '../context/webSocketExports'; import useTheme from '../stores/themeStore'; -import useZoomStore from '../stores/zoomStore'; import WecsDetailsPanel from './wecs_details/WecsDetailsPanel'; import { FlowCanvas } from './wds_topology/FlowCanvas'; import ListViewComponent from '../components/ListViewComponent'; @@ -535,7 +534,6 @@ const getLayoutedElements = ( const WecsTreeview = () => { const { t } = useTranslation(); const theme = useTheme(state => state.theme); - const { currentZoom, getScaledNodeStyle } = useZoomStore(); const { edgeType } = useEdgeTypeStore(); const [nodes, setNodes] = useState([]); const [edges, setEdges] = useState([]); From beeb49e5dd0b8dbfa0985e59f74bd77d68398de9 Mon Sep 17 00:00:00 2001 From: kunal-511 Date: Sat, 6 Dec 2025 18:00:25 +0530 Subject: [PATCH 5/6] update perfomance and timming issues Signed-off-by: kunal-511 --- frontend/src/components/WecsTopology.tsx | 730 +++++++++--------- .../components/wds_topology/FlowCanvas.tsx | 11 - 2 files changed, 363 insertions(+), 378 deletions(-) diff --git a/frontend/src/components/WecsTopology.tsx b/frontend/src/components/WecsTopology.tsx index 642a60021..c0c34da06 100644 --- a/frontend/src/components/WecsTopology.tsx +++ b/frontend/src/components/WecsTopology.tsx @@ -280,7 +280,7 @@ const getLayoutedElements = ( // recalculate only if node count changes significantly or if this is first render const shouldRecalculate = - prevNodes.current.length === 0 || Math.abs(nodes.length - prevNodes.current.length) > 0; + prevNodes.current.length === 0 || Math.abs(nodes.length - prevNodes.current.length) > 5; if (!shouldRecalculate) { prevNodes.current.forEach(node => nodeMap.set(node.id, node)); @@ -288,7 +288,7 @@ const getLayoutedElements = ( nodes.forEach(node => { const cachedNode = nodeMap.get(node.id); - if (!cachedNode || shouldRecalculate) { + if (!cachedNode || !isEqual(cachedNode, node) || shouldRecalculate) { dagreGraph.setNode(node.id, { width: NODE_WIDTH, height: NODE_HEIGHT }); newNodes.push(node); } else { @@ -872,232 +872,205 @@ const WecsTreeview = () => { return; } - requestAnimationFrame(async () => { - const clusterTimestampMap = await fetchAllClusterTimestamps(data); - - // Clear node cache to ensure fresh nodes with updated styles - nodeCache.current.clear(); - edgeIdCounter.current = 0; - - const newNodes: CustomNode[] = []; - const newEdges: CustomEdge[] = []; - - if (!stateRef.current.isExpanded) { - data.forEach(cluster => { - const clusterId = `cluster:${cluster.cluster}`; - const timestamp = clusterTimestampMap.get(cluster.cluster) || ''; - - createNode( - clusterId, - cluster.cluster, - 'cluster', - 'Active', - timestamp, - undefined, - { - apiVersion: 'v1', - kind: 'Cluster', - metadata: { name: cluster.cluster, namespace: '', creationTimestamp: timestamp }, - status: { phase: 'Active' }, - }, - null, - newNodes, - newEdges - ); - }); - } else { - data.forEach(cluster => { - const clusterId = `cluster:${cluster.cluster}`; - const timestamp = clusterTimestampMap.get(cluster.cluster) || ''; - - createNode( - clusterId, - cluster.cluster, - 'cluster', - 'Active', - timestamp, - undefined, - { - apiVersion: 'v1', - kind: 'Cluster', - metadata: { name: cluster.cluster, namespace: '', creationTimestamp: timestamp }, - status: { phase: 'Active' }, - }, - null, - newNodes, - newEdges - ); - - if (cluster.namespaces && Array.isArray(cluster.namespaces)) { - cluster.namespaces.forEach(namespace => { - const namespaceId = `ns:${cluster.cluster}:${namespace.namespace}`; - createNode( - namespaceId, - namespace.namespace, - 'namespace', - 'Active', - '', - namespace.namespace, - { - apiVersion: 'v1', - kind: 'Namespace', - metadata: { - name: namespace.namespace, - namespace: namespace.namespace, - creationTimestamp: '', - }, - status: { phase: 'Active' }, + const clusterTimestampMap = await fetchAllClusterTimestamps(data); + + // Clear node cache to ensure fresh nodes with updated styles + nodeCache.current.clear(); + edgeIdCounter.current = 0; + + const newNodes: CustomNode[] = []; + const newEdges: CustomEdge[] = []; + + if (!stateRef.current.isExpanded) { + data.forEach(cluster => { + const clusterId = `cluster:${cluster.cluster}`; + const timestamp = clusterTimestampMap.get(cluster.cluster) || ''; + + createNode( + clusterId, + cluster.cluster, + 'cluster', + 'Active', + timestamp, + undefined, + { + apiVersion: 'v1', + kind: 'Cluster', + metadata: { name: cluster.cluster, namespace: '', creationTimestamp: timestamp }, + status: { phase: 'Active' }, + }, + null, + newNodes, + newEdges + ); + }); + } else { + data.forEach(cluster => { + const clusterId = `cluster:${cluster.cluster}`; + const timestamp = clusterTimestampMap.get(cluster.cluster) || ''; + + createNode( + clusterId, + cluster.cluster, + 'cluster', + 'Active', + timestamp, + undefined, + { + apiVersion: 'v1', + kind: 'Cluster', + metadata: { name: cluster.cluster, namespace: '', creationTimestamp: timestamp }, + status: { phase: 'Active' }, + }, + null, + newNodes, + newEdges + ); + + if (cluster.namespaces && Array.isArray(cluster.namespaces)) { + cluster.namespaces.forEach(namespace => { + const namespaceId = `ns:${cluster.cluster}:${namespace.namespace}`; + createNode( + namespaceId, + namespace.namespace, + 'namespace', + 'Active', + '', + namespace.namespace, + { + apiVersion: 'v1', + kind: 'Namespace', + metadata: { + name: namespace.namespace, + namespace: namespace.namespace, + creationTimestamp: '', }, - clusterId, - newNodes, - newEdges - ); + status: { phase: 'Active' }, + }, + clusterId, + newNodes, + newEdges + ); + + if (namespace.resourceTypes && Array.isArray(namespace.resourceTypes)) { + if (stateRef.current.isCollapsed) { + const resourceGroups: Record = {}; - if (namespace.resourceTypes && Array.isArray(namespace.resourceTypes)) { - if (stateRef.current.isCollapsed) { - const resourceGroups: Record = {}; + namespace.resourceTypes.forEach(resourceType => { + // Skip Event type resources + if (resourceType.kind.toLowerCase() === 'event') return; - namespace.resourceTypes.forEach(resourceType => { - // Skip Event type resources - if (resourceType.kind.toLowerCase() === 'event') return; + resourceType.resources.forEach(resource => { + const kindLower = resourceType.kind.toLowerCase(); + if (!resourceGroups[kindLower]) { + resourceGroups[kindLower] = []; + } + resourceGroups[kindLower].push(resource.raw); + }); + }); + + Object.entries(resourceGroups).forEach(([kindLower, items]) => { + const count = items.length; + const groupId = `ns:${cluster.cluster}:${namespace.namespace}:${kindLower}:group`; + const status = items.some(item => item.status?.phase === 'Running') + ? 'Active' + : 'Inactive'; + const label = `${count} ${kindLower}${count !== 1 ? 's' : ''}`; + + createNode( + groupId, + label, + kindLower, + status, + items[0]?.metadata.creationTimestamp, + namespace.namespace, + items[0], + namespaceId, + newNodes, + newEdges + ); + }); + } else { + // Process all resource types for the expanded view + // First collect all ReplicaSet names that are children of deployments + const childReplicaSets = new Set(); + namespace.resourceTypes.forEach(resourceType => { + if (resourceType.kind.toLowerCase() === 'deployment') { resourceType.resources.forEach(resource => { - const kindLower = resourceType.kind.toLowerCase(); - if (!resourceGroups[kindLower]) { - resourceGroups[kindLower] = []; + if ( + resource && + resource.replicaSets && + Array.isArray(resource.replicaSets) + ) { + resource.replicaSets.forEach(rs => { + if (rs && rs.name) { + childReplicaSets.add(rs.name); + } + }); } - resourceGroups[kindLower].push(resource.raw); }); - }); + } + }); - Object.entries(resourceGroups).forEach(([kindLower, items]) => { - const count = items.length; - const groupId = `ns:${cluster.cluster}:${namespace.namespace}:${kindLower}:group`; - const status = items.some(item => item.status?.phase === 'Running') - ? 'Active' - : 'Inactive'; - const label = `${count} ${kindLower}${count !== 1 ? 's' : ''}`; + // Now process all resources while filtering ReplicaSets that are children + namespace.resourceTypes.forEach(resourceType => { + // Skip Event type resources + if (resourceType.kind.toLowerCase() === 'event') return; + + const kindLower = resourceType.kind.toLowerCase(); + + resourceType.resources.forEach((resource, index) => { + if (!resource || typeof resource !== 'object' || !resource.raw) return; + const rawResource = resource.raw; + if ( + !rawResource.metadata || + typeof rawResource.metadata !== 'object' || + !rawResource.metadata.name + ) + return; + + // Skip ReplicaSets that are already children of Deployments + if ( + kindLower === 'replicaset' && + childReplicaSets.has(rawResource.metadata.name) + ) { + return; + } + const resourceId = `${kindLower}:${cluster.cluster}:${namespace.namespace}:${rawResource.metadata.name}:${index}`; + const status = rawResource.status?.phase || 'Active'; createNode( - groupId, - label, + resourceId, + rawResource.metadata.name, kindLower, status, - items[0]?.metadata.creationTimestamp, + rawResource.metadata.creationTimestamp, namespace.namespace, - items[0], + rawResource, namespaceId, newNodes, newEdges ); - }); - } else { - // Process all resource types for the expanded view - // First collect all ReplicaSet names that are children of deployments - const childReplicaSets = new Set(); - - namespace.resourceTypes.forEach(resourceType => { - if (resourceType.kind.toLowerCase() === 'deployment') { - resourceType.resources.forEach(resource => { - if ( - resource && - resource.replicaSets && - Array.isArray(resource.replicaSets) - ) { - resource.replicaSets.forEach(rs => { - if (rs && rs.name) { - childReplicaSets.add(rs.name); - } - }); - } - }); - } - }); - - // Now process all resources while filtering ReplicaSets that are children - namespace.resourceTypes.forEach(resourceType => { - // Skip Event type resources - if (resourceType.kind.toLowerCase() === 'event') return; - - const kindLower = resourceType.kind.toLowerCase(); - resourceType.resources.forEach((resource, index) => { - if (!resource || typeof resource !== 'object' || !resource.raw) return; - const rawResource = resource.raw; - if ( - !rawResource.metadata || - typeof rawResource.metadata !== 'object' || - !rawResource.metadata.name - ) - return; - - // Skip ReplicaSets that are already children of Deployments - if ( - kindLower === 'replicaset' && - childReplicaSets.has(rawResource.metadata.name) - ) { - return; - } - - const resourceId = `${kindLower}:${cluster.cluster}:${namespace.namespace}:${rawResource.metadata.name}:${index}`; - const status = rawResource.status?.phase || 'Active'; - createNode( - resourceId, - rawResource.metadata.name, - kindLower, - status, - rawResource.metadata.creationTimestamp, - namespace.namespace, - rawResource, - namespaceId, - newNodes, - newEdges - ); - - if (kindLower === 'deployment' && rawResource.spec) { - if (resource.replicaSets && Array.isArray(resource.replicaSets)) { - resource.replicaSets.forEach((rs, rsIndex) => { - const replicaSetId = `replicaset:${cluster.cluster}:${namespace.namespace}:${rs.name}:${rsIndex}`; - createNode( - replicaSetId, - rs.name, - 'replicaset', - rs.raw.status?.phase || status, - rs.raw.metadata.creationTimestamp, - namespace.namespace, - rs.raw, - resourceId, - newNodes, - newEdges - ); - if (rs.pods && Array.isArray(rs.pods)) { - rs.pods.forEach((pod, podIndex) => { - const podId = `pod:${cluster.cluster}:${namespace.namespace}:${pod.name}:${podIndex}`; - createNode( - podId, - pod.name, - 'pod', - pod.raw.status?.phase || status, - pod.raw.metadata.creationTimestamp, - namespace.namespace, - pod.raw, - replicaSetId, - newNodes, - newEdges - ); - }); - } - }); - } - } else if (kindLower === 'replicaset' && rawResource.spec) { - if ( - resource.replicaSets && - Array.isArray(resource.replicaSets) && - resource.replicaSets.length > 0 - ) { - const pods = resource.replicaSets[0].pods; - if (pods && Array.isArray(pods)) { - pods.forEach((pod, podIndex) => { + if (kindLower === 'deployment' && rawResource.spec) { + if (resource.replicaSets && Array.isArray(resource.replicaSets)) { + resource.replicaSets.forEach((rs, rsIndex) => { + const replicaSetId = `replicaset:${cluster.cluster}:${namespace.namespace}:${rs.name}:${rsIndex}`; + createNode( + replicaSetId, + rs.name, + 'replicaset', + rs.raw.status?.phase || status, + rs.raw.metadata.creationTimestamp, + namespace.namespace, + rs.raw, + resourceId, + newNodes, + newEdges + ); + if (rs.pods && Array.isArray(rs.pods)) { + rs.pods.forEach((pod, podIndex) => { const podId = `pod:${cluster.cluster}:${namespace.namespace}:${pod.name}:${podIndex}`; createNode( podId, @@ -1107,77 +1080,23 @@ const WecsTreeview = () => { pod.raw.metadata.creationTimestamp, namespace.namespace, pod.raw, - resourceId, + replicaSetId, newNodes, newEdges ); }); } - } - } else if (kindLower === 'statefulset' && rawResource.spec) { - // Display actual pods from the data - if (resource.pods && Array.isArray(resource.pods)) { - resource.pods.forEach((pod, podIndex) => { - const podId = `pod:${cluster.cluster}:${namespace.namespace}:${pod.name}:${podIndex}`; - createNode( - podId, - pod.name, - 'pod', - pod.raw.status?.phase || status, - pod.raw.metadata.creationTimestamp, - namespace.namespace, - pod.raw, - resourceId, - newNodes, - newEdges - ); - }); - } - } else if (kindLower === 'daemonset' && rawResource.spec) { - // Display actual pods from the data - if (resource.pods && Array.isArray(resource.pods)) { - resource.pods.forEach((pod, podIndex) => { - const podId = `pod:${cluster.cluster}:${namespace.namespace}:${pod.name}:${podIndex}`; - createNode( - podId, - pod.name, - 'pod', - pod.raw.status?.phase || status, - pod.raw.metadata.creationTimestamp, - namespace.namespace, - pod.raw, - resourceId, - newNodes, - newEdges - ); - }); - } - } else if (kindLower === 'replicationcontroller' && rawResource.spec) { - // Display actual pods from the data - if (resource.pods && Array.isArray(resource.pods)) { - resource.pods.forEach((pod, podIndex) => { - const podId = `pod:${cluster.cluster}:${namespace.namespace}:${pod.name}:${podIndex}`; - createNode( - podId, - pod.name, - 'pod', - pod.raw.status?.phase || status, - pod.raw.metadata.creationTimestamp, - namespace.namespace, - pod.raw, - resourceId, - newNodes, - newEdges - ); - }); - } - } else if (kindLower === 'cronjob' && rawResource.spec) { - // Display actual jobs and pods from the data if they exist - // No hardcoding - } else if (kindLower === 'job' && rawResource.spec) { - // Display actual pods from the data - if (resource.pods && Array.isArray(resource.pods)) { - resource.pods.forEach((pod, podIndex) => { + }); + } + } else if (kindLower === 'replicaset' && rawResource.spec) { + if ( + resource.replicaSets && + Array.isArray(resource.replicaSets) && + resource.replicaSets.length > 0 + ) { + const pods = resource.replicaSets[0].pods; + if (pods && Array.isArray(pods)) { + pods.forEach((pod, podIndex) => { const podId = `pod:${cluster.cluster}:${namespace.namespace}:${pod.name}:${podIndex}`; createNode( podId, @@ -1193,102 +1112,179 @@ const WecsTreeview = () => { ); }); } - } else if (kindLower === 'service' && rawResource.spec) { - // Create Endpoints node for the Service - createNode( - `${resourceId}:endpoints`, - `endpoints-${rawResource.metadata.name}`, - 'endpoints', - status, - undefined, - namespace.namespace, - rawResource, - resourceId, - newNodes, - newEdges - ); - } else if (kindLower === 'ingress' && rawResource.spec) { - // Only show the Ingress without creating services automatically - // Services will be shown if they exist in the actual data - } else if (kindLower === 'configmap') { - // Create Volume nodes for ConfigMap - createNode( - `${resourceId}:volume`, - `volume-${rawResource.metadata.name}`, - 'volume', - status || 'Unknown', - undefined, - namespace.namespace, - rawResource, - resourceId, - newNodes, - newEdges - ); - } else if (kindLower === 'secret') { - createNode( - `${resourceId}:envvar`, - `envvar-${rawResource.metadata.name}`, - 'envvar', - status, - undefined, - namespace.namespace, - rawResource, - resourceId, - newNodes, - newEdges - ); - } else if (kindLower === 'persistentvolumeclaim' && rawResource.spec) { - // Only show the PVC without creating a PV automatically - } else if (kindLower === 'storageclass' && rawResource.spec) { - // Only show the StorageClass without creating a PV automatically - } else if (kindLower === 'horizontalpodautoscaler' && rawResource.spec) { - // Only show the HPA without creating target resources automatically - // Target resources will be shown if they exist in the actual data - } else if (kindLower === 'rolebinding' && rawResource.roleRef) { - // Only show the RoleBinding without creating roles automatically - // Roles will be shown if they exist in the actual data - } else if (kindLower === 'clusterrolebinding' && rawResource.roleRef) { - // Only show the ClusterRoleBinding without creating cluster roles automatically - // ClusterRoles will be shown if they exist in the actual data - } else if (kindLower === 'poddisruptionbudget' && rawResource.spec) { - // Intentionally empty - } else if (kindLower === 'networkpolicy' && rawResource.spec) { - // Intentionally empty - } else if ( - kindLower === 'ingressclass' || - kindLower === 'mutatingwebhookconfiguration' || - kindLower === 'validatingwebhookconfiguration' - ) { - // Intentionally empty } - }); + } else if (kindLower === 'statefulset' && rawResource.spec) { + // Display actual pods from the data + if (resource.pods && Array.isArray(resource.pods)) { + resource.pods.forEach((pod, podIndex) => { + const podId = `pod:${cluster.cluster}:${namespace.namespace}:${pod.name}:${podIndex}`; + createNode( + podId, + pod.name, + 'pod', + pod.raw.status?.phase || status, + pod.raw.metadata.creationTimestamp, + namespace.namespace, + pod.raw, + resourceId, + newNodes, + newEdges + ); + }); + } + } else if (kindLower === 'daemonset' && rawResource.spec) { + // Display actual pods from the data + if (resource.pods && Array.isArray(resource.pods)) { + resource.pods.forEach((pod, podIndex) => { + const podId = `pod:${cluster.cluster}:${namespace.namespace}:${pod.name}:${podIndex}`; + createNode( + podId, + pod.name, + 'pod', + pod.raw.status?.phase || status, + pod.raw.metadata.creationTimestamp, + namespace.namespace, + pod.raw, + resourceId, + newNodes, + newEdges + ); + }); + } + } else if (kindLower === 'replicationcontroller' && rawResource.spec) { + // Display actual pods from the data + if (resource.pods && Array.isArray(resource.pods)) { + resource.pods.forEach((pod, podIndex) => { + const podId = `pod:${cluster.cluster}:${namespace.namespace}:${pod.name}:${podIndex}`; + createNode( + podId, + pod.name, + 'pod', + pod.raw.status?.phase || status, + pod.raw.metadata.creationTimestamp, + namespace.namespace, + pod.raw, + resourceId, + newNodes, + newEdges + ); + }); + } + } else if (kindLower === 'cronjob' && rawResource.spec) { + // Display actual jobs and pods from the data if they exist + // No hardcoding + } else if (kindLower === 'job' && rawResource.spec) { + // Display actual pods from the data + if (resource.pods && Array.isArray(resource.pods)) { + resource.pods.forEach((pod, podIndex) => { + const podId = `pod:${cluster.cluster}:${namespace.namespace}:${pod.name}:${podIndex}`; + createNode( + podId, + pod.name, + 'pod', + pod.raw.status?.phase || status, + pod.raw.metadata.creationTimestamp, + namespace.namespace, + pod.raw, + resourceId, + newNodes, + newEdges + ); + }); + } + } else if (kindLower === 'service' && rawResource.spec) { + // Create Endpoints node for the Service + createNode( + `${resourceId}:endpoints`, + `endpoints-${rawResource.metadata.name}`, + 'endpoints', + status, + undefined, + namespace.namespace, + rawResource, + resourceId, + newNodes, + newEdges + ); + } else if (kindLower === 'ingress' && rawResource.spec) { + // Only show the Ingress without creating services automatically + // Services will be shown if they exist in the actual data + } else if (kindLower === 'configmap') { + // Create Volume nodes for ConfigMap + createNode( + `${resourceId}:volume`, + `volume-${rawResource.metadata.name}`, + 'volume', + status || 'Unknown', + undefined, + namespace.namespace, + rawResource, + resourceId, + newNodes, + newEdges + ); + } else if (kindLower === 'secret') { + createNode( + `${resourceId}:envvar`, + `envvar-${rawResource.metadata.name}`, + 'envvar', + status, + undefined, + namespace.namespace, + rawResource, + resourceId, + newNodes, + newEdges + ); + } else if (kindLower === 'persistentvolumeclaim' && rawResource.spec) { + // Only show the PVC without creating a PV automatically + } else if (kindLower === 'storageclass' && rawResource.spec) { + // Only show the StorageClass without creating a PV automatically + } else if (kindLower === 'horizontalpodautoscaler' && rawResource.spec) { + // Only show the HPA without creating target resources automatically + // Target resources will be shown if they exist in the actual data + } else if (kindLower === 'rolebinding' && rawResource.roleRef) { + // Only show the RoleBinding without creating roles automatically + // Roles will be shown if they exist in the actual data + } else if (kindLower === 'clusterrolebinding' && rawResource.roleRef) { + // Only show the ClusterRoleBinding without creating cluster roles automatically + // ClusterRoles will be shown if they exist in the actual data + } else if (kindLower === 'poddisruptionbudget' && rawResource.spec) { + // Intentionally empty + } else if (kindLower === 'networkpolicy' && rawResource.spec) { + // Intentionally empty + } else if ( + kindLower === 'ingressclass' || + kindLower === 'mutatingwebhookconfiguration' || + kindLower === 'validatingwebhookconfiguration' + ) { + // Intentionally empty + } }); - } + }); } - }); - } - }); - } + } + }); + } + }); + } - const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements( - newNodes, - newEdges, - 'LR', - prevNodes - ); - - if (!isEqual(nodes, layoutedNodes)) { - setNodes(layoutedNodes); - requestAnimationFrame(() => { - if (!isEqual(edges, layoutedEdges)) setEdges(layoutedEdges); - }); - } else if (!isEqual(edges, layoutedEdges)) { - setEdges(layoutedEdges); - } + const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements( + newNodes, + newEdges, + 'LR', + prevNodes + ); - prevNodes.current = layoutedNodes; - setIsTransforming(false); - }); + if (!isEqual(nodes, layoutedNodes)) { + setNodes(layoutedNodes); + setEdges(layoutedEdges); + } else if (!isEqual(edges, layoutedEdges)) { + setEdges(layoutedEdges); + } + + prevNodes.current = layoutedNodes; + setIsTransforming(false); }, [createNode, fetchAllClusterTimestamps] ); diff --git a/frontend/src/components/wds_topology/FlowCanvas.tsx b/frontend/src/components/wds_topology/FlowCanvas.tsx index 7b689dc85..e96722e63 100644 --- a/frontend/src/components/wds_topology/FlowCanvas.tsx +++ b/frontend/src/components/wds_topology/FlowCanvas.tsx @@ -325,17 +325,6 @@ export const FlowCanvas = memo(({ nodes, edges, theme }) => { /* Keep edges visible and interactive */ .react-flow__edge { pointer-events: all !important; - opacity: 0; - animation: fadeInEdge 0.3s ease-in forwards; - } - - @keyframes fadeInEdge { - from { - opacity: 0; - } - to { - opacity: 1; - } } /* Ensure edge paths remain visible */ From 288b27c3733eddfaf70b26e2f8d9d51a72de36f5 Mon Sep 17 00:00:00 2001 From: kunal-511 Date: Sun, 7 Dec 2025 15:00:39 +0530 Subject: [PATCH 6/6] Improve node spacing Signed-off-by: kunal-511 --- frontend/src/components/WecsTopology.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/WecsTopology.tsx b/frontend/src/components/WecsTopology.tsx index c0c34da06..fae4e39e1 100644 --- a/frontend/src/components/WecsTopology.tsx +++ b/frontend/src/components/WecsTopology.tsx @@ -262,8 +262,8 @@ const getLayoutedElements = ( // Use fixed layout values - let ReactFlow handle zoom visually const NODE_WIDTH = 146; const NODE_HEIGHT = 30; - const NODE_SEP = 40; - const RANK_SEP = 100; + const NODE_SEP = 60; + const RANK_SEP = 150; const CHILD_SPACING = NODE_HEIGHT + 30; if (nodes.length === 0) {