Browse Source

..

Signed-off-by: min.tian <min.tian.cn@gmail.com>
min.tian 2 years ago
parent
commit
cabce148b1

+ 0 - 0
client/src/pages/systemHealthy/HealthyIndex


+ 165 - 2
client/src/pages/systemHealthy/HealthyIndexDetailView.tsx

@@ -1,5 +1,168 @@
-const HealthyIndexDetailView = () => {
-  return <div></div>;
+import { makeStyles, Theme } from '@material-ui/core';
+import { CHART_WIDTH, LINE_CHART_SMALL_HEIGHT } from './consts';
+import HealthyIndexRow from './HealthyIndexRow';
+import LineChartSmall from './LineChartSmall';
+import { ENodeService, INodeTreeStructure } from './Types';
+import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight';
+import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
+import { useState } from 'react';
+
+const getStyles = makeStyles((theme: Theme) => ({
+  mainView: {
+    width: '100%',
+    marginTop: '16px',
+  },
+  healthyIndexItem: {
+    display: 'flex',
+    marginTop: '8px',
+    justifyContent: 'space-between',
+  },
+  healthyIndexLabel: {
+    // width: `${CHART_LABEL_WIDTH}px`,
+
+    fontWeight: 500,
+    fontSize: '12px',
+    color: '#444',
+    display: 'flex',
+    alignItems: 'center',
+    cursor: 'pointer',
+  },
+  healthyIndexLabelText: {},
+  healthyIndexRow: {
+    width: `${CHART_WIDTH}px`,
+    // border: '1px solid brown',
+  },
+  chartItem: {
+    margin: '8px 0',
+    display: 'flex',
+    justifyContent: 'space-between',
+    alignItems: 'flex-end',
+  },
+  chartLabel: {
+    width: `50px`,
+    paddingLeft: '20px',
+    fontSize: '12px',
+    fontWeight: 500,
+    color: '#444',
+  },
+  chart: {
+    height: `${LINE_CHART_SMALL_HEIGHT}px`,
+    width: `${CHART_WIDTH}px`,
+
+    // border: '1px solid brown',
+  },
+}));
+
+const HealthyIndexTreeItem = ({ node }: { node: INodeTreeStructure }) => {
+  const classes = getStyles();
+  const [open, setOpen] = useState(false);
+  return (
+    <>
+      <div
+        key={`${node.service}-${node.type}-${node.label}`}
+        className={classes.healthyIndexItem}
+      >
+        <div
+          className={classes.healthyIndexLabel}
+          onClick={() => setOpen(!open)}
+        >
+          {open ? (
+            <KeyboardArrowDownIcon fontSize="small" />
+          ) : (
+            <KeyboardArrowRightIcon fontSize="small" />
+          )}
+          <div className={classes.healthyIndexLabelText}>{`${
+            node.type
+          }-${node.label.slice(-5)}`}</div>
+        </div>
+        <div className={classes.healthyIndexRow}>
+          <HealthyIndexRow statusList={node.healthyStatus} />
+        </div>
+      </div>
+      {open && (
+        <>
+          <div className={classes.chartItem}>
+            <div className={classes.chartLabel}>cpu</div>
+            <div className={classes.chart}>
+              <LineChartSmall
+                data={node.cpu || []}
+                format={(v: number) => v.toFixed(3)}
+                unit={'Core'}
+              />
+            </div>
+          </div>
+          <div className={classes.chartItem}>
+            <div className={classes.chartLabel}>memory</div>
+            <div className={classes.chart}>
+              <LineChartSmall
+                data={node.memory || []}
+                format={(v: number) => (v / 1024 / 1024 / 1024).toFixed(1)}
+                unit={'GB'}
+              />
+            </div>
+          </div>
+        </>
+      )}
+    </>
+  );
+};
+
+const HealthyIndexWithTree = ({
+  nodeTree,
+}: {
+  nodeTree: INodeTreeStructure;
+}) => {
+  const classes = getStyles();
+  return (
+    <div className={classes.mainView}>
+      {!!nodeTree && (
+        <div className={classes.healthyIndexItem}>
+          <div className={classes.healthyIndexLabel}>{nodeTree.label}</div>
+          <div className={classes.healthyIndexRow}>
+            <HealthyIndexRow statusList={nodeTree?.healthyStatus || []} />
+          </div>
+        </div>
+      )}
+      {nodeTree.children.map(node => (
+        <HealthyIndexTreeItem key={node.label} node={node} />
+      ))}
+    </div>
+  );
+};
+
+const HealthyIndexWithoutTree = ({
+  nodeTree,
+}: {
+  nodeTree: INodeTreeStructure;
+}) => {
+  const classes = getStyles();
+  return (
+    <div className={classes.mainView}>
+      {nodeTree.children.map(node => (
+        <div
+          key={`${node.service}-${node.type}`}
+          className={classes.healthyIndexItem}
+        >
+          <div className={classes.healthyIndexLabel}>{node.label}</div>
+          <div className={classes.healthyIndexRow}>
+            <HealthyIndexRow statusList={node.healthyStatus} />
+          </div>
+        </div>
+      ))}
+    </div>
+  );
+};
+
+const HealthyIndexDetailView = ({
+  nodeTree,
+}: {
+  nodeTree: INodeTreeStructure;
+}) => {
+  return nodeTree.service === ENodeService.milvus ? (
+    <HealthyIndexWithoutTree nodeTree={nodeTree} />
+  ) : (
+    <HealthyIndexWithTree nodeTree={nodeTree} />
+  );
 };
 };
 
 
 export default HealthyIndexDetailView;
 export default HealthyIndexDetailView;

