Browse Source

..

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

+ 1 - 0
client/package.json

@@ -12,6 +12,7 @@
     "@material-ui/icons": "^4.11.3",
     "@material-ui/lab": "4.0.0-alpha.58",
     "@material-ui/pickers": "^3.3.10",
+    "@mui/icons-material": "^5.11.9",
     "@mui/x-data-grid": "^4.0.0",
     "axios": "^0.21.3",
     "d3": "^7.8.2",

+ 1 - 1
client/src/pages/connect/AuthForm.tsx

@@ -178,7 +178,7 @@ export const AuthForm = (props: any) => {
         defaultValue: prometheusInstance,
       },
       {
-        label: `${attuTrans.prometheusNamespace} ${attuTrans.optional}`,
+        label: `${attuTrans.prometheusNamespace}`,
         key: 'prometheus_namespace',
         onChange: setPrometheusNamespace,
         variant: 'filled',

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

@@ -1,5 +1,5 @@
-const ThresholdPanel = () => {
+const HealthyIndexDetailView = () => {
   return <div></div>;
 };
 
-export default ThresholdPanel;
+export default HealthyIndexDetailView;

+ 58 - 0
client/src/pages/systemHealthy/HealthyIndexLegend.tsx

@@ -0,0 +1,58 @@
+import { makeStyles, Theme } from '@material-ui/core';
+import { HEALTHY_INDEX_ROW_HEIGHT, HEALTHY_STATUS_COLORS } from './consts';
+import { EHealthyStatus } from './Types';
+
+const legendData = [
+  {
+    label: 'NoData',
+    value: EHealthyStatus.noData,
+  },
+  {
+    label: 'Healthy',
+    value: EHealthyStatus.healthy,
+  },
+  {
+    label: 'Warning',
+    value: EHealthyStatus.warning,
+  },
+  {
+    label: 'Failed',
+    value: EHealthyStatus.failed,
+  },
+];
+
+const getStyles = makeStyles((theme: Theme) => ({
+  legendItem: {
+    display: 'flex',
+    marginLeft: '12px',
+    fontSize: '10px',
+    alignItems: 'flex-end',
+  },
+  legendIcon: {
+    width: '16px',
+    borderRadius: '1px',
+  },
+  legendText: { marginLeft: '8px', fontWeight: 500, color: '#666' },
+}));
+
+const HealthyIndexLegend = () => {
+  const classes = getStyles();
+  return (
+    <>
+      {legendData.map(legend => (
+        <div className={classes.legendItem}>
+          <div
+            className={classes.legendIcon}
+            style={{
+              background: HEALTHY_STATUS_COLORS[legend.value],
+              height: `${HEALTHY_INDEX_ROW_HEIGHT * 0.8}px`,
+            }}
+          ></div>
+          <div className={classes.legendText}>{legend.label}</div>
+        </div>
+      ))}
+    </>
+  );
+};
+
+export default HealthyIndexLegend;

+ 30 - 18
client/src/pages/systemHealthy/HealthyIndexOverview.tsx

@@ -1,12 +1,17 @@
 import { makeStyles, Theme } from '@material-ui/core';
+import { Dispatch, SetStateAction } from 'react';
 import {
   CHART_WIDTH,
   LINE_CHART_LARGE_HEIGHT,
   MAIN_VIEW_WIDTH,
 } from './consts';
+import HealthyIndexLegend from './HealthyIndexLegend';
 import HealthyIndexRow from './HealthyIndexRow';
 import LineChartLarge from './LineChartLarge';
-import { ILineChartData, INodeTreeStructure } from './Types';
+import ThresholdPanel from './ThresholdPanel';
+import ThresholdSetting from './ThresholdSetting';
+import { ILineChartData, INodeTreeStructure, IThreshold } from './Types';
+// import SettingsApplicationsIcon from '@mui/icons-material/SettingsApplications';
 
 // export const CHART_LABEL_WIDTH = 70;
 
@@ -34,9 +39,6 @@ const getStyles = makeStyles((theme: Theme) => ({
     display: 'flex',
     alignItems: 'flex-end',
   },
-  legendItem: { display: 'flex', marginLeft: '12px' },
-  legendIcon: {},
-  legendText: { marginLeft: '8px' },
   settingIcon: { marginLeft: '16px' },
   mainView: {
     width: '100%',
@@ -80,9 +82,13 @@ const getStyles = makeStyles((theme: Theme) => ({
 const HealthyIndexOverview = ({
   nodes,
   lineChartsData,
+  threshold,
+  setThreshold,
 }: {
   nodes: INodeTreeStructure[];
   lineChartsData: ILineChartData[];
+  threshold: IThreshold;
+  setThreshold: Dispatch<SetStateAction<IThreshold>>;
 }) => {
   const classes = getStyles();
   console.log('nodes', nodes);
@@ -92,22 +98,24 @@ const HealthyIndexOverview = ({
       <div className={classes.headerContent}>
         <div className={classes.titleContainer}>
           <div className={classes.title}>Healthy Status</div>
-          <div className={classes.timeRangeTabs}>7day / 24h / 1h</div>
+          <div className={classes.timeRangeTabs}>
+            <span>
+              <b>7day</b>
+            </span>
+            <span> / </span>
+            <span>24h</span>
+            <span> / </span>
+            <span>1h</span>
+          </div>
         </div>
         <div className={classes.legendContainer}>
-          <div className={classes.legendItem}>
-            <div className={classes.legendIcon}>icon</div>
-            <div className={classes.legendText}>healthy</div>
-          </div>
-          <div className={classes.legendItem}>
-            <div className={classes.legendIcon}>icon</div>
-            <div className={classes.legendText}>healthy</div>
-          </div>
-          <div className={classes.legendItem}>
-            <div className={classes.legendIcon}>icon</div>
-            <div className={classes.legendText}>healthy</div>
+          <HealthyIndexLegend />
+          <div className={classes.settingIcon}>
+            <ThresholdSetting
+              threshold={threshold}
+              setThreshold={setThreshold}
+            />
           </div>
-          <div className={classes.settingIcon}>setting</div>
         </div>
       </div>
       <div className={classes.mainView}>
@@ -126,7 +134,11 @@ const HealthyIndexOverview = ({
           <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} />
+              <LineChartLarge
+                data={chartData.data}
+                format={chartData.format}
+                unit={chartData.unit}
+              />
             </div>
           </div>
         ))}

+ 0 - 3
client/src/pages/systemHealthy/LineChartLarge.tsx

@@ -1,10 +1,8 @@
 import * as d3 from 'd3';
-import { useEffect } from 'react';
 import {
   CHART_WIDTH,
   LINE_CHART_LARGE_HEIGHT,
   LINE_COLOR,
-  LINE_LABEL_FONT_FAMILY,
   LINE_LABEL_FONT_SIZE,
   LINE_LABEL_Y_PADDING,
 } from './consts';
@@ -22,7 +20,6 @@ const LineChartLarge = ({
   const width = CHART_WIDTH;
   const height = LINE_CHART_LARGE_HEIGHT - 3;
   const fontSize = LINE_LABEL_FONT_SIZE;
-  const fontFamily = LINE_LABEL_FONT_FAMILY;
 
   const xDomain = [0, length];
   const xRange = [0, CHART_WIDTH];

+ 58 - 18
client/src/pages/systemHealthy/SystemHealthyView.tsx

@@ -1,5 +1,5 @@
 import { makeStyles, Theme } from '@material-ui/core';
-import { useEffect, useState } from 'react';
+import { useEffect, useMemo, useState } from 'react';
 import { useNavigationHook } from '../../hooks/Navigation';
 import { useInterval } from '../../hooks/SystemView';
 import { PrometheusHttp } from '../../http/Prometheus';
@@ -21,6 +21,7 @@ import * as d3 from 'd3';
 import { reconNodeTree } from './dataHandler';
 import HealthyIndexOverview from './HealthyIndexOverview';
 import HealthyIndexDetailView from './HealthyIndexDetailView';
+import { KeyboardArrowDown } from '@material-ui/icons';
 // import data from "./data.json";
 
 const getStyles = makeStyles((theme: Theme) => ({
@@ -51,13 +52,28 @@ const getStyles = makeStyles((theme: Theme) => ({
   },
   showDetailView: {
     top: 0,
-    minHeight: '100%',
+    minHeight: '100vh',
     height: 'fit-content',
   },
   hideDetailView: {
     top: '2000px',
     maxHeight: 0,
   },
+  detailViewContainer: {
+    borderRadius: '8px',
+    boxShadow: '3px 3px 10px rgba(0, 0, 0, 0.05)',
+    display: 'grid',
+    gridTemplateColumns: '1fr auto',
+    // marginTop: '14px',
+    height: '100%',
+  },
+  childCloseBtn: {
+    border: 0,
+    backgroundColor: 'white',
+    gridArea: 'a',
+    cursor: 'pointer',
+    width: '100%',
+  },
 }));
 
 const SystemHealthyView = () => {
@@ -91,7 +107,7 @@ const SystemHealthyView = () => {
   const [selectedNode, setSelectedNode] = useState<INodeTreeStructure>();
   const defaultThreshold = {
     cpu: 1,
-    memory: 8 * 1024 * 1024 * 1024,
+    memory: 1 * 1024 * 1024 * 1024,
   };
   const [threshold, setThreshold] = useState<IThreshold>(defaultThreshold);
 
@@ -116,8 +132,8 @@ const SystemHealthyView = () => {
       {
         label: 'Search Latency',
         data: result.sqLatency,
-        format: (d) => d.toFixed(0),
-        unit: 'ms'
+        format: d => d.toFixed(0),
+        unit: 'ms',
       },
     ]);
   };
@@ -131,18 +147,34 @@ const SystemHealthyView = () => {
     updateData();
   }, INTERVAL);
 
-  const topoNodes = !!selectedNode ? selectedNode.children : nodes;
+  const hasDetailServices = [
+    ENodeService.index,
+    ENodeService.query,
+    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);
+    }
+  };
 
   return (
     <div className={classes.root}>
       <div className={classes.mainView}>
         <Topology
           nodes={nodes}
-          // nodes={nodes[2].children}
-          selectedNode={selectedNode as INodeTreeStructure}
-          setSelectedNode={setSelectedNode}
+          // selectedNode={selectedNode as INodeTreeStructure}
+          onClick={exploreDetailHandler}
+        />
+        <HealthyIndexOverview
+          nodes={nodes}
+          lineChartsData={lineChartsData}
+          threshold={threshold}
+          setThreshold={setThreshold}
         />
-        <HealthyIndexOverview nodes={nodes} lineChartsData={lineChartsData} />
       </div>
       <div
         className={clsx(
@@ -150,14 +182,22 @@ const SystemHealthyView = () => {
           selectedNode ? classes.showDetailView : classes.hideDetailView
         )}
       >
-        {selectedNode && (
-          <Topology
-            nodes={selectedNode.children}
-            selectedNode={selectedNode}
-            setSelectedNode={setSelectedNode}
-          />
-        )}
-        <HealthyIndexDetailView />
+        <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>
   );

+ 162 - 0
client/src/pages/systemHealthy/ThresholdSetting.tsx

@@ -0,0 +1,162 @@
+import {
+  Button,
+  Dialog,
+  DialogTitle,
+  Input,
+  List,
+  ListItem,
+  makeStyles,
+  Theme,
+  Typography,
+} from '@material-ui/core';
+import { Dispatch, SetStateAction, useMemo, useState } from 'react';
+import CustomInput from '../../components/customInput/CustomInput';
+import { ITextfieldConfig } from '../../components/customInput/Types';
+import { useFormValidation } from '../../hooks/Form';
+import { formatForm } from '../../utils/Form';
+import { HEALTHY_STATUS_COLORS } from './consts';
+import { EHealthyStatus, IThreshold } from './Types';
+
+export interface SimpleDialogProps {
+  open: boolean;
+  selectedValue: string;
+  onClose: (value: string) => void;
+}
+
+const getStyles = makeStyles((theme: Theme) => ({
+  root: {
+    padding: '12px 16px',
+  },
+  note: {
+    fontWeight: 500,
+    color: '#444',
+    fontSize: '14px',
+    margin: theme.spacing(1, 0, 2),
+  },
+  input: {
+    margin: theme.spacing(0.5, 0, 0),
+  },
+}));
+
+function ThresholdSettingDialog({
+  onClose,
+  open,
+  threshold,
+  setThreshold,
+}: {
+  open: boolean;
+  onClose: () => void;
+  threshold: IThreshold;
+  setThreshold: Dispatch<SetStateAction<IThreshold>>;
+}) {
+  const classes = getStyles();
+  const handleClose = () => {
+    console.log('form', form)
+    setThreshold(form);
+    onClose();
+  };
+
+  const [form, setForm] = useState<IThreshold>({
+    cpu: threshold.cpu,
+    memory: threshold.memory,
+  });
+
+  const handleFormChange = (key: 'cpu' | 'memory', value: number) => {
+    setForm(v => ({ ...v, [key]: value }));
+  };
+  const checkedForm = useMemo(() => {
+    return formatForm(form);
+  }, [form]);
+  const { validation, checkIsValid } = useFormValidation(checkedForm);
+
+  const inputConfigs: ITextfieldConfig[] = useMemo(
+    () => [
+      {
+        label: `CPU (Cores)`,
+        key: 'prometheus_address',
+        onChange: (v: string) => handleFormChange('cpu', +v),
+        variant: 'filled',
+        className: classes.input,
+        placeholder: 'CPU',
+        fullWidth: true,
+
+        defaultValue: form.cpu,
+      },
+      {
+        label: `Memory (GB)`,
+        key: 'prometheus_address',
+        onChange: (v: string) => handleFormChange('memory', +v),
+        variant: 'filled',
+        className: classes.input,
+        placeholder: 'Memory',
+        fullWidth: true,
+
+        defaultValue: form.memory,
+      },
+    ],
+    []
+  );
+
+  return (
+    <Dialog onClose={handleClose} open={open}>
+      <div className={classes.root}>
+        <div className={classes.note}>
+          {`Exceeding any threshold will result in a `}
+          <span
+            style={{
+              color: HEALTHY_STATUS_COLORS[EHealthyStatus.warning],
+              fontWeight: 600,
+              fontSize: 18,
+            }}
+          >
+            warning
+          </span>
+          .
+        </div>
+        {inputConfigs.map(v => (
+          <CustomInput
+            type="text"
+            textConfig={v}
+            key={v.label}
+            checkValid={checkIsValid}
+            validInfo={validation}
+          />
+        ))}
+      </div>
+    </Dialog>
+  );
+}
+
+const ThresholdSetting = ({
+  threshold,
+  setThreshold,
+}: {
+  threshold: IThreshold;
+  setThreshold: Dispatch<SetStateAction<IThreshold>>;
+}) => {
+  const [open, setOpen] = useState(false);
+
+  const handleClickOpen = () => {
+    setOpen(true);
+  };
+
+  const handleClose = () => {
+    setOpen(false);
+  };
+
+  return (
+    <div>
+      <Button variant="outlined" onClick={handleClickOpen}>
+        icon
+      </Button>
+      <ThresholdSettingDialog
+        threshold={threshold}
+        setThreshold={setThreshold}
+        open={open}
+        onClose={handleClose}
+      />
+    </div>
+  );
+};
+
+export default ThresholdSetting;

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

@@ -16,6 +16,7 @@ const getStyles = makeStyles((theme: Theme) => ({
     overflow: 'auto',
     backgroundColor: 'white',
     // overflow: 'visible',
+    minHeight: '90%',
   },
   node: {
     transition: 'all .25s',
@@ -49,7 +50,7 @@ const nodesLayout = (
     (nodes.find(node => node.type === ENodeType.coord) as INodeTreeStructure);
   const childrenNodes = nodes.filter(node => node !== rootNode);
 
-  const rootPos = [300, height / 2];
+  const rootPos = [300, height * 0.45];
   const angleStep = (2 * Math.PI) / Math.max(childrenNodes.length, 3);
   const randomBias = angleStep * 0.4;
   const childrenPos = childrenNodes.map((node, i) => [
@@ -68,12 +69,10 @@ const nodesLayout = (
 
 const Topology = ({
   nodes,
-  selectedNode,
-  setSelectedNode,
+  onClick,
 }: {
   nodes: INodeTreeStructure[];
-  selectedNode: INodeTreeStructure;
-  setSelectedNode: Dispatch<INodeTreeStructure>;
+  onClick: (service: ENodeService) => void;
 }) => {
   const width = TOPO_WIDTH;
   const height = TOPO_HEIGHT;
@@ -93,7 +92,7 @@ const Topology = ({
           return (
             <>
               {node.children.length > 0 && (
-                <g className={classes.node}>
+                <g className={classes.node} onClick={() => onClick(node.service)}>
                   <line
                     x1={childPos[0]}
                     y1={childPos[1]}
@@ -125,7 +124,7 @@ const Topology = ({
                   >{`${node.children.length - 1} Node(s)`}</text>
                 </g>
               )}
-              <g className={classes.node}>
+              <g className={classes.node} onClick={() => onClick(node.service)}>
                 <line
                   x1={rootPos[0]}
                   y1={rootPos[1]}

+ 4 - 4
client/src/pages/systemHealthy/Types.ts

@@ -15,10 +15,10 @@ export enum ENodeService {
   meta = 'Meta',
   msgstream = 'MsgStream',
   objstorage = 'ObjStorage',
-  root="Root",
-  query="Query",
-  index="Index",
-  data="Data",
+  root = 'Root',
+  query = 'Query',
+  index = 'Index',
+  data = 'Data',
 }
 
 export interface ILineChartData {

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

@@ -16,8 +16,7 @@ export const HEALTHY_STATUS_COLORS = {
   [EHealthyStatus.failed]: '#F16415',
 };
 
-export const LINE_CHART_LARGE_HEIGHT = 80;
+export const LINE_CHART_LARGE_HEIGHT = 60;
 export const LINE_COLOR = '#394E97';
 export const LINE_LABEL_Y_PADDING = 6;
 export const LINE_LABEL_FONT_SIZE = 14;
-export const LINE_LABEL_FONT_FAMILY = 'Helvetica Neue';

+ 19 - 0
client/yarn.lock

@@ -392,6 +392,13 @@
   dependencies:
     regenerator-runtime "^0.13.10"
 
+"@babel/runtime@^7.20.13":
+  version "7.20.13"
+  resolved "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.20.13.tgz#7055ab8a7cff2b8f6058bf6ae45ff84ad2aded4b"
+  integrity sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==
+  dependencies:
+    regenerator-runtime "^0.13.11"
+
 "@babel/runtime@^7.6.0":
   version "7.16.3"
   resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.3.tgz#b86f0db02a04187a3c17caa77de69840165d42d5"
@@ -682,6 +689,13 @@
     prop-types "^15.7.2"
     react-is "^17.0.2"
 
+"@mui/icons-material@^5.11.9":
+  version "5.11.9"
+  resolved "https://registry.npmmirror.com/@mui/icons-material/-/icons-material-5.11.9.tgz#db26c106d0d977ae1fc0c2d20ba2e829a8174c05"
+  integrity sha512-SPANMk6K757Q1x48nCwPGdSNb8B71d+2hPMJ0V12VWerpSsbjZtvAPi5FAn13l2O5mwWkvI0Kne+0tCgnNxMNw==
+  dependencies:
+    "@babel/runtime" "^7.20.13"
+
 "@mui/x-data-grid@^4.0.0":
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/@mui/x-data-grid/-/x-data-grid-4.0.2.tgz#5345e74630dff2efcb9a1efa04c6c8facfd8c80a"
@@ -3481,6 +3495,11 @@ regenerator-runtime@^0.13.10:
   resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz#ed07b19616bcbec5da6274ebc75ae95634bfc2ee"
   integrity sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw==
 
+regenerator-runtime@^0.13.11:
+  version "0.13.11"
+  resolved "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
+  integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
+
 regenerator-runtime@^0.13.4:
   version "0.13.9"
   resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"

+ 18 - 9
server/src/prometheus/prometheus.service.ts

@@ -122,7 +122,8 @@ export class PrometheusService {
 
     const res = result.data.result[0].values.map((d: any) => +d[1]).slice(1);
 
-    let leftLossCount, rightLossCount;
+    let leftLossCount;
+    let rightLossCount;
     leftLossCount = Math.floor((data[0].values[0][0] * 1000 - start) / step);
     res.unshift(...Array(leftLossCount).fill(-1));
     rightLossCount = Math.floor(
@@ -147,7 +148,8 @@ export class PrometheusService {
       .map((d: number, i: number) => (i > 0 ? d - totalCount[i - 1] : d))
       .slice(1);
 
-    let leftLossCount, rightLossCount;
+    let leftLossCount;
+    let rightLossCount;
     leftLossCount = Math.floor((data[0].values[0][0] * 1000 - start) / step);
     res.unshift(...Array(leftLossCount).fill(-1));
     rightLossCount = Math.floor(
@@ -169,10 +171,10 @@ export class PrometheusService {
     const length = Math.floor((end - start) / step);
     if (data.length === 0) return Array(length).fill(0);
 
-    const res = data[0].values
-      .map((d: any) => (isNaN(d[1]) ? 0 : +d[1]))
-      // .slice(1);
-    let leftLossCount, rightLossCount;
+    const res = data[0].values.map((d: any) => (isNaN(d[1]) ? 0 : +d[1]));
+    // .slice(1);
+    let leftLossCount;
+    let rightLossCount;
     leftLossCount = Math.floor((data[0].values[0][0] * 1000 - start) / step);
     res.unshift(...Array(leftLossCount).fill(-1));
     rightLossCount = Math.floor(
@@ -238,7 +240,8 @@ export class PrometheusService {
       (d: any) => d.metric.container.indexOf(type) >= 0
     );
     const nodesData = cpuNodes.map((d: any) => {
-      const type = d.metric.container.indexOf('coord') >= 0 ? 'coord' : 'node';
+      const nodeType =
+        d.metric.container.indexOf('coord') >= 0 ? 'coord' : 'node';
       const pod = d.metric.pod;
       const cpuProcessTotal = d.values.map((v: any) => +v[1]);
       const cpu = cpuProcessTotal
@@ -246,7 +249,8 @@ export class PrometheusService {
         .slice(1)
         .map((v: number) => v / (step / 1000));
 
-      let leftLossCount, rightLossCount;
+      let leftLossCount;
+      let rightLossCount;
       leftLossCount = Math.floor((d.values[0][0] * 1000 - start) / step);
       cpu.unshift(...Array(leftLossCount).fill(-1));
       rightLossCount = Math.floor(
@@ -264,7 +268,12 @@ export class PrometheusService {
       );
       memory.push(...Array(rightLossCount).fill(-2));
 
-      return { type, pod, cpu, memory: memory.slice(1) } as IPrometheusNode;
+      return {
+        type: nodeType,
+        pod,
+        cpu,
+        memory: memory.slice(1),
+      } as IPrometheusNode;
     });
 
     return nodesData;