vitess.io/vitess@v0.16.2/web/vtadmin/src/components/routes/topology/ClusterTopology.tsx (about) 1 /** 2 * Copyright 2022 The Vitess Authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 import React, { useEffect, useState } from 'react'; 17 18 import { useTopologyPath } from '../../../hooks/api'; 19 import { useDocumentTitle } from '../../../hooks/useDocumentTitle'; 20 import { ContentContainer } from '../../layout/ContentContainer'; 21 import { NavCrumbs } from '../../layout/NavCrumbs'; 22 import { WorkspaceHeader } from '../../layout/WorkspaceHeader'; 23 import { WorkspaceTitle } from '../../layout/WorkspaceTitle'; 24 import { Link, useParams } from 'react-router-dom'; 25 import { generateGraph, TopologyCell, TopologyCellChild } from './Nodes'; 26 27 import ReactFlow, { 28 addEdge, 29 MiniMap, 30 Controls, 31 Background, 32 useNodesState, 33 useEdgesState, 34 Connection, 35 } from 'react-flow-renderer'; 36 import { getTopologyPath } from '../../../api/http'; 37 38 export const ClusterTopology = () => { 39 interface RouteParams { 40 clusterID: string; 41 } 42 useDocumentTitle('Cluster Topolgy'); 43 const { clusterID } = useParams<RouteParams>(); 44 const { data } = useTopologyPath({ clusterID, path: '/' }); 45 const [topology, setTopology] = useState<{ cell: TopologyCell }>({ cell: data?.cell as TopologyCell }); 46 47 const [nodes, setNodes, onNodesChange] = useNodesState([]); 48 const [edges, setEdges, onEdgesChange] = useEdgesState([]); 49 50 const onConnect = (params: Connection) => setEdges((eds) => addEdge(params, eds)); 51 const onExpand = async (path: string) => { 52 const { cell } = await getTopologyPath({ clusterID, path }); 53 const newTopo = { ...topology }; 54 newTopo.cell.children = placeCell(newTopo.cell, cell as TopologyCell); 55 setTopology(newTopo); 56 }; 57 58 const placeCell = (currentCell: TopologyCell, newCell: TopologyCell): TopologyCellChild[] => { 59 const newChildren: TopologyCellChild[] = []; 60 currentCell.children?.forEach((c) => { 61 if (typeof c === 'string' && c === newCell?.name) { 62 newChildren.push(newCell as TopologyCell); 63 } 64 if (typeof c == 'string' && c !== newCell?.name) { 65 newChildren.push(c); 66 } 67 if (typeof c !== 'string') { 68 c.children = placeCell(c, newCell); 69 newChildren.push(c); 70 } 71 }); 72 return newChildren; 73 }; 74 75 useEffect(() => { 76 const { nodes: initialNodes, edges: initialEdges } = topology 77 ? generateGraph(topology, onExpand) 78 : { nodes: [], edges: [] }; 79 setNodes(initialNodes); 80 setEdges(initialEdges); 81 // eslint-disable-next-line react-hooks/exhaustive-deps 82 }, [topology]); 83 84 useEffect(() => { 85 if (data?.cell) { 86 setTopology({ cell: data?.cell as TopologyCell }); 87 } 88 // eslint-disable-next-line react-hooks/exhaustive-deps 89 }, [data]); 90 91 if (!data) { 92 return ( 93 <div> 94 <WorkspaceHeader> 95 <NavCrumbs> 96 <Link to="/topology">Topology</Link> 97 </NavCrumbs> 98 99 <WorkspaceTitle className="font-mono">{clusterID}</WorkspaceTitle> 100 </WorkspaceHeader> 101 102 <ContentContainer>404</ContentContainer> 103 </div> 104 ); 105 } 106 107 return ( 108 <div> 109 <WorkspaceHeader> 110 <NavCrumbs> 111 <Link to="/topology">Topology</Link> 112 </NavCrumbs> 113 114 <WorkspaceTitle className="font-mono">{clusterID}</WorkspaceTitle> 115 </WorkspaceHeader> 116 117 <ContentContainer className="lg:w-[1400px] lg:h-[1200px] md:w-[900px] md:h-[800px]"> 118 <ReactFlow 119 nodes={nodes} 120 edges={edges} 121 onNodesChange={onNodesChange} 122 onEdgesChange={onEdgesChange} 123 onConnect={onConnect} 124 fitView 125 attributionPosition="top-right" 126 > 127 <MiniMap 128 nodeStrokeColor={(n) => { 129 if (n.style?.background) return n.style.background as string; 130 if (n.type === 'input') return '#0041d0'; 131 if (n.type === 'output') return '#ff0072'; 132 if (n.type === 'default') return '#1a192b'; 133 134 return '#eee'; 135 }} 136 nodeColor={(n) => { 137 if (n.style?.background) return n.style.background as string; 138 139 return '#fff'; 140 }} 141 nodeBorderRadius={2} 142 /> 143 <Controls /> 144 <Background color="#aaa" gap={16} /> 145 </ReactFlow> 146 </ContentContainer> 147 </div> 148 ); 149 };