+ 44 - 51
client/src/pages/systemHealthy/HealthyIndexOverview.tsx

@@ -4,13 +4,21 @@ import {
   CHART_WIDTH,
   CHART_WIDTH,
   LINE_CHART_LARGE_HEIGHT,
   LINE_CHART_LARGE_HEIGHT,
   MAIN_VIEW_WIDTH,
   MAIN_VIEW_WIDTH,
+  TOPO_HEIGHT,
 } from './consts';
 } from './consts';
+import HealthyIndexDetailView from './HealthyIndexDetailView';
 import HealthyIndexLegend from './HealthyIndexLegend';
 import HealthyIndexLegend from './HealthyIndexLegend';
 import HealthyIndexRow from './HealthyIndexRow';
 import HealthyIndexRow from './HealthyIndexRow';
 import LineChartLarge from './LineChartLarge';
 import LineChartLarge from './LineChartLarge';
 import ThresholdSetting from './ThresholdSetting';
 import ThresholdSetting from './ThresholdSetting';
 import TimeRangeTabs from './TimeRangeTabs';
 import TimeRangeTabs from './TimeRangeTabs';
-import { ILineChartData, INodeTreeStructure, IThreshold, ITimeRangeOption } from './Types';
+import {
+  ENodeService,
+  ILineChartData,
+  INodeTreeStructure,
+  IThreshold,
+  ITimeRangeOption,
+} from './Types';
 // import SettingsApplicationsIcon from '@mui/icons-material/SettingsApplications';
 // import SettingsApplicationsIcon from '@mui/icons-material/SettingsApplications';
 
 
 // export const CHART_LABEL_WIDTH = 70;
 // export const CHART_LABEL_WIDTH = 70;
