Browse Source

Merge pull request #252 from czhen-zilliz/main

Add custom icon for plugin & Move vector search to plugin
nameczz 3 years ago
parent
commit
20a3c0b8b9

+ 11 - 2
client/README.md

@@ -16,7 +16,8 @@
       ├── hooks                   # React hooks
       ├── http                    # Http request api. And we have http interceptor in GlobalEffect.tsx file
       ├── i18n                    # Language i18n
-      ├── pages                   # All pages , business components and types.
+      ├── pages                   # All pages, business components and types.
+      ├── plugins                 # All import plugins.
       ├── router                  # React router, control the page auth.
       ├── styles                  # Styles, normally we use material to control styles.
       ├── types                   # Global types
@@ -60,6 +61,14 @@ Like utils / consts / utils / hooks , we dont want put all functions or datas in
 
 So when we need to create new file , treat the file like Class then name it.
 
+### Plugins Folder
+
+You can deploy any plugin developed by [template](https://github.com/zilliztech/insight-plugin-template). All client plugins should be placed at `src/plugins` folder. We have transferred `System View` and `Vector Search` to plugins. For more plugins development details please refer to [template repo](https://github.com/zilliztech/insight-plugin-template).
+
+### Alias Map
+
+As `react-app-rewire-alias` in `config-overrides.js`, we can use alias import. `insight_src/` is equal to `client/src` .
+
 ### Icon
 
 We put all icons in components/icons file. Normally we use material icon.
@@ -74,4 +83,4 @@ We use react-app-rewired to change webpack config.
 
 If we want to change the webpack config, we can edit config-overrides.js file.
 
-And we use milvus insight server to host our client site. So our build path is `../server/build` .
+Our build path is `./build`. And we use milvus insight server to host our client site.

+ 18 - 10
client/src/components/layout/Layout.tsx

@@ -9,6 +9,7 @@ import { useTranslation } from 'react-i18next';
 import { useHistory, useLocation } from 'react-router-dom';
 import { authContext } from '../../context/Auth';
 import { IconsType } from '../icons/Types';
+import loadable from '@loadable/component';
 
 const PLUGIN_DEV = process.env?.REACT_APP_PLUGIN_DEV;
 
@@ -81,16 +82,16 @@ const Layout = (props: any) => {
       label: navTrans('collection'),
       onClick: () => history.push('/collections'),
     },
-    {
-      icon: icons.navSearch,
-      label: navTrans('search'),
-      onClick: () => history.push('/search'),
-      iconActiveClass: 'activeSearchIcon',
-      iconNormalClass: 'normalSearchIcon',
-    },
+    // {
+    //   icon: icons.navSearch,
+    //   label: navTrans('search'),
+    //   onClick: () => history.push('/search'),
+    //   iconActiveClass: 'activeSearchIcon',
+    //   iconNormalClass: 'normalSearchIcon',
+    // },
   ];
 
