From 998433c1aaacabcc7834954aab2cddece550835c Mon Sep 17 00:00:00 2001 From: Noble Mittal <62551163+beingnoble03@users.noreply.github.com> Date: Mon, 16 Dec 2024 14:01:42 +0530 Subject: [PATCH] VTAdmin(web): Add simpler topology tree structure (#17245) Signed-off-by: Noble Mittal --- web/vtadmin/src/components/App.tsx | 5 + .../components/routes/topology/Topology.tsx | 11 +- .../components/routes/topologyTree/Node.tsx | 74 +++++++++++++ .../routes/topologyTree/TopologyTree.tsx | 103 ++++++++++++++++++ web/vtadmin/src/hooks/api.ts | 2 +- 5 files changed, 193 insertions(+), 2 deletions(-) create mode 100644 web/vtadmin/src/components/routes/topologyTree/Node.tsx create mode 100644 web/vtadmin/src/components/routes/topologyTree/TopologyTree.tsx diff --git a/web/vtadmin/src/components/App.tsx b/web/vtadmin/src/components/App.tsx index 3bb41ea35f0..fd0f772ae19 100644 --- a/web/vtadmin/src/components/App.tsx +++ b/web/vtadmin/src/components/App.tsx @@ -45,6 +45,7 @@ import { Transactions } from './routes/Transactions'; import { Transaction } from './routes/transaction/Transaction'; import { CreateReshard } from './routes/createWorkflow/CreateReshard'; import { CreateMaterialize } from './routes/createWorkflow/CreateMaterialize'; +import { TopologyTree } from './routes/topologyTree/TopologyTree'; import { SchemaMigrations } from './routes/SchemaMigrations'; import { CreateSchemaMigration } from './routes/createSchemaMigration/CreateSchemaMigration'; @@ -164,6 +165,10 @@ export const App = () => { + + + + diff --git a/web/vtadmin/src/components/routes/topology/Topology.tsx b/web/vtadmin/src/components/routes/topology/Topology.tsx index b4a65c3e8df..bd08dcfd1b1 100644 --- a/web/vtadmin/src/components/routes/topology/Topology.tsx +++ b/web/vtadmin/src/components/routes/topology/Topology.tsx @@ -53,6 +53,11 @@ export const Topology = () => { View Topology + + + View Topology Tree + + )); @@ -65,7 +70,11 @@ export const Topology = () => {
Clusters
- +
diff --git a/web/vtadmin/src/components/routes/topologyTree/Node.tsx b/web/vtadmin/src/components/routes/topologyTree/Node.tsx new file mode 100644 index 00000000000..4bb70ddbe0e --- /dev/null +++ b/web/vtadmin/src/components/routes/topologyTree/Node.tsx @@ -0,0 +1,74 @@ +/** + * Copyright 2024 The Vitess Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React, { useEffect, useState } from 'react'; + +import { useTopologyPath } from '../../../hooks/api'; +import { buildChildNodes, TopologyNode } from './TopologyTree'; + +interface NodeParams { + topologyNode: TopologyNode; + clusterID: string; +} + +export const Node = ({ topologyNode, clusterID }: NodeParams) => { + const [isOpen, setIsOpen] = useState(false); + const [node, setNode] = useState(topologyNode); + + const childrenPath = `${topologyNode.path}/${topologyNode.name}`; + const { data } = useTopologyPath( + { clusterID, path: childrenPath }, + { + enabled: isOpen, + } + ); + + useEffect(() => { + if (data) { + setNode((prevNode) => ({ + ...prevNode, + children: buildChildNodes(data.cell, childrenPath), + data: data.cell?.data, + })); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [data]); + + const nodeTitle = `${isOpen ? '▼' : '►'} ${node.name}`; + return ( +
+
setIsOpen(!isOpen)}> + {nodeTitle} +
+ + {isOpen && ( +
+ {node.data ? ( +
+ {node.data} +
+ ) : ( + <> + {node.children && + node.children.map((child, idx) => { + return ; + })} + + )} +
+ )} +
+ ); +}; diff --git a/web/vtadmin/src/components/routes/topologyTree/TopologyTree.tsx b/web/vtadmin/src/components/routes/topologyTree/TopologyTree.tsx new file mode 100644 index 00000000000..cb27876391e --- /dev/null +++ b/web/vtadmin/src/components/routes/topologyTree/TopologyTree.tsx @@ -0,0 +1,103 @@ +/** + * Copyright 2024 The Vitess Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React, { useEffect, useState } from 'react'; + +import { useTopologyPath } from '../../../hooks/api'; +import { useDocumentTitle } from '../../../hooks/useDocumentTitle'; +import { ContentContainer } from '../../layout/ContentContainer'; +import { NavCrumbs } from '../../layout/NavCrumbs'; +import { WorkspaceHeader } from '../../layout/WorkspaceHeader'; +import { WorkspaceTitle } from '../../layout/WorkspaceTitle'; +import { Link, useParams } from 'react-router-dom'; + +import { Node } from './Node'; +import { vtctldata } from '../../../proto/vtadmin'; + +export interface TopologyNode { + name?: string | null; + data?: string | null; + path: string; + children?: TopologyNode[]; +} + +export const buildChildNodes = (cell: vtctldata.ITopologyCell | null | undefined, path: string) => { + if (cell) { + const childNodes: TopologyNode[] | undefined = cell.children + ? cell.children.map((child) => { + return { + name: child, + path, + }; + }) + : undefined; + return childNodes; + } +}; + +export const TopologyTree = () => { + interface RouteParams { + clusterID: string; + } + useDocumentTitle('Cluster Topolgy'); + const { clusterID } = useParams(); + const { data } = useTopologyPath({ clusterID, path: '/' }); + const [topologyNode, setTopologyNode] = useState(); + + useEffect(() => { + if (data?.cell) { + const topologyNode: TopologyNode = { + path: data.cell.path || '/', + data: data.cell.data, + children: buildChildNodes(data.cell, ''), + }; + setTopologyNode(topologyNode); + } + }, [data]); + + if (!data) { + return ( +
+ + + Topology + + + {clusterID} + + + 404 +
+ ); + } + + return ( +
+ + + Topology + + {clusterID} + + + + {topologyNode && + topologyNode.children?.map((child, idx) => ( + + ))} + +
+ ); +}; diff --git a/web/vtadmin/src/hooks/api.ts b/web/vtadmin/src/hooks/api.ts index 18ab3b60a53..c2e1209f128 100644 --- a/web/vtadmin/src/hooks/api.ts +++ b/web/vtadmin/src/hooks/api.ts @@ -729,7 +729,7 @@ export const useTopologyPath = ( params: GetTopologyPathParams, options?: UseQueryOptions | undefined ) => { - return useQuery(['topology-path', params], () => getTopologyPath(params)); + return useQuery(['topology-path', params], () => getTopologyPath(params), options); }; /** * useValidate is a mutate hook that validates that all nodes reachable from the global replication graph,