@@ -18,8 +26,9 @@ import { ILineChartData, INodeTreeStructure, IThreshold, ITimeRangeOption } from
 const getStyles = makeStyles((theme: Theme) => ({
 const getStyles = makeStyles((theme: Theme) => ({
   root: {
   root: {
     width: `${MAIN_VIEW_WIDTH}px`,
     width: `${MAIN_VIEW_WIDTH}px`,
-    height: '100%',
-    padding: '16px 24px',
+    height: `${TOPO_HEIGHT}px`,
+    overflow: 'auto',
+    padding: '16px 56px 16px 24px',
     // boxShadow: '0 0 5px #ccc',
     // boxShadow: '0 0 5px #ccc',
     fontSize: '14px',
     fontSize: '14px',
   },
   },
@@ -29,9 +38,11 @@ const getStyles = makeStyles((theme: Theme) => ({
   },
   },
   titleContainer: {},
   titleContainer: {},
   title: {
   title: {
-    fontSize: '18px',
-    fontWeight: 500,
+    display: 'flex',
+    alignItems: 'flex-end',
   },
   },
+  titleMain: { fontSize: '18px', fontWeight: 500 },
+  titleExt: { fontSize: '18px', fontWeight: 500, marginLeft: '8px' },
   timeRangeTabs: {
   timeRangeTabs: {
     fontSize: '12px',
     fontSize: '12px',
   },
   },
@@ -40,25 +51,7 @@ const getStyles = makeStyles((theme: Theme) => ({
     alignItems: 'flex-end',
     alignItems: 'flex-end',
   },
   },
   settingIcon: { marginLeft: '16px', display: 'flex', alignItems: 'flex-end' },
   settingIcon: { marginLeft: '16px', display: 'flex', alignItems: 'flex-end' },
-  mainView: {
-    width: '100%',
-    marginTop: '16px',
-  },
-  healthyIndexItem: {
-    display: 'flex',
-    marginTop: '8px',
-    justifyContent: 'space-between',
-  },
-  healthyIndexLabel: {
-    // width: `${CHART_LABEL_WIDTH}px`,
 
 
-    fontWeight: 500,
-    color: '#444',
-  },
-  healthyIndexRow: {
-    width: `${CHART_WIDTH}px`,
-    // border: '1px solid brown',
-  },
   chartView: { width: '100%', marginTop: '30px' },
   chartView: { width: '100%', marginTop: '30px' },
   chartItem: {
   chartItem: {
     margin: '24px 0',
     margin: '24px 0',
@@ -80,14 +73,14 @@ const getStyles = makeStyles((theme: Theme) => ({
 }));
 }));
 
 
 const HealthyIndexOverview = ({
 const HealthyIndexOverview = ({
-  nodes,
+  selectedNode,
   lineChartsData,
   lineChartsData,
   threshold,
   threshold,
   setThreshold,
   setThreshold,
   timeRange,
   timeRange,
   setTimeRange,
   setTimeRange,
 }: {
 }: {
-  nodes: INodeTreeStructure[];
+  selectedNode: INodeTreeStructure;
   lineChartsData: ILineChartData[];
   lineChartsData: ILineChartData[];
   threshold: IThreshold;
   threshold: IThreshold;
   setThreshold: Dispatch<SetStateAction<IThreshold>>;
   setThreshold: Dispatch<SetStateAction<IThreshold>>;
@@ -99,8 +92,15 @@ const HealthyIndexOverview = ({
     <div className={classes.root}>
     <div className={classes.root}>
       <div className={classes.headerContent}>
       <div className={classes.headerContent}>
         <div className={classes.titleContainer}>
         <div className={classes.titleContainer}>
-          <div className={classes.title}>Healthy Status</div>
-          <div className={classes.timeRangeTabs}> 
+          <div className={classes.title}>
+            <div className={classes.titleMain}>Healthy Status</div>
+            {selectedNode.service !== ENodeService.milvus && (
+              <div className={classes.titleExt}>
+                {`> ${selectedNode.service}`}
+              </div>
+            )}
+          </div>
+          <div className={classes.timeRangeTabs}>
             <TimeRangeTabs timeRange={timeRange} setTimeRange={setTimeRange} />
             <TimeRangeTabs timeRange={timeRange} setTimeRange={setTimeRange} />
           </div>
           </div>
         </div>
         </div>
@@ -114,31 +114,24 @@ const HealthyIndexOverview = ({
           </div>
           </div>
         </div>
         </div>
       </div>
       </div>
-      <div className={classes.mainView}>
-        {nodes.map(node => (
-          <div className={classes.healthyIndexItem}>
-            <div className={classes.healthyIndexLabel}>{node.label}</div>
-            <div className={classes.healthyIndexRow}>
-              <HealthyIndexRow statusList={node.healthyStatus} />
-            </div>
-          </div>
-        ))}
-      </div>
-      <div className={classes.chartView}>
-        <div className={classes.title}>Search Query History</div>
-        {lineChartsData.map(chartData => (
-          <div className={classes.chartItem}>
-            <div className={classes.chartLabel}>{chartData.label}</div>
-            <div className={classes.chart}>
-              <LineChartLarge
-                data={chartData.data}
-                format={chartData.format}
-                unit={chartData.unit}
-              />
+      <HealthyIndexDetailView nodeTree={selectedNode} />
+      {selectedNode.service === ENodeService.milvus && (
+        <div className={classes.chartView}>
+          <div className={classes.title}>Search Query History</div>
+          {lineChartsData.map(chartData => (
+            <div className={classes.chartItem}>
+              <div className={classes.chartLabel}>{chartData.label}</div>
+              <div className={classes.chart}>
+                <LineChartLarge
+                  data={chartData.data}
+                  format={chartData.format}
+                  unit={chartData.unit}
+                />
+              </div>
             </div>
             </div>
-          </div>
-        ))}
-      </div>
+          ))}
+        </div>
+      )}
     </div>
     </div>
   );
   );
 };
 };

+ 92 - 2
client/src/pages/systemHealthy/LineChartSmall.tsx

@@ -1,11 +1,101 @@
+import * as d3 from 'd3';
+import {
+  CHART_WIDTH,
+  LINE_CHART_LARGE_HEIGHT,
+  LINE_CHART_SMALL_HEIGHT,
+  LINE_COLOR,
+  LINE_LABEL_FONT_SIZE,
+  LINE_LABEL_Y_PADDING,
+  LINE_SMALL_LABEL_FONT_SIZE,
+} from './consts';
+
 const LineChartSmall = ({
 const LineChartSmall = ({
   data,
   data,
   format = d => d,
   format = d => d,
+  unit = '',
 }: {
 }: {
   data: number[];
   data: number[];
   format?: (d: any) => string;
   format?: (d: any) => string;
+  unit?: string;
 }) => {
 }) => {
-  return <div></div>;
-};
+  const length = data.length;
+  const width = CHART_WIDTH;
+  const height = LINE_CHART_SMALL_HEIGHT - 3;
+  const fontSize = LINE_SMALL_LABEL_FONT_SIZE;
+
+  const xDomain = [0, length];
+  const xRange = [0, CHART_WIDTH];
+  let maxData = d3.max(data, d => d) as number;
+  maxData = maxData === 0 ? 1 : maxData;
+
+  const yDomain = [0, maxData * 1.1];
+  const yRange = [height, 0];
 
 
+  const xScale = d3.scaleLinear(xDomain, xRange);
+  const yScale = d3.scaleLinear(yDomain, yRange);
+
+  const nodes = data
+    .map((d, i) => (d >= 0 ? [xScale(i + 0.5), yScale(d)] : undefined))
+    .filter(a => a) as [number, number][];
+
+  const line = d3
+    .line()
+    .curve(d3.curveBumpX)
+    .x(d => d[0])
+    .y(d => d[1]);
+
+  return (
+    <svg
+      width={width}
+      height={height}
+      style={{ overflow: 'visible' }}
+      fontSize={fontSize}
+    >
+      <g className="x-axis">
+        <line x1={0} y1={height} x2={width} y2={height} stroke="#666" />
+        <line
+          x1={width - LINE_LABEL_Y_PADDING}
+          y1={yScale(maxData)}
+          x2={width}
+          y2={yScale(maxData)}
+          stroke="#666"
+          strokeWidth="2"
+        />
+      </g>
+      <g className="y-axis">
+        <text x={width + LINE_LABEL_Y_PADDING} y={height} textAnchor="start">
+          {0}
+        </text>
+        <text
+          x={width + LINE_LABEL_Y_PADDING}
+          y={yScale(maxData) + 3}
+          textAnchor="start"
+        >
+          {format(maxData)}
+        </text>
+        {unit && (
+          <text
+            x={width + LINE_LABEL_Y_PADDING}
+            y={yScale(maxData) + 3 + fontSize}
+            textAnchor="start"
+            fill={'#333'}
+            fontSize={fontSize - 2}
+          >
+            ({unit})
+          </text>
+        )}
+      </g>
+      <g className="line">
+        <path
+          d={line(nodes) as any}
+          fill="none"
+          stroke={`${LINE_COLOR}`}
+          strokeWidth={3}
+          opacity={0.8}
+          strokeLinecap="round"
+        />
+      </g>
+    </svg>
+  );
+};
 export default LineChartSmall;
 export default LineChartSmall;

+ 22 - 37
client/src/pages/systemHealthy/SystemHealthyView.tsx

@@ -92,8 +92,13 @@ const SystemHealthyView = () => {
   };
   };
   const [threshold, setThreshold] = useState<IThreshold>(defaultThreshold);
   const [threshold, setThreshold] = useState<IThreshold>(defaultThreshold);
   const [prometheusData, setPrometheusData] = useState<IPrometheusAllData>();
   const [prometheusData, setPrometheusData] = useState<IPrometheusAllData>();
-  const nodes = useMemo<INodeTreeStructure[]>(
-    () => (prometheusData ? reconNodeTree(prometheusData, threshold) : []),
+  const nodeTree = useMemo<INodeTreeStructure>(
+    () =>
+      prometheusData
+        ? reconNodeTree(prometheusData, threshold)
+        : ({
+            children: [] as INodeTreeStructure[],
+          } as INodeTreeStructure),
     [prometheusData, threshold]
     [prometheusData, threshold]
   );
   );
   const lineChartsData = useMemo<ILineChartData[]>(
   const lineChartsData = useMemo<ILineChartData[]>(
@@ -118,7 +123,6 @@ const SystemHealthyView = () => {
         : [],
         : [],
     [prometheusData]
     [prometheusData]
   );
   );
-  const [selectedNode, setSelectedNode] = useState<INodeTreeStructure>();
 
 
   const updateData = async () => {
   const updateData = async () => {
     const curT = new Date().getTime();
     const curT = new Date().getTime();
@@ -146,24 +150,28 @@ const SystemHealthyView = () => {
     ENodeService.data,
     ENodeService.data,
   ];
   ];
 
 
-  const exploreDetailHandler = (service: ENodeService) => {
-    console.log('service', service);
-    if (hasDetailServices.indexOf(service) >= 0) {
-      const node = nodes.find(node => node.service === service);
-      node !== selectedNode && setSelectedNode(node);
-    }
-  };
+  const [selectedService, setSelectedService] = useState<ENodeService>(
+    ENodeService.root
+  );
+
+  const selectedNode = useMemo<INodeTreeStructure>(() => {
+    if (hasDetailServices.indexOf(selectedService) >= 0) {
+      return nodeTree.children.find(
+        node => node.service === selectedService
+      ) as INodeTreeStructure;
+    } else return nodeTree;
+  }, [selectedService, nodeTree]);
 
 
   return (
   return (
     <div className={classes.root}>
     <div className={classes.root}>
       <div className={classes.mainView}>
       <div className={classes.mainView}>
         <Topology
         <Topology
-          nodes={nodes}
-          // selectedNode={selectedNode as INodeTreeStructure}
-          onClick={exploreDetailHandler}
+          nodeTree={nodeTree}
+          selectedService={selectedService}
+          onClick={setSelectedService}
         />
         />
         <HealthyIndexOverview
         <HealthyIndexOverview
-          nodes={nodes}
+          selectedNode={selectedNode}
           lineChartsData={lineChartsData}
           lineChartsData={lineChartsData}
           threshold={threshold}
           threshold={threshold}
           setThreshold={setThreshold}
           setThreshold={setThreshold}
@@ -171,29 +179,6 @@ const SystemHealthyView = () => {
           setTimeRange={setTimeRange}
           setTimeRange={setTimeRange}
         />
         />
       </div>
       </div>
-      <div
-        className={clsx(
-          classes.detailView,
-          selectedNode ? classes.showDetailView : classes.hideDetailView
-        )}
-      >
-        <button
-          className={classes.childCloseBtn}
-          onClick={() => setSelectedNode(undefined)}
-        >
-          <KeyboardArrowDown />
-        </button>
-        <div className={classes.detailViewContainer}>
-          {selectedNode && (
-            <Topology
-              nodes={selectedNode.children}
-              // selectedNode={selectedNode}
-              onClick={exploreDetailHandler}
-            />
-          )}
-          <HealthyIndexDetailView />
-        </div>
-      </div>
     </div>
     </div>
   );
   );
 };
 };

+ 1 - 1
client/src/pages/systemHealthy/ThresholdSetting.tsx

@@ -79,7 +79,7 @@ function ThresholdSettingDialog({
   const inputConfigs: ITextfieldConfig[] = useMemo(
   const inputConfigs: ITextfieldConfig[] = useMemo(
     () => [
     () => [
       {
       {
-        label: `CPU (Cores)`,
+        label: `CPU (Core)`,
         key: 'prometheus_address',
         key: 'prometheus_address',
         onChange: (v: string) => handleFormChange('cpu', +v),
         onChange: (v: string) => handleFormChange('cpu', +v),
         variant: 'filled',
         variant: 'filled',

+ 8 - 8
client/src/pages/systemHealthy/TimeRangeTabs.tsx

@@ -8,24 +8,23 @@ const getStyles = makeStyles((theme: Theme) => ({
   root: {
   root: {
     fontSize: '14px',
     fontSize: '14px',
     display: 'flex',
     display: 'flex',
-    alignItems: "flex-end",
+    alignItems: 'flex-end',
     color: '#999',
     color: '#999',
     fontWeight: 500,
     fontWeight: 500,
   },
   },
   divider: {
   divider: {
-    margin: "0 4px"
+    margin: '0 4px',
   },
   },
   label: {
   label: {
-    cursor: "pointer",
+    cursor: 'pointer',
     '&:hover': {
     '&:hover': {
-
-    fontSize: "16px",
-    }
+      fontSize: '16px',
+    },
   },
   },
   active: {
   active: {
     fontWeight: 600,
     fontWeight: 600,
-    fontSize: "16px",
-    color: "#222"
+    fontSize: '16px',
+    color: '#222',
   },
   },
 }));
 }));
 
 
@@ -43,6 +42,7 @@ const TimeRangeTabs = ({
         <>
         <>
           {i > 0 && <div className={classes.divider}> / </div>}
           {i > 0 && <div className={classes.divider}> / </div>}
           <div
           <div
+            key={timeRangeOption.label}
             className={clsx(
             className={clsx(
               classes.label,
               classes.label,
               timeRangeOption.value === timeRange.value && classes.active
               timeRangeOption.value === timeRange.value && classes.active

+ 35 - 7
client/src/pages/systemHealthy/Topology.tsx

@@ -8,6 +8,7 @@ import {
 } from './consts';
 } from './consts';
 import { getIcon } from './getIcon';
 import { getIcon } from './getIcon';
 import { ENodeService, ENodeType, INodeTreeStructure } from './Types';
 import { ENodeService, ENodeType, INodeTreeStructure } from './Types';
+import clsx from 'clsx';
 
 
 const getStyles = makeStyles((theme: Theme) => ({
 const getStyles = makeStyles((theme: Theme) => ({
   root: {
   root: {
@@ -38,6 +39,20 @@ const getStyles = makeStyles((theme: Theme) => ({
       outline: 'none',
       outline: 'none',
     },
     },
   },
   },
+  selected: {
+    '& svg path': {
+      fill: 'white',
+    },
+
+    '& circle': {
+      fill: theme.palette.primary.main,
+      stroke: theme.palette.primary.main,
+    },
+
+    '& text': {
+      fill: 'white',
+    },
+  },
 }));
 }));
 
 
 const nodesLayout = (
 const nodesLayout = (
@@ -68,11 +83,13 @@ const nodesLayout = (
 };
 };
 
 
 const Topology = ({
 const Topology = ({
-  nodes,
+  nodeTree,
   onClick,
   onClick,
+  selectedService,
 }: {
 }: {
-  nodes: INodeTreeStructure[];
+  nodeTree: INodeTreeStructure;
   onClick: (service: ENodeService) => void;
   onClick: (service: ENodeService) => void;
+  selectedService: ENodeService;
 }) => {
 }) => {
   const width = TOPO_WIDTH;
   const width = TOPO_WIDTH;
   const height = TOPO_HEIGHT;
   const height = TOPO_HEIGHT;
@@ -81,9 +98,7 @@ const Topology = ({
   const theme = useTheme();
   const theme = useTheme();
 
 
   const { rootNode, childrenNodes, rootPos, childrenPos, subChildrenPos } =
   const { rootNode, childrenNodes, rootPos, childrenPos, subChildrenPos } =
-    nodesLayout(nodes, width, height);
-
-  console.log('!!');
+    nodesLayout(nodeTree.children, width, height);
 
 
   return (
   return (
     <div className={classes.root}>
     <div className={classes.root}>
@@ -91,6 +106,7 @@ const Topology = ({
         {childrenNodes.map((node, i) => {
         {childrenNodes.map((node, i) => {
           const childPos = childrenPos[i];
           const childPos = childrenPos[i];
           const subChildPos = subChildrenPos[i];
           const subChildPos = subChildrenPos[i];
+
           return (
           return (
             <>
             <>
               {node.children.length > 0 && (
               {node.children.length > 0 && (
@@ -129,7 +145,13 @@ const Topology = ({
                   >{`${node.children.length - 1} Node(s)`}</text>
                   >{`${node.children.length - 1} Node(s)`}</text>
                 </g>
                 </g>
               )}
               )}
-              <g className={classes.node} onClick={() => onClick(node.service)}>
+              <g
+                className={clsx(
+                  classes.node,
+                  node.service === selectedService && classes.selected
+                )}
+                onClick={() => onClick(node.service)}
+              >
                 <line
                 <line
                   x1={rootPos[0]}
                   x1={rootPos[0]}
                   y1={rootPos[1]}
                   y1={rootPos[1]}
@@ -163,7 +185,13 @@ const Topology = ({
                 </text>
                 </text>
               </g>
               </g>
 
 
-              <g className={classes.node}>
+              <g
+                onClick={() => onClick(rootNode.service)}
+                className={clsx(
+                  classes.node,
+                  rootNode.service === selectedService && classes.selected
+                )}
+              >
                 <circle
                 <circle
                   cx={rootPos[0]}
                   cx={rootPos[0]}
                   cy={rootPos[1]}
                   cy={rootPos[1]}

+ 2 - 0
client/src/pages/systemHealthy/consts.ts

@@ -17,9 +17,11 @@ export const HEALTHY_STATUS_COLORS = {
 };
 };
 
 
 export const LINE_CHART_LARGE_HEIGHT = 60;
 export const LINE_CHART_LARGE_HEIGHT = 60;
+export const LINE_CHART_SMALL_HEIGHT = 42;
 export const LINE_COLOR = '#394E97';
 export const LINE_COLOR = '#394E97';
 export const LINE_LABEL_Y_PADDING = 6;
 export const LINE_LABEL_Y_PADDING = 6;
 export const LINE_LABEL_FONT_SIZE = 14;
 export const LINE_LABEL_FONT_SIZE = 14;
+export const LINE_SMALL_LABEL_FONT_SIZE = 12;
 export const timeRangeOptions: ITimeRangeOption[] = [
 export const timeRangeOptions: ITimeRangeOption[] = [
     {
     {
       label: '1h',
       label: '1h',

+ 15 - 9
client/src/pages/systemHealthy/dataHandler.ts

@@ -109,15 +109,21 @@ export const reconNodeTree = (
     threshold
     threshold
   );
   );
 
 
-  return [
-    rootNode,
-    indexNode,
-    queryNode,
-    dataNode,
-    metaNode,
-    msgstreamNode,
-    objstorageNode,
-  ] as INodeTreeStructure[];
+  return {
+    service: ENodeService.milvus,
+    type: ENodeType.overview,
+    label: 'Overview',
+    healthyStatus: [],
+    children: [
+      rootNode,
+      indexNode,
+      queryNode,
+      dataNode,
+      metaNode,
+      msgstreamNode,
+      objstorageNode,
+    ],
+  } as INodeTreeStructure;
 };
 };
 
 
 export const THIRD_PARTY_SERVICE_HEALTHY_THRESHOLD = 0.95;
 export const THIRD_PARTY_SERVICE_HEALTHY_THRESHOLD = 0.95;