-  function importAll(r: any) {
+  function importAll(r: any, outOfRoot = false) {
     r.keys().forEach((key: any) => {
       const content = r(key);
       const pathName = content.client?.path;
@@ -101,9 +102,16 @@ const Layout = (props: any) => {
       };
       result.onClick = () => history.push(`${pathName}`);
       const iconName: IconsType = content.client?.iconName;
+      const iconEntry = content.client?.icon;
+      const dirName = key.split('/config.json').shift().split('/')[1];
+      // const fileEntry = content.client?.entry;
       if (iconName) {
-        // TODO: support custom icon
         result.icon = icons[iconName];
+      } else if (iconEntry) {
+        const customIcon = outOfRoot
+          ? loadable(() => import(`all_plugins/${dirName}/client/${iconEntry}`))
+          : loadable(() => import(`../../plugins/${dirName}/${iconEntry}`));
+        result.icon = customIcon;
       }
       content.client?.iconActiveClass &&
         (result.iconActiveClass = content.client?.iconActiveClass);
@@ -114,7 +122,7 @@ const Layout = (props: any) => {
   }
   importAll(require.context('../../plugins', true, /config\.json$/));
   PLUGIN_DEV &&
-    importAll(require.context('all_plugins/', true, /config\.json$/));
+    importAll(require.context('all_plugins/', true, /config\.json$/), true);
 
   return (
     <div className={classes.root}>

+ 6 - 3
client/src/components/menu/Types.ts

@@ -1,5 +1,6 @@
 import { ButtonProps } from '@material-ui/core/Button';
 import { ReactElement } from 'react';
+import { LoadableClassComponent } from '@loadable/component';
 
 export type SimpleMenuType = {
   label: string;
@@ -14,10 +15,12 @@ export type SimpleMenuType = {
   menuItemWidth?: string;
 };
 
+type CustomIcon = (
+  props?: any
+) => React.ReactElement<any, string | React.JSXElementConstructor<any>>;
+
 export type NavMenuItem = {
-  icon: (
-    props?: any
-  ) => React.ReactElement<any, string | React.JSXElementConstructor<any>>;
+  icon: CustomIcon | LoadableClassComponent<any>;
   iconActiveClass?: string;
   iconNormalClass?: string;
   label: string;

+ 14 - 3
client/src/hooks/Navigation.ts

@@ -6,12 +6,15 @@ import { ALL_ROUTER_TYPES, NavInfo } from '../router/Types';
 export const useNavigationHook = (
   type: ALL_ROUTER_TYPES,
   extraParam?: {
-    collectionName: string;
+    collectionName?: string;
+    title?: string;
   }
 ) => {
   const { t: navTrans } = useTranslation('nav');
   const { setNavInfo } = useContext(navContext);
-  const { collectionName } = extraParam || { collectionName: '' };
+  const { collectionName = '', title = 'PLUGIN TITLE' } = extraParam || {
+    collectionName: '',
+  };
 
   useEffect(() => {
     switch (type) {
@@ -55,8 +58,16 @@ export const useNavigationHook = (
         setNavInfo(navInfo);
         break;
       }
+      case ALL_ROUTER_TYPES.PLUGIN: {
+        const navInfo: NavInfo = {
+          navTitle: title,
+          backPath: '',
+        };
+        setNavInfo(navInfo);
+        break;
+      }
       default:
         break;
     }
-  }, [type, navTrans, setNavInfo, collectionName]);
+  }, [type, navTrans, setNavInfo, collectionName, title]);
 };

+ 1 - 1
client/src/http/Collection.ts

@@ -5,7 +5,7 @@ import {
   InsertDataParam,
 } from '../pages/collections/Types';
 import { Field } from '../pages/schema/Types';
-import { VectorSearchParam } from '../pages/seach/Types';
+import { VectorSearchParam } from '../types/SearchTypes';
 import { QueryParam } from '../pages/query/Types';
 import { IndexState, ShowCollectionsType } from '../types/Milvus';
 import { formatNumber } from '../utils/Common';

+ 0 - 78
client/src/pages/system/BaseCard.tsx

@@ -1,78 +0,0 @@
-
-import { FC } from 'react';
-import { makeStyles } from '@material-ui/core';
-import { SvgIcon } from '@material-ui/core';
-import { BaseCardProps } from './Types';
-import { ReactComponent } from '../../assets/imgs/pic.svg'
-
-const getStyles = makeStyles(() => ({
-  root: {
-    backgroundColor: 'white',
-    borderRadius: '8px',
-    boxShadow: '3px 3px 10px rgba(0, 0, 0, 0.05)',
-    boxSizing: 'border-box',
-    height: '150px',
-    padding: '16px',
-  },
-  title: {
-    color: '#82838E',
-    fontSize: '14px',
-    marginBottom: '5px',
-    textTransform: 'capitalize',
-  },
-  content: {
-    color: '#010E29',
-    fontSize: '20px',
-    fontWeight: 600,
-    lineHeight: '36px',
-  },
-  desc: {
-    color: '#82838E',
-    fontSize: '14px',
-    lineHeight: '36px',
-    marginLeft: "8px",
-  },
-  emptyRoot: {
-    alignItems: 'center',
-    display: 'flex',
-    flexDirection: 'column',
-    justifyContent: 'flex-start',
-
-    '& > svg': {
-      marginTop: '10px',
-      width: '100%',
-    }
-  },
-  emptyTitle: {
-    fontSize: '14px',
-    marginTop: '14px',
-    textTransform: 'capitalize',
-  },
-  emptyDesc: {
-    fontSize: '10px',
-    color: '#82838E',
-    marginTop: '8px',
-  },
-}));
-
-const BaseCard: FC<BaseCardProps> = (props) => {
-  const classes = getStyles();
-  const { children, title, content, desc } = props;
-  return (
-    <div className={classes.root}>
-      <div className={classes.title}>{title}</div>
-      {content && <span className={classes.content}>{content}</span>}
-      {desc && <span className={classes.desc}>{desc}</span>}
-      {!content && !desc && (
-        <div className={classes.emptyRoot}>
-          <SvgIcon viewBox="0 0 101 26" component={ReactComponent} {...props} />
-          <span className={classes.emptyTitle}>no data available</span>
-          <span className={classes.emptyDesc}>There is no data to show you right now.</span>
-        </div>
-      )}
-      {children}
-    </div>
-  );
-};
-
-export default BaseCard;

+ 0 - 195
client/src/pages/system/DataCard.tsx

@@ -1,195 +0,0 @@
-
-import { FC } from 'react';
-import { useTranslation } from 'react-i18next';
-import { makeStyles } from '@material-ui/core';
-import Progress from './Progress';
-import { getByteString } from '../../utils/Format';
-import { DataProgressProps, DataSectionProps, DataCardProps } from './Types';
-
-const getStyles = makeStyles(() => ({
-  root: {
-    backgroundColor: '#F6F6F6',
-    borderTopRightRadius: '8px',
-    borderBottomRightRadius: '8px',
-    height: '100%',
-    padding: '20px 16px',
-    boxSizing: 'border-box',
-  },
-
-  title: {
-    display: 'flex',
-    justifyContent: 'space-between',
-  },
-
-  content: {
-    color: '#010E29',
-    fontSize: '20px',
-    fontWeight: 600,
-    lineHeight: '36px',
-  },
-
-  desc: {
-    color: '#82838E',
-    fontSize: '14px',
-    lineHeight: '36px',
-    marginLeft: "8px",
-  },
-
-  rootName: {
-    color: '#82838E',
-    fontSize: '20px',
-    lineHeight: '24px',
-  },
-
-  childName: {
-    color: '#06AFF2',
-    fontSize: '20px',
-    lineHeight: '24px',
-  },
-
-  ip: {
-    color: '#010E29',
-    fontSize: '16px',
-    lineHeight: '24px',
-  },
-
-  sectionRoot: {
-    borderSpacing: '0 1px',
-    display: 'table',
-    marginTop: '24px',
-    width: '100%'
-  },
-
-  sectionRow: {
-    display: 'table-row',
-  },
-
-  sectionHeaderCell: {
-    display: 'table-cell',
-    color: '#82838E',
-    fontSize: '12px',
-    lineHeight: '24px',
-    padding: '8px 16px',
-    textTransform: 'uppercase',
-    width: '50%',
-  },
-
-  sectionCell: {
-    backgroundColor: 'white',
-    color: '#010E29',
-    display: 'table-cell',
-    fontSize: '14px',
-    lineHeight: '24px',
-    padding: '12px 16px',
-    textTransform: 'capitalize',
-    verticalAlign: 'middle',
-    width: '50%',
-  },
-  progressTitle: {
-    fontSize: '14px',
-    color: '#010E29',
-    lineHeight: '24px',
-    display: 'flex',
-    justifyContent: 'space-between',
-  }
-}));
-
-const DataSection: FC<DataSectionProps> = (props) => {
-  const classes = getStyles();
-  const { titles, contents } = props;
-
-  return (
-    <div className={classes.sectionRoot}>
-      <div className={classes.sectionRow}>
-        {titles.map((titleEntry) => <div key={titleEntry} className={classes.sectionHeaderCell}>{titleEntry}</div>)}
-      </div>
-      {contents.map((contentEntry) => {
-        return (
-          <div key={contentEntry.label} className={classes.sectionRow}>
-            <div className={classes.sectionCell}>
-              {contentEntry.label}
-            </div>
-            <div className={classes.sectionCell}>
-              {contentEntry.value}
-            </div>
-          </div>)
-      })}
-    </div>
-  );
-}
-
-const DataProgress: FC<DataProgressProps> = ({ percent = 0, desc = '' }) => {
-  const classes = getStyles();
-  return (
-    <div>
-      <div className={classes.progressTitle}>
-        <span>{`${Number(percent * 100).toFixed(2)}%`}</span>
-        <span>{desc}</span>
-      </div>
-      <Progress percent={percent} color='#06AFF2' />
-    </div>
-  )
-};
-
-const DataCard: FC<DataCardProps & React.HTMLAttributes<HTMLDivElement>> = (props) => {
-  const classes = getStyles();
-  const { t } = useTranslation('systemView');
-  const { t: commonTrans } = useTranslation();
-  const capacityTrans: { [key in string]: string } = commonTrans('capacity');
-  const { node, extend } = props;
-  const hardwareTitle = [t('hardwareTitle'), t('valueTitle')];
-  const hardwareContent = [];
-  const infos = node?.infos?.hardware_infos || {};
-
-  const {
-    cpu_core_count: cpu = 0,
-    cpu_core_usage: cpuUsage = 0,
-    memory = 1,
-    memory_usage: memoryUsage = 0,
-    disk = 1,
-    disk_usage: diskUsage = 0,
-  } = infos;
-
-  if (extend) {
-    hardwareContent.push({ label: t('thCPUCount'), value: cpu });
-    hardwareContent.push({
-      label: t('thCPUUsage'), value: <DataProgress percent={cpuUsage / 100} />
-    });
-    hardwareContent.push({
-      label: t('thMemUsage'), value: <DataProgress percent={memoryUsage / memory} desc={getByteString(memoryUsage, memory, capacityTrans)} />
-    });
-    hardwareContent.push({
-      label: t('thDiskUsage'), value: <DataProgress percent={diskUsage / disk} desc={getByteString(diskUsage, disk, capacityTrans)} />
-    });
-  }
-
-  const systemTitle = [t('systemTitle'), t('valueTitle')];
-  const systemContent = [];
-  const sysInfos = node?.infos?.system_info || {};
-  const {
-    system_version: version,
-    deploy_mode: mode = '',
-    created_time: create = '',
-    updated_time: update = '',
-  } = sysInfos;
-  systemContent.push({ label: t('thVersion'), value: version });
-  systemContent.push({ label: t('thDeployMode'), value: mode });
-  systemContent.push({ label: t('thCreateTime'), value: create });
-  systemContent.push({ label: t('thUpdateTime'), value: update });
-
-  return (
-    <div className={classes.root}>
-      <div className={classes.title}>
-        <div>
-          <span className={classes.rootName}>Milvus / </span>
-          <span className={classes.childName}>{node?.infos?.name}</span>
-        </div>
-        <div className={classes.ip}>{`${t('thIP')}:${infos?.ip || ''}`}</div>
-      </div>
-      {extend && <DataSection titles={hardwareTitle} contents={hardwareContent} />}
-      <DataSection titles={systemTitle} contents={systemContent} />
-    </div>
-  );
-};
-
-export default DataCard;

+ 0 - 125
client/src/pages/system/LineChartCard.tsx

@@ -1,125 +0,0 @@
-
-import { FC, useState, useEffect, useRef } from 'react';
-import { makeStyles } from '@material-ui/core';
-import BaseCard from './BaseCard';
-import { LineChartCardProps, LinceChartNode } from './Types';
-
-const getStyles = makeStyles(() => ({
-  root: {
-    transform: 'scaleY(-1)',
-    maxWidth: '90%',
-  },
-  ycoord: {
-    cursor: 'pointer',
-    "&:hover, &:focus": {
-      "& line": {
-        transition: 'all .25s',
-        opacity: 1,
-      },
-    },
-
-    "&:hover": {
-      "& circle": {
-        fill: '#06AFF2',
-      },
-    },
-
-    "&:focus": {
-      outline: 'none',
-
-      "& circle": {
-        fill: '#06F3AF',
-      },
-    },
-  }
-}));
-
-const LineChartCard: FC<LineChartCardProps> = (props) => {
-
-  const FULL_HEIGHT = 60;
-  const FULL_WIDTH = 300;
-  const ROUND = 5;
-  const STEP = 25;
-
-  const classes = getStyles();
-  const { title, value } = props;
-  const [displayNodes, setDisplayNodes] = useState<LinceChartNode[]>([]);
-  const [currentNode, setCurrentNode] = useState<LinceChartNode>({
-    percent: 0,
-    value: 0,
-    timestamp: Date.now(),
-  });
-
-  const max = useRef(1);
-  const isHover = useRef(false);
-  const nodes = useRef<LinceChartNode[]>([]);
-
-  useEffect(() => {
-    // show at most 10 nodes. so remove the earliest node when nodes exceed 10
-    if (nodes.current.length > 9) {
-      nodes.current.shift();
-    }
-
-    if (value && max.current) {
-      // calculate the y-axis max scale
-      let currentMax = max.current;
-      if (value > max.current) {
-        const pow = Math.ceil(Math.log10(value));
-        currentMax = Math.pow(10, pow);
-        max.current = currentMax;
-      }
-
-      // generate a new node and save in ref
-      if (nodes.current) {
-        const newNodes = nodes.current.slice(0);
-        const newNode = {
-          percent: value / currentMax * 100,
-          value,
-          timestamp: Date.now(),
-        }
-        newNodes.push(newNode);
-        nodes.current = newNodes;
-
-        // refresh nodes for display when mouse is not hovering on the chart
-        if (!isHover.current) {
-          setDisplayNodes(newNodes);
-          setCurrentNode(newNode);
-        }
-      }
-    }
-  }, [value]);
-
-  return (
-    nodes.current.length ? (
-      <BaseCard title={title} content={`${Math.round(currentNode.value)}ms`} desc={new Date(currentNode.timestamp).toLocaleString()}>
-        <svg className={classes.root} onMouseEnter={() => isHover.current = true} onMouseLeave={() => isHover.current = false} width={FULL_WIDTH} height={FULL_HEIGHT} viewBox={`0 5 ${FULL_WIDTH} ${FULL_HEIGHT}`} fill="white" xmlns="http://www.w3.org/2000/svg">
-          {
-            displayNodes.map((node, index) => {
-              const x1 = FULL_WIDTH - (displayNodes.length - index + 1) * STEP;
-              const y1 = node.percent * .5 + ROUND * 2;
-
-              let line = null;
-              if (index < displayNodes.length - 1) {
-                const x2 = FULL_WIDTH - (displayNodes.length - index) * STEP;
-                const y2 = displayNodes[index + 1]['percent'] * .5 + ROUND * 2;
-                line = <line x1={x1} y1={y1} x2={x2} y2={y2} stroke="#06AFF2" />;
-              }
-              return (
-                <g key={`${node.value}${index}`}>
-                  {line}
-                  <g className={classes.ycoord} onMouseOver={() => { setCurrentNode(node) }}>
-                    <circle cx={x1} cy={y1} r={ROUND} fill="white" stroke="#06AFF2" />
-                    <rect opacity="0" x={x1 - ROUND} y={0} width={ROUND * 2} height={FULL_HEIGHT} fill="#E9E9ED" />
-                    <line opacity="0" x1={x1} y1={0} x2={x1} y2={FULL_WIDTH} strokeWidth="2" stroke="#06AFF2" strokeDasharray="2.5" />
-                  </g>
-                </g>
-              )
-            })
-          }
-        </svg>
-      </BaseCard >
-    ) : <BaseCard title={title} />
-  );
-};
-
-export default LineChartCard;

+ 0 - 86
client/src/pages/system/MiniTopology.tsx

@@ -1,86 +0,0 @@
-import { FC } from 'react';
-import { makeStyles, Theme } from '@material-ui/core';
-import { MiniTopoProps } from './Types';
-
-const getStyles = makeStyles((theme: Theme) => ({
-  container: {
-    height: '100%',
-    width: 'auto',
-  },
-  childNode: {
-    transition: 'all .25s',
-    cursor: 'pointer',
-    transformOrigin: '50% 50%',
-    transformBox: 'fill-box',
-
-    '& circle': {
-      transition: 'all .25s',
-    },
-
-    '& text': {
-      transition: 'all .25s',
-    },
-
-    '&:hover, &:focus': {
-      transform: 'scale(1.1)',
-      filter: 'drop-shadow(3px 3px 5px rgba(0, 0, 0, .2))',
-    },
-
-    '&:focus': {
-      outline: 'none',
-
-      '& svg path': {
-        fill: 'white',
-      },
-
-      '& circle': {
-        fill: '#06AFF2',
-        stroke: '#06AFF2',
-      },
-
-      '& text': {
-        fill: 'white',
-      }
-    }
-  },
-}));
-
-const capitalize = (s: string) => {
-  return s.charAt(0).toUpperCase() + s.slice(1);
-}
-
-const MiniTopo: FC<MiniTopoProps> = (props) => {
-  const classes = getStyles();
-  const { selectedCord, selectedChildNode, setCord } = props;
-
-  const WIDTH = 400;                // width for svg
-  const HEIGHT = 400;               // height for svg
-  const LINE = 80;                // line lenght from lv2 node
-  const ANGLE = 10;                // angle offset for lv2 node
-  const R1 = 45;                    // root node radius
-  const R2 = 30;                    // lv1 node radius
-  const W3 = 20;                    // width of child rect
-
-  const childNodeCenterX = WIDTH / 2 + LINE * Math.cos(ANGLE * Math.PI / 180);
-  const childNodeCenterY = HEIGHT / 2 + LINE * Math.sin(ANGLE * Math.PI / 180);
-
-  return (
-    <svg className={classes.container} width={WIDTH} height={HEIGHT} viewBox={`0 0 ${WIDTH} ${HEIGHT}`} xmlns="http://www.w3.org/2000/svg">
-      <rect width="100%" height="100%" fill="white" />
-      <line x1={`${WIDTH / 3}`} y1={`${HEIGHT / 3}`} x2={childNodeCenterX} y2={childNodeCenterY} stroke="#06AFF2" />
-      <g className={classes.childNode} onClick={() => { setCord(null) }}>
-        <circle cx={`${WIDTH / 3}`} cy={`${HEIGHT / 3}`} r={R1} fill="white" stroke="#06AFF2" />
-        <text fontFamily="Roboto" alignmentBaseline="middle" textAnchor="middle" fill="#06AFF2" fontWeight="700" fontSize="12" x={`${WIDTH / 3}`} y={`${HEIGHT / 3}`}>{selectedCord ? capitalize(selectedCord.infos?.name) : ''}</text>
-      </g>
-      <g>
-        <svg width="60" height="60" viewBox="0 0 60 60" x={childNodeCenterX - 30} y={childNodeCenterY - 30}>
-          <circle cx={R2} cy={R2} r={R2} fill="#06AFF2" stroke="white" />
-          <rect className="selected" x={R2 - W3 / 2} y={R2 - W3 / 2} width={W3} height={W3} fill="white" />
-        </svg>
-        <text fontFamily="Roboto" textAnchor="middle" fill="#82838E" fontSize="12" x={childNodeCenterX} y={childNodeCenterY + 50}>{`${selectedChildNode ? selectedChildNode.infos?.name : ''}`}</text>
-      </g>
-    </svg >
-  );
-};
-
-export default MiniTopo;

+ 0 - 151
client/src/pages/system/NodeListView.tsx

@@ -1,151 +0,0 @@
-import { FC, useState, useEffect } from 'react';
-import { useTranslation } from 'react-i18next';
-import { makeStyles, Theme } from '@material-ui/core';
-import KeyboardArrowDown from '@material-ui/icons/KeyboardArrowDown';
-import { DataGrid } from '@mui/x-data-grid';
-import { useNavigationHook } from '../../hooks/Navigation';
-import { ALL_ROUTER_TYPES } from '../../router/Types';
-import MiniTopo from './MiniTopology';
-import { getByteString } from '../../utils/Format';
-import DataCard from './DataCard';
-import { NodeListViewProps, Node } from './Types';
-
-const getStyles = makeStyles((theme: Theme) => ({
-  root: {
-    fontFamily: 'Roboto',
-    margin: '14px 40px',
-    display: 'grid',
-    gridTemplateColumns: 'auto 400px',
-    gridTemplateRows: '40px 400px auto',
-    gridTemplateAreas:
-      `"a a"
-       "b ."
-       "b d"`,
-    height: 'calc(100% - 28px)',
-  },
-  cardContainer: {
-    display: 'grid',
-    gap: '16px',
-    gridTemplateColumns: 'repeat(4, minmax(300px, 1fr))',
-  },
-  contentContainer: {
-    borderRadius: '8px',
-    boxShadow: '3px 3px 10px rgba(0, 0, 0, 0.05)',
-    display: 'grid',
-    marginTop: '14px',
-  },
-  childView: {
-    height: '100%',
-    width: '100%',
-    transition: 'all .25s',
-    position: 'absolute',
-    zIndex: 1000,
-    backgroundColor: 'white',
-  },
-  childCloseBtn: {
-    border: 0,
-    backgroundColor: 'white',
-    gridArea: 'a',
-    cursor: 'pointer',
-    width: '100%',
-  },
-  gridContainer: {
-    gridArea: 'b',
-    display: 'flex',
-  },
-  dataCard: {
-    gridArea: 'd',
-  }
-}));
-
-const NodeListView: FC<NodeListViewProps> = (props) => {
-  useNavigationHook(ALL_ROUTER_TYPES.SYSTEM);
-  const { t } = useTranslation('systemView');
-  const { t: commonTrans } = useTranslation();
-  const capacityTrans: { [key in string]: string } = commonTrans('capacity');
-
-  const classes = getStyles();
-  const [selectedChildNode, setSelectedChildNode] = useState<Node | undefined>();
-  const [rows, setRows] = useState<any[]>([]);
-  const { selectedCord, childNodes, setCord } = props;
-
-  let columns: any[] = [
-    {
-      field: 'name',
-      headerName: t('thName'),
-      flex: 1,
-    },
-    {
-      field: 'ip',
-      headerName: t('thIP'),
-      flex: 1,
-    },
-    {
-      field: 'cpuCore',
-      headerName: t('thCPUCount'),
-      flex: 1,
-    },
-    {
-      field: 'cpuUsage',
-      headerName: t('thCPUUsage'),
-      flex: 1,
-    },
-    {
-      field: 'diskUsage',
-      headerName: t('thDiskUsage'),
-      flex: 1,
-    },
-    {
-      field: 'memUsage',
-      headerName: t('thMemUsage'),
-      flex: 1,
-    },
-  ];
-
-  useEffect(() => {
-    if (selectedCord) {
-      const connectedIds = selectedCord.connected.map(node => node.connected_identifier);
-      const newRows: any[] = [];
-      childNodes.forEach(node => {
-        if (connectedIds.includes(node.identifier)) {
-          const dataRow = {
-            id: node?.identifier,
-            ip: node?.infos?.hardware_infos.ip,
-            cpuCore: node?.infos?.hardware_infos.cpu_core_count,
-            cpuUsage: node?.infos?.hardware_infos.cpu_core_usage,
-            diskUsage: getByteString(node?.infos?.hardware_infos.disk_usage, node?.infos?.hardware_infos.disk, capacityTrans),
-            memUsage: getByteString(node?.infos?.hardware_infos.memory_usage, node?.infos?.hardware_infos.memory, capacityTrans),
-            name: node?.infos?.name,
-          }
-          newRows.push(dataRow);
-        }
-      })
-      setRows(newRows);
-    }
-  }, [selectedCord, childNodes, capacityTrans]);
-
-  return (
-    <div className={classes.root}>
-      <button className={classes.childCloseBtn} onClick={() => setCord(null)}>
-        <KeyboardArrowDown />
-      </button>
-      <div className={classes.gridContainer}>
-        <DataGrid
-          rows={rows}
-          columns={columns}
-          hideFooterPagination
-          hideFooterSelectedRowCount
-          onRowClick={(rowData) => {
-            const selectedNode = childNodes.find(node => rowData.row.id === node.identifier);
-            setSelectedChildNode(selectedNode);
-          }}
-        />
-      </div>
-      <MiniTopo selectedCord={selectedCord} setCord={setCord} selectedChildNode={selectedChildNode} />
-      <DataCard className={classes.dataCard} node={selectedChildNode} />
-    </div>
-
-  );
-};
-
-export default NodeListView;

+ 0 - 30
client/src/pages/system/Progress.tsx

@@ -1,30 +0,0 @@
-
-import { FC } from 'react';
-import { makeStyles } from '@material-ui/core';
-import { ProgressProps } from './Types';
-
-const getStyles = makeStyles(() => ({
-  root: {
-    height: 'auto',
-    transform: 'scaleY(-1)',
-    width: '100%',
-
-    "& line": {
-      transformOrigin: '10px 15px',
-    },
-  },
-}));
-
-const Progress: FC<ProgressProps> = (props) => {
-  const classes = getStyles();
-  const { percent = 0, color = '#06F3AF' } = props;
-
-  return (
-    <svg className={classes.root} width="300" height="30" viewBox="0 0 300 30" fill="none" xmlns="http://www.w3.org/2000/svg">
-      <line x1={10} y1={15} x2={290} y2={15} vectorEffect="non-scaling-stroke" strokeWidth="12" stroke="#AEAEBB" strokeLinecap="round" />
-      <line x1={10} y1={15} x2={290} y2={15} vectorEffect="non-scaling-stroke" transform={`scale(${percent}, 1)`} strokeWidth="12" stroke={color} strokeLinecap="round" />
-    </svg >
-  );
-};
-
-export default Progress;

+ 0 - 28
client/src/pages/system/ProgressCard.tsx

@@ -1,28 +0,0 @@
-
-import { FC } from 'react';
-import { useTranslation } from 'react-i18next';
-import BaseCard from './BaseCard';
-import Progress from './Progress';
-import { getByteString } from '../../utils/Format';
-import { ProgressCardProps } from './Types';
-
-const color1 = '#06F3AF';
-const color2 = '#635DCE';
-
-const ProgressCard: FC<ProgressCardProps> = (props) => {
-  const { title, total, usage } = props;
-  const { t } = useTranslation('systemView');
-  const { t: commonTrans } = useTranslation();
-  const capacityTrans: { [key in string]: string } = commonTrans('capacity');
-
-  const color = title === t('diskTitle') ? color1 : color2;
-  const percent = (usage && total) ? (usage / total) : 0;
-
-  return (
-    <BaseCard title={title} content={`${getByteString(usage, total, capacityTrans)} (${Math.floor(percent * 100)}%)`}>
-      <Progress percent={percent} color={color} />
-    </BaseCard>
-  );
-};
-
-export default ProgressCard;

+ 0 - 154
client/src/pages/system/SystemView.tsx

@@ -1,154 +0,0 @@
-import { useState, useEffect, useRef } from 'react';
-import { useTranslation } from 'react-i18next';
-import { makeStyles, Theme } from '@material-ui/core';
-import clsx from 'clsx';
-import { useNavigationHook } from '../../hooks/Navigation';
-import { ALL_ROUTER_TYPES } from '../../router/Types';
-import { MilvusHttp } from '../../http/Milvus';
-import { useInterval } from '../../hooks/SystemView';
-import Topo from './Topology';
-import NodeListView from './NodeListView';
-import LineChartCard from './LineChartCard';
-import ProgressCard from './ProgressCard';
-import DataCard from './DataCard';
-
-const getStyles = makeStyles((theme: Theme) => ({
-  root: {
-    fontFamily: 'Roboto',
-    margin: '14px 40px',
-    position: 'relative',
-    height: 'calc(100vh - 80px)',
-    display: 'flex',
-    flexDirection: 'column',
-  },
-  cardContainer: {
-    display: 'grid',
-    gap: '16px',
-    gridTemplateColumns: 'repeat(4, minmax(300px, 1fr))',
-  },
-  transparent: {
-    opacity: 0,
-    transition: 'opacity .5s',
-  },
-  contentContainer: {
-    borderRadius: '8px',
-    boxShadow: '3px 3px 10px rgba(0, 0, 0, 0.05)',
-    display: 'grid',
-    gridTemplateColumns: '1fr auto',
-    marginTop: '14px',
-    height: '100%',
-  },
-  childView: {
-    height: '100%',
-    width: '100%',
-    transition: 'all .25s',
-    position: 'absolute',
-    zIndex: 1000,
-    backgroundColor: 'white',
-  },
-  showChildView: {
-    top: 0,
-    maxHeight: 'auto',
-  },
-  hideChildView: {
-    top: '1000px',
-    maxHeight: 0,
-  },
-  childCloseBtn: {
-    border: 0,
-    backgroundColor: 'white',
-    width: '100%',
-  }
-}));
-
-
-const parseJson = (jsonData: any) => {
-  const nodes: any[] = [];
-  const childNodes: any[] = [];
-
-  const system = {
-    // qps: Math.random() * 1000,
-    letency: Math.random() * 1000,
-    disk: 0,
-    diskUsage: 0,
-    memory: 0,
-    memoryUsage: 0,
-  }
-
-  jsonData?.response?.nodes_info.forEach((node: any) => {
-    const type = node?.infos?.type;
-    // coordinator node
-    if (type.includes("Coord")) {
-      nodes.push(node);
-      // other nodes
-    } else {
-      childNodes.push(node);
-    }
-
-    const info = node.infos.hardware_infos;
-    system.memory += info.memory;
-    system.memoryUsage += info.memory_usage;
-    system.disk += info.disk;
-    system.diskUsage += info.disk_usage;
-  });
-  return { nodes, childNodes, system };
-}
-
-
-const SystemView: any = () => {
-  useNavigationHook(ALL_ROUTER_TYPES.SYSTEM);
-  const { t } = useTranslation('systemView');
-
-  const classes = getStyles();
-  const INTERVAL = 10000;
-
-  const [data, setData] = useState<{ nodes: any, childNodes: any, system: any }>({ nodes: [], childNodes: [], system: {} });
-  const [selectedNode, setNode] = useState<any>();
-  const [selectedCord, setCord] = useState<any>();
-  const { nodes, childNodes, system } = data;
-
-  useInterval(async () => {
-    if (!selectedCord) {
-      const res = await MilvusHttp.getMetrics();
-      setData(parseJson(res));
-    }
-  }, INTERVAL);
-
-  useEffect(() => {
-    async function fetchData() {
-      const res = await MilvusHttp.getMetrics();
-      setData(parseJson(res));
-    }
-    fetchData();
-  }, []);
-
-  let qps = system?.qps || 0;
-  const letency = system?.letency || 0;
-  const childView = useRef<HTMLInputElement>(null);
-
-  return (
-    <div className={classes.root}>
-      <div className={clsx(classes.cardContainer, selectedCord && classes.transparent)}>
-        <ProgressCard title={t('diskTitle')} usage={system.diskUsage} total={system.disk} />
-        <ProgressCard title={t('memoryTitle')} usage={system.memoryUsage} total={system.memory} />
-        <LineChartCard title={t('qpsTitle')} value={qps} />
-        <LineChartCard title={t('letencyTitle')} value={letency} />
-      </div>
-      <div className={classes.contentContainer}>
-        <Topo nodes={nodes} setNode={setNode} setCord={setCord} />
-        <DataCard node={selectedNode} extend />
-      </div>
-
-      <div
-        ref={childView}
-        className={clsx(classes.childView,
-          selectedCord ? classes.showChildView : classes.hideChildView)}
-      >
-        {selectedCord && (<NodeListView selectedCord={selectedCord} childNodes={childNodes} setCord={setCord} />)}
-      </div>
-    </div >
-
-  );
-};
-
-export default SystemView;

File diff suppressed because it is too large
+ 0 - 178
client/src/pages/system/Topology.tsx


+ 0 - 71
client/src/pages/system/Types.ts

@@ -1,71 +0,0 @@
-import { ReactNode } from "react";
-
-export interface Node {
-  infos: {
-    hardware_infos: any,
-    system_info: any,
-    name: string,
-  },
-  connected: {
-    connected_identifier: number,
-  }[],
-  identifier: number,
-}
-
-export interface ProgressProps {
-  percent: number,
-  color: string,
-}
-
-export interface ProgressCardProps {
-  title: string,
-  total: number,
-  usage: number,
-}
-
-export interface BaseCardProps {
-  children?: ReactNode,
-  title: string,
-  content?: string,
-  desc?: string,
-}
-
-export interface LineChartCardProps {
-  title: string,
-  value: number,
-}
-
-export interface DataProgressProps {
-  percent: number,
-  desc?: string,
-}
-
-export interface DataSectionProps {
-  titles: string[],
-  contents: { label: string, value: string }[],
-}
-
-export interface DataCardProps {
-  node?: Node,
-  extend?: boolean
-}
-
-export interface LinceChartNode {
-  percent: number,
-  value: number,
-  timestamp: number,
-}
-
-type SetCord = (arg1: Node | null) => void;
-
-export interface MiniTopoProps {
-  selectedCord: Node,
-  selectedChildNode: Node | undefined,
-  setCord: SetCord,
-}
-
-export interface NodeListViewProps {
-  selectedCord: Node,
-  childNodes: Node[],
-  setCord: SetCord,
-}

+ 0 - 0
client/src/pages/seach/Constants.ts → client/src/plugins/search/Constants.ts


+ 0 - 0
client/src/pages/seach/SearchParams.tsx → client/src/plugins/search/SearchParams.tsx


+ 0 - 0
client/src/pages/seach/Styles.ts → client/src/plugins/search/Styles.ts


+ 2 - 2
client/src/pages/seach/Types.ts → client/src/plugins/search/Types.ts

@@ -1,7 +1,7 @@
 import { Option } from '../../components/customSelector/Types';
 import { searchKeywordsType } from '../../consts/Milvus';
-import { DataType, DataTypeEnum } from '../collections/Types';
-import { IndexView } from '../schema/Types';
+import { DataType, DataTypeEnum } from 'insight_src/pages/collections/Types';
+import { IndexView } from 'insight_src/pages/schema/Types';
 
 export interface SearchParamsProps {
   // if user created index, pass metric type choosed when creating

+ 1 - 1
client/src/pages/seach/VectorSearch.tsx → client/src/plugins/search/VectorSearch.tsx

@@ -16,7 +16,7 @@ import SimpleMenu from '../../components/menu/SimpleMenu';
 import { TOP_K_OPTIONS } from './Constants';
 import { Option } from '../../components/customSelector/Types';
 import { CollectionHttp } from '../../http/Collection';
-import { CollectionData, DataTypeEnum } from '../collections/Types';
+import { CollectionData, DataTypeEnum } from 'insight_src/pages/collections/Types';
 import { IndexHttp } from '../../http/Index';
 import { getVectorSearchStyles } from './Styles';
 import { parseValue } from '../../utils/Insert';

+ 13 - 0
client/src/plugins/search/config.json

@@ -0,0 +1,13 @@
+{
+  "name": "search",
+  "version": "0.1.0",
+  "client": {
+    "path": "search",
+    "entry": "VectorSearch.tsx",
+    "label": "Vector Search",
+    "iconName": "navSearch",
+    "auth": true,
+    "iconActiveClass": "activeSearchIcon",
+    "iconNormalClass": "normalSearchIcon"
+  }
+}

+ 6 - 6
client/src/router/Config.ts

@@ -2,7 +2,7 @@ import Collection from '../pages/collections/Collection';
 import Collections from '../pages/collections/Collections';
 import Connect from '../pages/connect/Connect';
 import Overview from '../pages/overview/Overview';
-import VectorSearch from '../pages/seach/VectorSearch';
+// import VectorSearch from '../pages/seach/VectorSearch';
 import { RouterConfigType } from './Types';
 import loadable from '@loadable/component';
 
@@ -29,11 +29,11 @@ const RouterConfig: RouterConfigType[] = [
     component: Collection,
     auth: true,
   },
-  {
-    path: '/search',
-    component: VectorSearch,
-    auth: true,
-  },
+  // {
+  //   path: '/search',
+  //   component: VectorSearch,
+  //   auth: true,
+  // },
 ];
 
 function importAll(r: any, outOfRoot = false) {

+ 3 - 1
client/src/router/Types.ts

@@ -8,7 +8,9 @@ export enum ALL_ROUTER_TYPES {
   // 'search'
   SEARCH = 'search',
   // 'system'
-  SYSTEM = 'system'
+  SYSTEM = 'system',
+  // plugins
+  PLUGIN = 'plugin',
 }
 
 export type NavInfo = {

+ 70 - 0
client/src/types/SearchTypes.ts

@@ -0,0 +1,70 @@
+import { Option } from '../components/customSelector/Types';
+import { searchKeywordsType } from '../consts/Milvus';
+import { DataType, DataTypeEnum } from '../pages/collections/Types';
+import { IndexView } from '../pages/schema/Types';
+
+export interface SearchParamsProps {
+  // if user created index, pass metric type choosed when creating
+  // else pass empty string
+  metricType: string;
+  // used for getting metric type options
+  embeddingType: DataTypeEnum.FloatVector | DataTypeEnum.BinaryVector;
+  // default index type is FLAT
+  indexType: string;
+  // index extra params, e.g. nlist
+  indexParams: { key: string; value: string }[];
+  searchParamsForm: {
+    [key in string]: number;
+  };
+  topK: number;
+  handleFormChange: (form: { [key in string]: number }) => void;
+  wrapperClass?: string;
+  setParamsDisabled: (isDisabled: boolean) => void;
+}
+
+export interface SearchResultView {
+  // dynamic field names
+  [key: string]: string | number;
+  rank: number;
+  distance: number;
+}
+
+export interface FieldOption extends Option {
+  fieldType: DataType;
+  // used to get metric type, index type and index params for search params
+  // if user doesn't create index, default value is null
+  indexInfo: IndexView | null;
+  // used for check vector input validation
+  dimension: number;
+}
+
+export interface SearchParamInputConfig {
+  label: string;
+  key: searchKeywordsType;
+  min: number;
+  max: number;
+  isInt?: boolean;
+  // no value: empty string
+  value: number | string;
+  handleChange: (value: number) => void;
+  className?: string;
+}
+
+export interface VectorSearchParam {
+  expr?: string;
+  search_params: {
+    anns_field: string; // your vector field name
+    topk: string | number;
+    metric_type: string;
+    params: string;
+  };
+  vectors: any;
+  output_fields: string[];
+  vector_type: number | DataTypeEnum;
+}
+
+export interface SearchResult {
+  // dynamic field names
+  [key: string]: string | number;
+  score: number;
+}

+ 1 - 1
client/src/utils/search.ts

@@ -10,7 +10,7 @@ import {
   FieldOption,
   SearchResult,
   SearchResultView,
-} from '../pages/seach/Types';
+} from '../types/SearchTypes';
 
 /**
  * Do not change  vector search result default sort  by ourself.

+ 2 - 1
client/tsconfig.paths.json

@@ -2,7 +2,8 @@
   "compilerOptions": {
     "baseUrl": ".",
     "paths": {
-      "all_plugins/*": ["src/plugins"]
+      "all_plugins/*": ["src/plugins"],
+      "insight_src/*": ["src/*"]
     }
   }
 }

Some files were not shown because too many files changed in this diff