Browse Source

Merge branch 'main' of github.com:zilliztech/attu into fix-auth-login

nameczz 2 years ago
parent
commit
68c78a38bd
88 changed files with 949 additions and 936 deletions
  1. BIN
      .github/images/create_collection.png
  2. BIN
      .github/images/create_index.png
  3. BIN
      .github/images/data_preview.png
  4. BIN
      .github/images/query_advanced_filter.png
  5. BIN
      .github/images/system_view.png
  6. BIN
      .github/images/vector_search.png
  7. 3 56
      .github/workflows/dev.yml
  8. 48 33
      README.md
  9. 6 6
      client/package.json
  10. 5 1
      client/src/components/customDialog/DialogTemplate.tsx
  11. 2 0
      client/src/components/customDialog/Types.ts
  12. 11 53
      client/src/components/customInput/SearchInput.tsx
  13. 2 0
      client/src/components/customInput/Types.ts
  14. 2 1
      client/src/components/customSelector/CustomSelector.tsx
  15. 2 0
      client/src/components/customSelector/Types.ts
  16. 1 3
      client/src/components/customToolTip/CustomToolTip.tsx
  17. 2 0
      client/src/components/icons/Icons.tsx
  18. 1 0
      client/src/components/icons/Types.ts
  19. 3 3
      client/src/components/layout/Header.tsx
  20. 0 152
      client/src/components/layout/Layout.tsx
  21. 1 2
      client/src/components/menu/Types.ts
  22. 2 2
      client/src/consts/Milvus.tsx
  23. 12 1
      client/src/http/Collection.ts
  24. 15 10
      client/src/i18n/cn/button.ts
  25. 25 11
      client/src/i18n/cn/collection.ts
  26. 1 2
      client/src/i18n/cn/common.ts
  27. 2 3
      client/src/i18n/cn/dialog.ts
  28. 3 2
      client/src/i18n/cn/index.ts
  29. 5 0
      client/src/i18n/cn/insert.ts
  30. 3 3
      client/src/i18n/cn/partition.ts
  31. 1 2
      client/src/i18n/cn/search.ts
  32. 2 1
      client/src/i18n/cn/success.ts
  33. 1 1
      client/src/i18n/cn/user.ts
  34. 2 1
      client/src/i18n/cn/warning.ts
  35. 1 0
      client/src/i18n/en/button.ts
  36. 23 9
      client/src/i18n/en/collection.ts
  37. 1 1
      client/src/i18n/en/common.ts
  38. 1 1
      client/src/i18n/en/dialog.ts
  39. 3 3
      client/src/i18n/en/partition.ts
  40. 1 1
      client/src/i18n/en/success.ts
  41. 2 1
      client/src/i18n/en/warning.ts
  42. 4 8
      client/src/index.tsx
  43. 132 0
      client/src/pages/collections/Aliases.tsx
  44. 3 3
      client/src/pages/collections/Collection.tsx
  45. 77 78
      client/src/pages/collections/Collections.tsx
  46. 22 10
      client/src/pages/collections/Create.tsx
  47. 24 11
      client/src/pages/collections/CreateAlias.tsx
  48. 103 77
      client/src/pages/collections/CreateFields.tsx
  49. 8 0
      client/src/pages/collections/Types.ts
  50. 4 4
      client/src/pages/connect/AuthForm.tsx
  51. 11 3
      client/src/pages/connect/Connect.tsx
  52. 1 1
      client/src/pages/connect/ConnectContainer.tsx
  53. 135 0
      client/src/pages/index.tsx
  54. 7 6
      client/src/pages/overview/collectionCard/CollectionCard.tsx
  55. 42 62
      client/src/pages/partitions/Partitions.tsx
  56. 7 9
      client/src/pages/preview/Preview.tsx
  57. 2 6
      client/src/pages/query/Query.tsx
  58. 1 0
      client/src/pages/schema/Create.tsx
  59. 1 0
      client/src/pages/schema/CreateForm.tsx
  60. 1 1
      client/src/pages/schema/IndexTypeElement.tsx
  61. 0 0
      client/src/pages/search/Constants.ts
  62. 5 6
      client/src/pages/search/SearchParams.tsx
  63. 0 0
      client/src/pages/search/Styles.ts
  64. 2 2
      client/src/pages/search/Types.ts
  65. 5 2
      client/src/pages/search/VectorSearch.tsx
  66. 0 0
      client/src/pages/system/BaseCard.tsx
  67. 0 0
      client/src/pages/system/DataCard.tsx
  68. 0 0
      client/src/pages/system/LineChartCard.tsx
  69. 0 0
      client/src/pages/system/MiniTopology.tsx
  70. 0 0
      client/src/pages/system/NodeListView.tsx
  71. 0 0
      client/src/pages/system/Progress.tsx
  72. 0 0
      client/src/pages/system/ProgressCard.tsx
  73. 0 0
      client/src/pages/system/SystemView.tsx
  74. 0 0
      client/src/pages/system/Topology.tsx
  75. 0 0
      client/src/pages/system/Types.ts
  76. 0 13
      client/src/plugins/search/config.json
  77. 0 11
      client/src/plugins/system/config.json
  78. 0 58
      client/src/router/Config.ts
  79. 40 51
      client/src/router/Router.tsx
  80. 0 6
      client/src/router/Types.ts
  81. 5 1
      client/src/utils/Common.ts
  82. 63 126
      client/yarn.lock
  83. 2 2
      server/package.json
  84. 11 0
      server/src/collections/collections.controller.ts
  85. 1 0
      server/src/collections/collections.service.ts
  86. 3 1
      server/src/collections/dto.ts
  87. 4 0
      server/src/utils/Helper.ts
  88. 28 13
      server/yarn.lock

BIN
.github/images/create_collection.png


BIN
.github/images/create_index.png


BIN
.github/images/data_preview.png


BIN
.github/images/query_advanced_filter.png


BIN
.github/images/system_view.png


BIN
.github/images/vector_search.png


+ 3 - 56
.github/workflows/dev.yml

@@ -4,11 +4,13 @@ on:
   pull_request_target:
     branches: [main]
     types: [closed]
+  push:
+    branches:
+      - main
 
 jobs:
   build:
     runs-on: ubuntu-latest
-    if: github.event.pull_request.merged == true
     steps:
       - uses: actions/checkout@v2
       - name: Setup Node.js
@@ -41,58 +43,3 @@ jobs:
 
       - name: Docker Push Dev
         run: docker push zilliz/attu:dev
-
-  k8s:
-    runs-on: ubuntu-latest
-    needs: build
-    steps:
-      - uses: actions/checkout@v2
-      - name: Setup Node.js
-        uses: actions/setup-node@v1
-        with:
-          node-version: 12
-
-      - name: Install OpenVPN and kubectl
-        run: |
-          sudo apt-get update
-          sudo apt-get install openvpn -y
-          sudo apt-get install -y apt-transport-https ca-certificates curl
-          sudo curl -fsSLo /usr/share/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg
-          echo "deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list
-          sudo apt-get update
-          sudo apt-get install kubectl -y
-
-      - name: Connect VPN
-        uses: golfzaptw/action-connect-ovpn@master
-        id: connect_vpn
-        with:
-          FILE_OVPN: ".github/workflows/client.ovpn"
-        env:
-          CA_CRT: ${{ secrets.VPN_CA}}
-          USER_CRT: ${{ secrets.VPN_CRT }}
-          USER_KEY: ${{ secrets.VPN_KEY }}
-
-      - name: Deploy to cluster
-        run: |
-          echo ${{ secrets.kubeconfig }} > config64
-          base64 -d config64 > kubeconfig
-          kubectl delete pods -n ued -l app=attu --kubeconfig=kubeconfig
-          sleep 60
-
-  check:
-    runs-on: ubuntu-latest
-    needs: [build, k8s]
-    steps:
-      - uses: actions/checkout@v2
-      - name: Setup Node.js
-        uses: actions/setup-node@v1
-        with:
-          node-version: 12
-
-      - name: Check insight status
-        env:
-          INSIGHT_URL: ${{ secrets.INSIGHT_URL }}
-        run: |
-          yarn add axios
-          yarn add @actions/core
-          node checkInsight.js

+ 48 - 33
README.md

@@ -4,47 +4,50 @@
 [![downloads](https://img.shields.io/docker/pulls/zilliz/attu)](https://img.shields.io/docker/pulls/zilliz/attu)
 [![codecov](https://codecov.io/gh/zilliztech/attu/branch/main/graph/badge.svg?token=jvIEVF9IwW)](https://codecov.io/gh/zilliztech/attu)
 
-Attu provides an intuitive and efficient GUI for Milvus, allowing you to interact with your databases and manage your data with just few clicks.
+Attu is an all-in-one milvus administration tool. With Attu, you can dramatically reduce the cost of managing milvus.
 
-<img src="./.github/images/screenshot.png" alt="attu" />
+<img src="./.github/images/screenshot.png" alt="attu" width="800" alt="attu" />
 
-## Attu Doc
-
-- [English](./doc/en.md)
-- [中文](./doc/zh-cn.md)
-
-## Features and Roadmap
-
-Attu is under rapid development and we are adding new features weekly, here are the current plan, we will release a version once a feature is available.
+## Features
 
+- Basic dashboard
+  - View basic collection statistics
+  - Quick search from loaded collection
+  - Quick release loaded collection
 - Manage collections/partitions
-- Manage index
-- Basic statistics overview
-- Load/release collections for search
-- Insert entities
-- Vector search with advanced filter
+  - Create collection/partion
+  - Delete collection/partions
+  - View collection schema
+  - Create/drop index with parameters
+  - Load/release collections for search
+- Data Management
+  - Insert entities
+  - Data preview
+  - Data query
+- Vector search/query with advanced filter
 - System view
-- Data query
-- View root node configuration
-- Support TLS connection and username,password
-- Vector Visualization(TBD)
-- More...
+  - View milvus nodes system info
+  - View milvus nodes configuration
+- Manage Milvus user
+- More are comming...
 
 ## Quick start
 
-### Before you start
+> If you prefer desktop application, you can download the [desktop version of Attu](https://github.com/zilliztech/attu/releases/).
 
-Ensure you have Milvus installed on [your server](https://milvus.io/docs/install_standalone-docker.md) or [cluster](https://milvus.io/docs/install_cluster-docker.md), and attu only supports Milvus 2.x.
+## Run attu from docker
 
-### ⭐️ Start a attu instance
+> Ensure you have Milvus installed on [your server](https://milvus.io/docs/install_standalone-docker.md) or [cluster](https://milvus.io/docs/install_cluster-docker.md), and attu only supports Milvus 2.x.
 
-**_ Before attu v2.1.0 , [check here](https://github.com/zilliztech/attu/tree/v2.0.5) _**
+> _ Before attu v2.1.0 , [check here](https://github.com/zilliztech/attu/tree/v2.0.5) _\*\*
+
+### ✈️ Start a attu container
 
 ```code
 docker run -p 8000:3000 -e MILVUS_URL={milvus server ip}:19530 zilliz/attu:latest
 ```
 
-Once you start the docker, open the browser, type `http://{ attu ip }:8000`, you can view the attu.
+Once you start the container, open the browser, type `http://{ attu ip }:8000`, you can view the attu GUI.
 
 #### Params
 
@@ -54,15 +57,24 @@ Once you start the docker, open the browser, type `http://{ attu ip }:8000`, you
 
 Tip: **127.0.0.1 or localhost will not work when runs on docker**
 
-#### Try the dev build
+## Try the dev build
 
-**_note_** We plan to release attu once a feature is done. Also, if you want to try the nightly build, please pull the docker image with the `dev` tag.
+> We plan to release attu once a feature is done. Also, if you want to try the nightly build, please pull the docker image with the `dev` tag.
 
 ```code
 docker run -p 8000:3000 -e MILVUS_URL={ your machine IP }:19530 zilliz/attu:dev
 ```
 
-## ✨ Building and Running attu, and/or Contributing Code
+## Screenshots
+
+<img src="./.github/images/screenshot.png" alt="attu" width="800" alt="attu" />
+<img src="./.github/images/create_collection.png" width="800" alt="attu" />
+<img src="./.github/images/create_index.png" width="800" alt="attu" />
+<img src="./.github/images/data_preview.png" width="800" alt="attu" />
+<img src="./.github/images/query_advanced_filter.png" width="800" alt="attu" />
+<img src="./.github/images/vector_search.png" width="800" alt="attu" />
+
+## ✨ Contributing Code
 
 You might want to build Attu locally to contribute some code, test out the latest features, or try
 out an open PR:
@@ -83,20 +95,23 @@ out an open PR:
 
 ### Milvus
 
-New to milvus? Milvus is an open-source vector database built to power AI applications and embedding similarity search.
+New to milvus? [Milvus](https://milvus.io) is an open-source vector database built to power AI applications and embedding similarity search.
 
 ### Userful links
 
-- [Milvus installation guide](https://milvus.io/docs/v2.0.0/install_standalone-docker.md)
-- [Milvus python sdk](https://milvus.io/docs/v2.0.0/explore_pymilvus.md)
-- [Milvus bootcamp](https://milvus.io/bootcamp)
+- [Milvus docs](https://milvus.io/docs)
+- [Milvus python sdk](https://github.com/milvus-io/pymilvus)
+- [Milvus java sdk](https://github.com/milvus-io/milvus-sdk-java)
+- [Milvus gp sdk](https://github.com/milvus-io/milvus-sdk-go)
+- [Milvus node sdk](https://github.com/milvus-io/milvus-sdk-node)
+- [Feder](https://github.com/zilliztech/feder)
 
 #### ❓ Questions? Problems?
 
 - If you've found a bug or want to request a feature, please create a [GitHub Issue](https://github.com/zilliztech/attu/issues/new/choose).
   Please check to make sure someone else hasn't already created an issue for the same topic.
 
-[milvus-doc]: https://milvus.io/docs/home
+[milvus-doc]: https://milvus.io/docs
 
 ## Community
 

+ 6 - 6
client/package.json

@@ -18,11 +18,11 @@
     "file-saver": "^2.0.5",
     "i18next": "^20.3.1",
     "papaparse": "^5.3.1",
-    "react": "^17.0.2",
-    "react-dom": "^17.0.2",
+    "react": "^18.2.0",
+    "react-dom": "^18.2.0",
     "react-highlight-words": "^0.17.0",
     "react-i18next": "^12.0.0",
-    "react-router-dom": "^5.2.0",
+    "react-router-dom": "^6.4.3",
     "react-syntax-highlighter": "^15.4.4",
     "socket.io-client": "^4.1.3",
     "typescript": "^4.1.2",
@@ -41,10 +41,10 @@
     "@types/loadable__component": "^5.13.4",
     "@types/node": "^12.0.0",
     "@types/papaparse": "^5.2.6",
-    "@types/react": "^17.0.0",
-    "@types/react-dom": "^17.0.0",
+    "@types/react": "^18.0.25",
+    "@types/react-dom": "^18.0.8",
     "@types/react-highlight-words": "^0.16.2",
-    "@types/react-router-dom": "^5.1.7",
+    "@types/react-router-dom": "^5.3.3",
     "@types/react-syntax-highlighter": "^13.5.2",
     "@types/webpack-env": "^1.16.3",
     "@vitest/coverage-c8": "^0.25.0",

+ 5 - 1
client/src/components/customDialog/DialogTemplate.tsx

@@ -54,6 +54,7 @@ const DialogTemplate: FC<DialogContainerProps> = ({
   // needed for code mode
   showCode = false,
   codeBlocksData = [],
+  dialogClass = '',
 }) => {
   const { t } = useTranslation('btn');
   const cancel = cancelLabel || t('cancel');
@@ -77,7 +78,10 @@ const DialogTemplate: FC<DialogContainerProps> = ({
 
   return (
     <section className={classes.wrapper}>
-      <div ref={dialogRef} className={`${classes.dialog} ${classes.block}`}>
+      <div
+        ref={dialogRef}
+        className={`${classes.dialog} ${classes.block} ${dialogClass}`}
+      >
         <CustomDialogTitle onClose={handleClose} showCloseIcon={showCloseIcon}>
           {title}
         </CustomDialogTitle>

+ 2 - 0
client/src/components/customDialog/Types.ts

@@ -36,4 +36,6 @@ export type DialogContainerProps = {
   // code mode requirement
   showCode?: boolean;
   codeBlocksData?: CodeViewData[];
+  children: ReactElement;
+  dialogClass?: string;
 };

+ 11 - 53
client/src/components/customInput/SearchInput.tsx

@@ -1,7 +1,7 @@
 import { InputAdornment, makeStyles, TextField } from '@material-ui/core';
 import { useRef, FC, useState, useEffect, useMemo } from 'react';
 import { useTranslation } from 'react-i18next';
-import { useHistory } from 'react-router-dom';
+import { useSearchParams } from 'react-router-dom';
 import Icons from '../icons/Icons';
 import { SearchType } from './Types';
 
@@ -75,64 +75,24 @@ const useSearchStyles = makeStyles(theme => ({
   },
 }));
 
-let timer: NodeJS.Timeout | null = null;
-
 const SearchInput: FC<SearchType> = props => {
   const { searchText = '', onClear = () => {}, onSearch = () => {} } = props;
-  const [searchValue, setSearchValue] = useState<string | null>(
-    searchText || null
-  );
-
-  const [isInit, setIsInit] = useState<boolean>(true);
-
-  const searched = useMemo(
-    () => searchValue !== '' && searchValue !== null,
-    [searchValue]
-  );
-
+  const [searchParams, setSearchParams] = useSearchParams();
+  const [searchValue, setSearchValue] = useState<string>(searchText || '');
+  const searched = useMemo(() => searchValue !== '', [searchValue]);
   const classes = useSearchStyles({ searched });
   const { t: commonTrans } = useTranslation();
-
-  const history = useHistory();
-
   const inputRef = useRef<any>(null);
 
-  const savedSearchFn = useRef<(value: string) => void>(() => {});
-  useEffect(() => {
-    savedSearchFn.current = onSearch;
-  }, [onSearch]);
+  const handleSearch = (value: string) => {
+    onSearch(value);
+  };
 
   useEffect(() => {
-    if (timer) {
-      clearTimeout(timer);
-    }
-    if (searchValue !== null && !isInit) {
-      timer = setTimeout(() => {
-        // save other params data and remove last time search info
-        const location = history.location;
-        const params = new URLSearchParams(location.search);
-        params.delete('search');
-
-        if (searchValue) {
-          params.append('search', searchValue);
-        }
-        // add search value in url
-        history.push({ search: params.toString() });
-
-        savedSearchFn.current(searchValue);
-      }, 300);
-    }
-
-    return () => {
-      timer && clearTimeout(timer);
-    };
-  }, [searchValue, history, isInit]);
-
-  const handleSearch = (value: string | null) => {
-    if (value !== null) {
-      onSearch(value);
-    }
-  };
+    searchParams[searchValue ? 'set' : 'delete']('search', searchValue);
+    setSearchParams(searchParams);
+    handleSearch(searchValue);
+  }, [searchValue]);
 
   return (
     <div className={classes.wrapper}>
@@ -150,7 +110,6 @@ const SearchInput: FC<SearchType> = props => {
                 className={`flex-center ${classes.iconWrapper}`}
                 onClick={e => {
                   setSearchValue('');
-                  setIsInit(false);
                   inputRef.current.focus();
                   onClear();
                 }}
@@ -173,7 +132,6 @@ const SearchInput: FC<SearchType> = props => {
         onChange={e => {
           const value = e.target.value.trim();
           setSearchValue(value);
-          setIsInit(false);
           if (value === '') {
             onClear();
           }

+ 2 - 0
client/src/components/customInput/Types.ts

@@ -1,4 +1,5 @@
 import { ReactElement } from 'react';
+import { InputLabelProps } from '@material-ui/core';
 import { IValidationItem } from '../../hooks/Form';
 import { IExtraParam, ValidType } from '../../utils/Validation';
 
@@ -78,6 +79,7 @@ export interface ITextfieldConfig {
   type?: string;
   onBlur?: (event: any) => void;
   onChange?: (event: any) => void;
+  InputLabelProps?: Partial<InputLabelProps>;
 }
 
 export interface IAdornmentConfig {

+ 2 - 1
client/src/components/customSelector/CustomSelector.tsx

@@ -16,12 +16,13 @@ const CustomSelector: FC<CustomSelectorType> = props => {
     variant,
     wrapperClass = '',
     labelClass = '',
+    size = 'medium',
     ...others
   } = props;
   const id = generateId('selector');
 
   return (
-    <FormControl variant={variant} className={wrapperClass}>
+    <FormControl variant={variant} className={wrapperClass} size={size}>
       {label && (
         <InputLabel classes={{ root: labelClass }} htmlFor={id}>
           {label}

+ 2 - 0
client/src/components/customSelector/Types.ts

@@ -20,6 +20,8 @@ export type CustomSelectorType = SelectProps & {
   variant?: 'filled' | 'outlined' | 'standard';
   labelClass?: string;
   wrapperClass?: string;
+  hiddenLabel?: boolean;
+  size?: 'small' | 'medium' | undefined;
 };
 
 export interface ICustomGroupSelect {

+ 1 - 3
client/src/components/customToolTip/CustomToolTip.tsx

@@ -5,9 +5,7 @@ import { makeStyles, Theme, createStyles } from '@material-ui/core';
 
 const useStyles = makeStyles((theme: Theme) =>
   createStyles({
-    tooltip: {
-      textTransform: 'capitalize',
-    },
+    tooltip: {},
   })
 );
 

+ 2 - 0
client/src/components/icons/Icons.tsx

@@ -2,6 +2,7 @@ import React from 'react';
 import { IconsType } from './Types';
 import SearchIcon from '@material-ui/icons/Search';
 import AddIcon from '@material-ui/icons/Add';
+import AddCircleOutlineIcon from '@material-ui/icons/AddCircleOutlineOutlined';
 import DeleteIcon from '@material-ui/icons/Delete';
 import FileCopyIcon from '@material-ui/icons/FileCopy';
 import Visibility from '@material-ui/icons/Visibility';
@@ -44,6 +45,7 @@ import { ReactComponent as SystemIcon } from '../../assets/icons/system.svg';
 const icons: { [x in IconsType]: (props?: any) => React.ReactElement } = {
   search: (props = {}) => <SearchIcon {...props} />,
   add: (props = {}) => <AddIcon {...props} />,
+  addOutline:  (props = {}) => <AddCircleOutlineIcon {...props} />,
   delete: (props = {}) => <DeleteIcon {...props} />,
   list: (props = {}) => <ReorderIcon {...props} />,
   copy: (props = {}) => <FileCopyIcon {...props} />,

+ 1 - 0
client/src/components/icons/Types.ts

@@ -1,6 +1,7 @@
 export type IconsType =
   | 'search'
   | 'add'
+  | 'addOutline'
   | 'delete'
   | 'list'
   | 'copy'

+ 3 - 3
client/src/components/layout/Header.tsx

@@ -3,7 +3,7 @@ import { makeStyles, Theme, createStyles, Typography } from '@material-ui/core';
 import { HeaderType } from './Types';
 import { navContext } from '../../context/Navigation';
 import icons from '../icons/Icons';
-import { useHistory } from 'react-router-dom';
+import { useNavigate } from 'react-router-dom';
 import { authContext } from '../../context/Auth';
 import { useTranslation } from 'react-i18next';
 import { MILVUS_ADDRESS } from '../../consts/Localstorage';
@@ -60,14 +60,14 @@ const Header: FC<HeaderType> = props => {
   const classes = useStyles();
   const { navInfo } = useContext(navContext);
   const { address, setAddress, setIsAuth } = useContext(authContext);
-  const history = useHistory();
+  const navigate = useNavigate();
   const { t: commonTrans } = useTranslation();
   const statusTrans = commonTrans('status');
   const BackIcon = icons.back;
   const LogoutIcon = icons.logout;
 
   const handleBack = (path: string) => {
-    history.push(path);
+    navigate(path);
   };
 
   const handleLogout = () => {

+ 0 - 152
client/src/components/layout/Layout.tsx

@@ -1,152 +0,0 @@
-import GlobalEffect from './GlobalEffect';
-import Header from './Header';
-import { makeStyles, Theme, createStyles } from '@material-ui/core';
-import NavMenu from '../menu/NavMenu';
-import { NavMenuItem } from '../menu/Types';
-import { useContext, useMemo } from 'react';
-import icons from '../icons/Icons';
-import { useTranslation } from 'react-i18next';
-import { useHistory, useLocation } from 'react-router-dom';
-import { authContext } from '../../context/Auth';
-import { rootContext } from '../../context/Root';
-import { IconsType } from '../icons/Types';
-
-const useStyles = makeStyles((theme: Theme) =>
-  createStyles({
-    root: {
-      minHeight: '100vh',
-      backgroundColor: '#f5f5f5',
-    },
-    content: {
-      display: 'flex',
-
-      '& .normalSearchIcon': {
-        '& path': {
-          fill: theme.palette.attuGrey.dark,
-        },
-      },
-
-      '& .activeSearchIcon': {
-        '& path': {
-          fill: theme.palette.primary.main,
-        },
-      },
-    },
-    body: {
-      flex: 1,
-      display: 'flex',
-      flexDirection: 'column',
-      height: '100vh',
-      overflowY: 'scroll',
-    },
-  })
-);
-
-const Layout = (props: any) => {
-  const history = useHistory();
-  const { isAuth } = useContext(authContext);
-  const { versionInfo } = useContext(rootContext);
-
-  const { t: navTrans } = useTranslation('nav');
-  const classes = useStyles();
-  const location = useLocation();
-  const defaultActive = useMemo(() => {
-    if (location.pathname.includes('collection')) {
-      return navTrans('collection');
-    }
-
-    if (location.pathname.includes('search')) {
-      return navTrans('search');
-    }
-
-    if (location.pathname.includes('system')) {
-      return navTrans('system');
-    }
-
-    if (location.pathname.includes('users')) {
-      return navTrans('user');
-    }
-
-    return navTrans('overview');
-  }, [location, navTrans]);
-
-  const menuItems: NavMenuItem[] = [
-    {
-      icon: icons.navOverview,
-      label: navTrans('overview'),
-      onClick: () => history.push('/'),
-    },
-    {
-      icon: icons.navCollection,
-      label: navTrans('collection'),
-      onClick: () => history.push('/collections'),
-    },
-    {
-      icon: icons.navPerson,
-      label: navTrans('user'),
-      onClick: () => history.push('/users'),
-    },
-    // {
-    //   icon: icons.navSearch,
-    //   label: navTrans('search'),
-    //   onClick: () => history.push('/search'),
-    //   iconActiveClass: 'activeSearchIcon',
-    //   iconNormalClass: 'normalSearchIcon',
-    // },
-  ];
-
-  function importAll(r: any) {
-    Object.keys(r).forEach((key: any) => {
-      const content = r[key];
-      const pathName = content.client?.path;
-
-      if (!pathName) return;
-      const result: NavMenuItem = {
-        icon: icons.navOverview,
-        label: content.client?.label || 'PLGUIN',
-      };
-      result.onClick = () => history.push(`/${pathName}`);
-      const iconName: IconsType = content.client?.iconName;
-      if (iconName) {
-        result.icon = icons[iconName];
-      }
-      content.client?.iconActiveClass &&
-        (result.iconActiveClass = content.client?.iconActiveClass);
-      content.client?.iconNormalClass &&
-        (result.iconNormalClass = content.client?.iconNormalClass);
-
-      menuItems.push(result);
-    });
-  }
-  const pluginConfigs = import.meta.glob(`../../plugins/**/config.json`, {
-    eager: true,
-  });
-
-  importAll(pluginConfigs);
-
-  return (
-    <div className={classes.root}>
-      <GlobalEffect>
-        <div className={classes.content}>
-          {isAuth && (
-            <NavMenu
-              width="200px"
-              data={menuItems}
-              defaultActive={defaultActive}
-              // used for nested child menu
-              defaultOpen={{ [navTrans('overview')]: true }}
-              versionInfo={versionInfo}
-            />
-          )}
-
-          <div className={classes.body}>
-            {isAuth && <Header />}
-            {props.children}
-          </div>
-        </div>
-      </GlobalEffect>
-    </div>
-  );
-};
-
-export default Layout;

+ 1 - 2
client/src/components/menu/Types.ts

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

+ 2 - 2
client/src/consts/Milvus.tsx

@@ -86,7 +86,7 @@ export const FLOAT_INDEX_CONFIG: indexConfigType = {
   //   search: ['nprobe'],
   // },
   FLAT: {
-    create: [''],
+    create: [],
     search: ['nprobe'],
   },
   HNSW: {
@@ -107,7 +107,7 @@ export const BINARY_INDEX_CONFIG: indexConfigType = {
   // },
   BIN_FLAT: {
     create: ['nlist'],
-    search: ['nprobe'],
+    search: [],
   },
   BIN_IVF_FLAT: {
     create: ['nlist'],

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

@@ -3,7 +3,7 @@ import {
   CollectionView,
   DeleteEntitiesReq,
   InsertDataParam,
-  LoadSampleParam
+  LoadSampleParam,
 } from '../pages/collections/Types';
 import { Field } from '../pages/schema/Types';
 import { VectorSearchParam } from '../types/SearchTypes';
@@ -16,6 +16,7 @@ import dayjs from 'dayjs';
 import { LOADING_STATE } from '../consts/Milvus';
 
 export class CollectionHttp extends BaseModel implements CollectionView {
+  private aliases!: string[];
   private autoID!: string;
   private collection_name!: string;
   private description!: string;
@@ -117,6 +118,12 @@ export class CollectionHttp extends BaseModel implements CollectionView {
     });
   }
 
+  static dropAlias(collectionName: string, params: { alias: string }) {
+    return super.delete({
+      path: `${this.COLLECTIONS_URL}/${collectionName}/alias/${params.alias}`,
+    });
+  }
+
   static queryData(collectionName: string, params: QueryParam) {
     return super.query({
       path: `${this.COLLECTIONS_URL}/${collectionName}/query`,
@@ -128,6 +135,10 @@ export class CollectionHttp extends BaseModel implements CollectionView {
     return this.autoID;
   }
 
+  get _aliases() {
+    return this.aliases;
+  }
+
   get _desc() {
     return this.description || '--';
   }

+ 15 - 10
client/src/i18n/cn/button.ts

@@ -1,22 +1,27 @@
 const btnTrans = {
-  cancel: '取消',
-  save: '保存',
-  reset: '重置',
-  update: '更新',
-  search: '搜索',
-  confirm: '确认',
-  connect: '连接',
-  import: '导入',
-  delete: '删除',
-  release: 'Release',
+  cancel: 'Cancel',
+  save: 'Save',
   create: 'Create',
+  reset: 'Reset',
+  update: 'Update',
+  search: 'Search',
+  confirm: 'Confirm',
+  connect: 'Connect',
+  import: 'Import',
+  delete: 'Delete',
+  drop: 'Drop',
+  release: 'Release',
   load: 'Load',
   insert: 'Import Data',
+  refresh: 'Refresh',
   next: 'Next',
   previous: 'Previous',
   done: 'Done',
   vectorSearch: 'Vector search',
   query: 'Query',
+  importSampleData: 'Import Sample data',
+  loading: 'Loading...',
+  importing: 'Importing...'
 };
 
 export default btnTrans;

+ 25 - 11
client/src/i18n/cn/collection.ts

@@ -2,13 +2,12 @@ const collectionTrans = {
   noLoadData: 'No Loaded Collection',
   noData: 'No Collection',
 
-  rowCount: 'Entity Count',
-  tooltip: 'data in one entity',
+  rowCount: 'Approx Entity Count',
 
   create: 'Create Collection',
   delete: 'delete',
   deleteTooltip: 'Please select at least one item to delete.',
-  alias: 'alias',
+  alias: 'Alias',
   aliasTooltip: 'Please select one collection to create alias',
   download: 'Download',
   downloadTooltip: 'Download all query results',
@@ -19,29 +18,40 @@ const collectionTrans = {
   // table
   id: 'ID',
   name: 'Name',
+  nameTip: 'Collection Name',
   status: 'Status',
   desc: 'Description',
   createdTime: 'Created Time',
   maxLength: 'Max Length',
 
+  // table tooltip
+  aliasInfo: 'Alias can be used as collection name in vector search.',
+  consistencyLevelInfo:
+    'Consistency refers to the property that ensures every node or replica has the same view of data when writing or reading data at a given time.',
+  entityCountInfo: 'Approximately entity count.',
+
   // create dialog
   createTitle: 'Create Collection',
-  general: '1. General Info',
-  schema: '2. Define Schema',
-  consistency: '3. Consistency Level',
+  general: 'General information',
+  schema: 'Schema',
+  consistency: 'Consistency Level',
   consistencyLevel: 'Consistency Level',
-  description: 'Description (Optional)',
+  description: 'Description',
   fieldType: 'Field Type',
   vectorFieldType: 'Vector Field Type',
   fieldName: 'Field Name',
+  idFieldName: 'ID Name',
+  vectorFieldName: 'Vector Name',
   autoId: 'Auto ID',
+  vectorType: 'Vector Type',
+  idType: 'ID Type',
   dimension: 'Dimension',
   dimensionTooltip: 'Only vector type has dimension',
   dimensionMutipleWarning: 'Dimension should be 8 multiple',
-  dimensionPositiveWarning: 'Dimension should be positive number',
+  dimensionPositiveWarning: 'Positive number only',
   newBtn: 'add new field',
   nameLengthWarning: 'Name length should be less than 256',
-  nameContentWarning: 'Name can only contain numbers, letters, and underscores',
+  nameContentWarning: 'Only numbers, letters, and underscores are allowed.',
   nameFirstLetterWarning:
     'Name first character must be underscore or character(a~z, A~Z)',
 
@@ -58,16 +68,20 @@ const collectionTrans = {
   releaseConfirmLabel: 'Release',
 
   // delete dialog
-  deleteWarning:
-    'You are trying to delete a collection with data. This action cannot be undone.',
+  deleteWarning: `You are trying to delete a collection with data. This action cannot be undone.`,
   deleteDataWarning: `You are trying to delete entites. This action cannot be undone.`,
+  deleteAliasWarning: `You are trying to drop an alias. This action cannot be undone.`,
 
   // collection tabs
   partitionTab: 'Partitions',
   schemaTab: 'Schema',
   queryTab: 'Data Query',
+  previewTab: 'Data Preview',
   startTip: 'Start your data query',
   exprPlaceHolder: 'Please enter your query by using advanced filter ->',
+
+  // alias dialog
+  aliasCreatePlaceholder: 'Alias name',
 };
 
 export default collectionTrans;

+ 1 - 2
client/src/i18n/cn/common.ts

@@ -8,7 +8,7 @@ const commonTrans = {
     ssl: 'SSL',
   },
   status: {
-    loaded: 'loaded for search',
+    loaded: 'loaded',
     unloaded: 'unloaded',
     error: 'error',
     running: 'running',
@@ -42,7 +42,6 @@ const commonTrans = {
     join: 'Join our growing social community today',
     get: 'Get insight, tips and share ideas',
   },
-
   capacity: {
     b: 'B',
     kb: 'KB',

+ 2 - 3
client/src/i18n/cn/dialog.ts

@@ -1,10 +1,9 @@
 const dialogTrans = {
   deleteTipAction: 'Type',
   deleteTipPurpose: 'to confirm.',
-  deleteTitle: `Delete {{type}}`,
-  createAlias: `Create alias for {{type}}`,
-
+  deleteTitle: `Drop {{type}}`,
   releaseTitle: `Release {{type}}`,
+  createAlias: `Create alias for {{type}}`,
   loadTitle: `Load {{type}}`,
 
   loadContent: `You are trying to load a {{type}} with data. Only loaded {{type}} can be searched.`,

+ 3 - 2
client/src/i18n/cn/index.ts

@@ -1,13 +1,14 @@
 const indexTrans = {
   type: 'Index Type',
   param: 'Index Parameters',
-  create: 'Create Index',
 
+  create: 'Create Index',
   index: 'Index',
-  metric: 'Metric Type',
   desc: 'Description',
+
   creating: 'Creating Index',
 
+  metric: 'Metric Type',
   createSuccess: 'Start creating index',
   deleteWarning:
     'You are trying to delete an index. This action cannot be undone.',

+ 5 - 0
client/src/i18n/cn/insert.ts

@@ -19,6 +19,7 @@ const insertTrans = {
     'Uploaded data column count is not equal to schema count',
   uploadAutoIdFieldWarning:
     'AutoId field ({{fieldName}}) does not require data',
+
   previewTipData: 'Data Preview(Top 4 rows shown)',
   previewTipAction: '*Change header cell selector value to edit field name',
   requiredFieldName: 'Field Name*',
@@ -27,6 +28,10 @@ const insertTrans = {
   statusLoadingTip: 'Please wait patiently, thank you',
   statusSuccess: 'Import Data Successfully!',
   statusError: 'Import Data Failed!',
+
+  importSampleData: 'Import sample data into {{collection}}',
+  sampleDataSize: 'Choose sample data size',
+  importSampleDataDesc: `Import random data based on the collection's schema.`
 };
 
 export default insertTrans;

+ 3 - 3
client/src/i18n/cn/partition.ts

@@ -8,15 +8,15 @@ const partitionTrans = {
   name: 'Name',
   createdTime: 'Created Time',
   status: 'Status',
-  rowCount: 'Entity Count',
-  tooltip: 'data in one entity',
+  rowCount: 'Approx Entity Count',
+  tooltip: 'Approximately entity count.',
 
   createTitle: 'Create Partition',
   nameWarning: '_default is reserved, cannot be used as name',
 
   deleteWarning:
     'You are trying to delete partition. This action cannot be undone.',
-  deletePartitionError: 'default partition cannot be deleted',
+  deletePartitionError: 'default partition cannot be dropped',
 };
 
 export default partitionTrans;

+ 1 - 2
client/src/i18n/cn/search.ts

@@ -11,8 +11,7 @@ const searchTrans = {
   result: 'Search Results',
   topK: 'TopK {{number}}',
   filter: 'Advanced Filter',
-  vectorValueWarning:
-    'Vector value should be an array of length {{dimension}}(dimension)',
+  vectorValueWarning: 'Vector value should be an array of length {{dimension}}',
   timeTravel: 'Time Travel',
   timeTravelPrefix: 'Data before',
 };

+ 2 - 1
client/src/i18n/cn/success.ts

@@ -2,8 +2,9 @@ const successTrans = {
   connect: 'Connection to milvus successful',
   create: `{{name}} has been created`,
   load: `{{name}} has been loaded`,
-  delete: `Delete {{name}} successfully`,
+  delete: `{{name}} successfully dropped`,
   release: `{{name}} has been released`,
+  update: `{{name}} has been updated`,
 };
 
 export default successTrans;

+ 1 - 1
client/src/i18n/cn/user.ts

@@ -7,7 +7,7 @@ const userTrans = {
   newPassword: 'New Password',
   confirmPassword: 'Confirm Password',
   update: 'Update password',
-  isNotSame: 'Confirm password is not same as new password',
+  isNotSame: 'Not same as new password',
   deleteTip:
     'Please select at least one item to delete and root can not be deleted.',
 };

+ 2 - 1
client/src/i18n/cn/warning.ts

@@ -1,8 +1,9 @@
 const warningTrans = {
   required: '{{name}} is required',
+  requiredOnly: 'Required',
   positive: '{{name}} should be positive',
   integer: '{{name}} should be integers',
-  range: 'range is {{min}} ~ {{max}}',
+  range: 'Range is {{min}} ~ {{max}}',
   specValueOrRange:
     '{{name}} should be {{specValue}}, or in range {{min}} ~ {{max}}',
   noSupportIndexType:

+ 1 - 0
client/src/i18n/en/button.ts

@@ -9,6 +9,7 @@ const btnTrans = {
   connect: 'Connect',
   import: 'Import',
   delete: 'Delete',
+  drop: 'Drop',
   release: 'Release',
   load: 'Load',
   insert: 'Import Data',

+ 23 - 9
client/src/i18n/en/collection.ts

@@ -2,13 +2,12 @@ const collectionTrans = {
   noLoadData: 'No Loaded Collection',
   noData: 'No Collection',
 
-  rowCount: 'Entity Count',
-  tooltip: 'data in one entity',
+  rowCount: 'Approx Entity Count',
 
   create: 'Create Collection',
   delete: 'delete',
   deleteTooltip: 'Please select at least one item to delete.',
-  alias: 'alias',
+  alias: 'Alias',
   aliasTooltip: 'Please select one collection to create alias',
   download: 'Download',
   downloadTooltip: 'Download all query results',
@@ -19,29 +18,40 @@ const collectionTrans = {
   // table
   id: 'ID',
   name: 'Name',
+  nameTip: 'Collection Name',
   status: 'Status',
   desc: 'Description',
   createdTime: 'Created Time',
   maxLength: 'Max Length',
 
+  // table tooltip
+  aliasInfo: 'Alias can be used as collection name in vector search.',
+  consistencyLevelInfo:
+    'Consistency refers to the property that ensures every node or replica has the same view of data when writing or reading data at a given time.',
+  entityCountInfo: 'Approximately entity count.',
+
   // create dialog
   createTitle: 'Create Collection',
-  general: '1. General Info',
-  schema: '2. Define Schema',
-  consistency: '3. Consistency Level',
+  general: 'General information',
+  schema: 'Schema',
+  consistency: 'Consistency Level',
   consistencyLevel: 'Consistency Level',
-  description: 'Description (Optional)',
+  description: 'Description',
   fieldType: 'Field Type',
   vectorFieldType: 'Vector Field Type',
   fieldName: 'Field Name',
+  idFieldName: 'ID Name',
+  vectorFieldName: 'Vector Name',
   autoId: 'Auto ID',
+  vectorType: 'Vector Type',
+  idType: 'ID Type',
   dimension: 'Dimension',
   dimensionTooltip: 'Only vector type has dimension',
   dimensionMutipleWarning: 'Dimension should be 8 multiple',
-  dimensionPositiveWarning: 'Dimension should be positive number',
+  dimensionPositiveWarning: 'Positive number only',
   newBtn: 'add new field',
   nameLengthWarning: 'Name length should be less than 256',
-  nameContentWarning: 'Name can only contain numbers, letters, and underscores',
+  nameContentWarning: 'Only numbers, letters, and underscores are allowed.',
   nameFirstLetterWarning:
     'Name first character must be underscore or character(a~z, A~Z)',
 
@@ -60,6 +70,7 @@ const collectionTrans = {
   // delete dialog
   deleteWarning: `You are trying to delete a collection with data. This action cannot be undone.`,
   deleteDataWarning: `You are trying to delete entites. This action cannot be undone.`,
+  deleteAliasWarning: `You are trying to drop an alias. This action cannot be undone.`,
 
   // collection tabs
   partitionTab: 'Partitions',
@@ -68,6 +79,9 @@ const collectionTrans = {
   previewTab: 'Data Preview',
   startTip: 'Start your data query',
   exprPlaceHolder: 'Please enter your query by using advanced filter ->',
+
+  // alias dialog
+  aliasCreatePlaceholder: 'Alias name',
 };
 
 export default collectionTrans;

+ 1 - 1
client/src/i18n/en/common.ts

@@ -8,7 +8,7 @@ const commonTrans = {
     ssl: 'SSL',
   },
   status: {
-    loaded: 'loaded for search',
+    loaded: 'loaded',
     unloaded: 'unloaded',
     error: 'error',
     running: 'running',

+ 1 - 1
client/src/i18n/en/dialog.ts

@@ -1,7 +1,7 @@
 const dialogTrans = {
   deleteTipAction: 'Type',
   deleteTipPurpose: 'to confirm.',
-  deleteTitle: `Delete {{type}}`,
+  deleteTitle: `Drop {{type}}`,
   releaseTitle: `Release {{type}}`,
   createAlias: `Create alias for {{type}}`,
   loadTitle: `Load {{type}}`,

+ 3 - 3
client/src/i18n/en/partition.ts

@@ -8,15 +8,15 @@ const partitionTrans = {
   name: 'Name',
   createdTime: 'Created Time',
   status: 'Status',
-  rowCount: 'Entity Count',
-  tooltip: 'data in one entity',
+  rowCount: 'Approx Entity Count',
+  tooltip: 'Approximately entity count.',
 
   createTitle: 'Create Partition',
   nameWarning: '_default is reserved, cannot be used as name',
 
   deleteWarning:
     'You are trying to delete partition. This action cannot be undone.',
-  deletePartitionError: 'default partition cannot be deleted',
+  deletePartitionError: 'default partition cannot be dropped',
 };
 
 export default partitionTrans;

+ 1 - 1
client/src/i18n/en/success.ts

@@ -2,7 +2,7 @@ const successTrans = {
   connect: 'Connection to milvus successful',
   create: `{{name}} has been created`,
   load: `{{name}} has been loaded`,
-  delete: `{{name}} successfully deleted`,
+  delete: `{{name}} successfully dropped`,
   release: `{{name}} has been released`,
   update: `{{name}} has been updated`,
 };

+ 2 - 1
client/src/i18n/en/warning.ts

@@ -1,8 +1,9 @@
 const warningTrans = {
   required: '{{name}} is required',
+  requiredOnly: 'Required',
   positive: '{{name}} should be positive',
   integer: '{{name}} should be integers',
-  range: 'range is {{min}} ~ {{max}}',
+  range: 'Range is {{min}} ~ {{max}}',
   specValueOrRange:
     '{{name}} should be {{specValue}}, or in range {{min}} ~ {{max}}',
   noSupportIndexType:

+ 4 - 8
client/src/index.tsx

@@ -1,16 +1,12 @@
-import { StrictMode } from 'react';
-import ReactDOM from 'react-dom';
+import { createRoot } from 'react-dom/client';
 import './index.css';
 import './i18n';
 import App from './App';
 import reportWebVitals from './reportWebVitals';
 
-ReactDOM.render(
-  <StrictMode>
-    <App />
-  </StrictMode>,
-  document.getElementById('root')
-);
+const container = document.getElementById('root');
+const root = createRoot(container!); // createRoot(container!) if you use TypeScript
+root.render(<App />);
 
 // If you want to start measuring performance in your app, pass a function
 // to log results (for example: reportWebVitals(console.log))

+ 132 - 0
client/src/pages/collections/Aliases.tsx

@@ -0,0 +1,132 @@
+import { useContext } from 'react';
+import { Chip, IconButton, makeStyles, Theme } from '@material-ui/core';
+import { useTranslation } from 'react-i18next';
+import { rootContext } from '../../context/Root';
+import { AliasesProps } from './Types';
+import icons from '../../components/icons/Icons';
+import DeleteIcon from '@material-ui/icons/Delete';
+import CreateAlias from './CreateAlias';
+import DeleteTemplate from '../../components/customDialog/DeleteDialogTemplate';
+import { CollectionHttp } from '../../http/Collection';
+
+const useStyles = makeStyles((theme: Theme) => ({
+  wrapper: {
+    display: 'flex',
+    flexWrap: 'wrap',
+    gap: 8,
+  },
+  iconBtn: {
+    marginTop: 4,
+    width: '16px',
+    height: '16px',
+  },
+}));
+
+export default function Aliases(props: AliasesProps) {
+  const {
+    aliases,
+    collectionName,
+    onCreate = () => {},
+    onDelete = () => {},
+  } = props;
+  const classes = useStyles();
+  const { setDialog, openSnackBar, handleCloseDialog } =
+    useContext(rootContext);
+  // i18n
+  const { t: btnTrans } = useTranslation('btn');
+  const { t: dialogTrans } = useTranslation('dialog');
+  const { t: collectionTrans } = useTranslation('collection');
+  const { t: successTrans } = useTranslation('success');
+
+  const AddIcon = icons.add;
+
+  const handleCreate = (e: React.MouseEvent) => {
+    setDialog({
+      open: true,
+      type: 'custom',
+      params: {
+        component: (
+          <CreateAlias
+            collectionName={collectionName}
+            cb={() => {
+              onCreate();
+            }}
+          />
+        ),
+      },
+    });
+    e.stopPropagation();
+  };
+
+  if (aliases.length === 0) {
+    return (
+      <>
+        <IconButton
+          onClick={handleCreate}
+          size="small"
+          classes={{ root: classes.iconBtn }}
+          aria-label="add"
+        >
+          <AddIcon width="8" height="8" fontSize="small" />
+        </IconButton>
+      </>
+    );
+  }
+
+  const handleDelete = async (params: {
+    collection: string;
+    alias: string;
+  }) => {
+    await CollectionHttp.dropAlias(params.collection, { alias: params.alias });
+    openSnackBar(successTrans('delete', { name: collectionTrans('alias') }));
+    handleCloseDialog();
+    onDelete();
+  };
+
+  const _onDelete = (alias: { collection: string; alias: string }) => {
+    setDialog({
+      open: true,
+      type: 'custom',
+      params: {
+        component: (
+          <DeleteTemplate
+            label={btnTrans('drop')}
+            title={dialogTrans('deleteTitle', {
+              type: collectionTrans('alias'),
+            })}
+            text={collectionTrans('deleteAliasWarning')}
+            handleDelete={() => handleDelete(alias)}
+          />
+        ),
+      },
+    });
+  };
+
+  return (
+    <div className={classes.wrapper}>
+      {aliases.map(a => (
+        <Chip
+          key={a}
+          size="small"
+          label={a}
+          variant="outlined"
+          deleteIcon={<DeleteIcon />}
+          onClick={(e: React.MouseEvent) => {
+            e.stopPropagation();
+          }}
+          onDelete={() => {
+            _onDelete({ collection: collectionName, alias: a });
+          }}
+        />
+      ))}
+      <IconButton
+        onClick={handleCreate}
+        size="small"
+        classes={{ root: classes.iconBtn }}
+        aria-label="add"
+      >
+        <AddIcon width="8" height="8" fontSize="small" />
+      </IconButton>
+    </div>
+  );
+}

+ 3 - 3
client/src/pages/collections/Collection.tsx

@@ -4,7 +4,7 @@ import { ALL_ROUTER_TYPES } from '../../router/Types';
 import CustomTabList from '../../components/customTabList/CustomTabList';
 import { ITab } from '../../components/customTabList/Types';
 import Partitions from '../partitions/Partitions';
-import { useHistory, useLocation, useParams } from 'react-router-dom';
+import { useNavigate, useLocation, useParams } from 'react-router-dom';
 import { useMemo } from 'react';
 import { parseLocationSearch } from '../../utils/Format';
 import Schema from '../schema/Schema';
@@ -23,7 +23,7 @@ const Collection = () => {
 
   useNavigationHook(ALL_ROUTER_TYPES.COLLECTION_DETAIL, { collectionName });
 
-  const history = useHistory();
+  const navigate = useNavigate();
   const location = useLocation();
 
   const { t: collectionTrans } = useTranslation('collection');
@@ -37,7 +37,7 @@ const Collection = () => {
 
   const handleTabChange = (activeIndex: number) => {
     const path = location.pathname;
-    history.push(`${path}?activeIndex=${activeIndex}`);
+    navigate(`${path}?activeIndex=${activeIndex}`);
   };
 
   const tabs: ITab[] = [

+ 77 - 78
client/src/pages/collections/Collections.tsx

@@ -1,5 +1,5 @@
 import { useCallback, useContext, useEffect, useMemo, useState } from 'react';
-import { Link } from 'react-router-dom';
+import { Link, useSearchParams } from 'react-router-dom';
 import { useNavigationHook } from '../../hooks/Navigation';
 import { ALL_ROUTER_TYPES } from '../../router/Types';
 import AttuGrid from '../../components/grid/Grid';
@@ -30,7 +30,6 @@ import {
   useLoadAndReleaseDialogHook,
 } from '../../hooks/Dialog';
 import Highlighter from 'react-highlight-words';
-import { parseLocationSearch } from '../../utils/Format';
 import InsertContainer from '../../components/insert/Container';
 import ImportSample from '../../components/insert/ImportSample';
 import { MilvusHttp } from '../../http/Milvus';
@@ -38,7 +37,7 @@ import { LOADING_STATE } from '../../consts/Milvus';
 import { webSokcetContext } from '../../context/WebSocket';
 import { WS_EVENTS, WS_EVENTS_TYPE } from '../../consts/Http';
 import { checkIndexBuilding, checkLoading } from '../../utils/Validation';
-// import CreateAlias from './CreateAlias';
+import Aliases from './Aliases';
 
 const useStyles = makeStyles((theme: Theme) => ({
   emptyWrapper: {
@@ -63,16 +62,14 @@ const useStyles = makeStyles((theme: Theme) => ({
   },
 }));
 
-let timer: NodeJS.Timeout | null = null;
-// get init search value from url
-const { urlSearch = '' } = parseLocationSearch(window.location.search);
-
 const Collections = () => {
   useNavigationHook(ALL_ROUTER_TYPES.COLLECTIONS);
   const { handleAction } = useLoadAndReleaseDialogHook({ type: 'collection' });
   const { handleInsertDialog } = useInsertDialogHook();
-
-  const [search, setSearch] = useState<string>(urlSearch);
+  const [searchParams] = useSearchParams();
+  const [search, setSearch] = useState<string>(
+    (searchParams.get('search') as string) || ''
+  );
   const [loading, setLoading] = useState<boolean>(false);
   const [selectedCollections, setSelectedCollections] = useState<
     CollectionView[]
@@ -92,13 +89,38 @@ const Collections = () => {
   const InfoIcon = icons.info;
   const SourceIcon = icons.source;
 
-  const searchedCollections = useMemo(
-    () => collections.filter(collection => collection._name.includes(search)),
-    [collections, search]
-  );
+  const fetchData = useCallback(async () => {
+    try {
+      setLoading(true);
+      const res = await CollectionHttp.getCollections();
+      const hasLoadingOrBuildingCollection = res.some(
+        v => checkLoading(v) || checkIndexBuilding(v)
+      );
+
+      // if some collection is building index or loading, start pulling data
+      if (hasLoadingOrBuildingCollection) {
+        MilvusHttp.triggerCron({
+          name: WS_EVENTS.COLLECTION,
+          type: WS_EVENTS_TYPE.START,
+        });
+      }
+
+      setCollections(res);
+    } finally {
+      setLoading(false);
+    }
+  }, [setCollections]);
+
+  useEffect(() => {
+    fetchData();
+  }, [fetchData]);
 
   const formatCollections = useMemo(() => {
-    const data = searchedCollections.map(v => {
+    const filteredCollections = search
+      ? collections.filter(collection => collection._name.includes(search))
+      : collections;
+
+    const data = filteredCollections.map(v => {
       // const indexStatus = statusRes.find(item => item._name === v._name);
       Object.assign(v, {
         nameElement: (
@@ -116,12 +138,20 @@ const Collections = () => {
         indexCreatingElement: (
           <StatusIcon type={v._indexState || ChildrenStatusType.FINISH} />
         ),
+        _aliasElement: (
+          <Aliases
+            aliases={v._aliases}
+            collectionName={v._name}
+            onCreate={fetchData}
+            onDelete={fetchData}
+          />
+        ),
       });
-
       return v;
     });
+
     return data;
-  }, [classes.highlight, classes.link, search, searchedCollections]);
+  }, [search, collections]);
 
   const {
     pageSize,
@@ -135,32 +165,6 @@ const Collections = () => {
     orderBy,
   } = usePaginationHook(formatCollections);
 
-  const fetchData = useCallback(async () => {
-    try {
-      setLoading(true);
-      const res = await CollectionHttp.getCollections();
-      const hasLoadingOrBuildingCollection = res.some(
-        v => checkLoading(v) || checkIndexBuilding(v)
-      );
-
-      // if some collection is building index or loading, start pulling data
-      if (hasLoadingOrBuildingCollection) {
-        MilvusHttp.triggerCron({
-          name: WS_EVENTS.COLLECTION,
-          type: WS_EVENTS_TYPE.START,
-        });
-      }
-
-      setCollections(res);
-    } finally {
-      setLoading(false);
-    }
-  }, [setCollections]);
-
-  useEffect(() => {
-    fetchData();
-  }, [fetchData]);
-
   const handleInsert = async (
     collectionName: string,
     partitionName: string,
@@ -271,9 +275,6 @@ const Collections = () => {
   };
 
   const handleSearch = (value: string) => {
-    if (timer) {
-      clearTimeout(timer);
-    }
     setSearch(value);
   };
 
@@ -336,7 +337,7 @@ const Collections = () => {
           params: {
             component: (
               <DeleteTemplate
-                label={btnTrans('delete')}
+                label={btnTrans('drop')}
                 title={dialogTrans('deleteTitle', {
                   type: collectionTrans('collection'),
                 })}
@@ -353,30 +354,7 @@ const Collections = () => {
       disabledTooltip: collectionTrans('deleteTooltip'),
       disabled: data => data.length === 0,
     },
-    // Todo: hide alias after we can get all alias from milvus.
-    // {
-    //   type: 'iconBtn',
-    //   onClick: () => {
-    //     setDialog({
-    //       open: true,
-    //       type: 'custom',
-    //       params: {
-    //         component: (
-    //           <CreateAlias
-    //             collectionName={selectedCollections[0]._name}
-    //             cb={() => {
-    //               setSelectedCollections([]);
-    //             }}
-    //           />
-    //         ),
-    //       },
-    //     });
-    //   },
-    //   label: collectionTrans('alias'),
-    //   icon: 'alias',
-    //   disabledTooltip: collectionTrans('aliasTooltip'),
-    //   disabled: data => data.length !== 1,
-    // },
+
     {
       label: 'Search',
       icon: 'search',
@@ -409,7 +387,20 @@ const Collections = () => {
       label: (
         <span className="flex-center">
           {collectionTrans('rowCount')}
-          <CustomToolTip title={collectionTrans('tooltip')}>
+          <CustomToolTip title={collectionTrans('entityCountInfo')}>
+            <InfoIcon classes={{ root: classes.icon }} />
+          </CustomToolTip>
+        </span>
+      ),
+    },
+    {
+      id: '_aliasElement',
+      align: 'left',
+      disablePadding: false,
+      label: (
+        <span className="flex-center">
+          {collectionTrans('alias')}
+          <CustomToolTip title={collectionTrans('aliasInfo')}>
             <InfoIcon classes={{ root: classes.icon }} />
           </CustomToolTip>
         </span>
@@ -419,8 +410,16 @@ const Collections = () => {
       id: 'consistency_level',
       align: 'left',
       disablePadding: false,
-      label: collectionTrans('consistencyLevel'),
+      label: (
+        <span className="flex-center">
+          {collectionTrans('consistencyLevel')}
+          <CustomToolTip title={collectionTrans('consistencyLevelInfo')}>
+            <InfoIcon classes={{ root: classes.icon }} />
+          </CustomToolTip>
+        </span>
+      ),
     },
+
     {
       id: '_desc',
       align: 'left',
@@ -433,12 +432,12 @@ const Collections = () => {
       disablePadding: false,
       label: collectionTrans('createdTime'),
     },
-    {
-      id: 'indexCreatingElement',
-      align: 'left',
-      disablePadding: false,
-      label: '',
-    },
+    // {
+    //   id: 'indexCreatingElement',
+    //   align: 'left',
+    //   disablePadding: false,
+    //   label: '',
+    // },
     {
       id: 'action',
       align: 'center',

+ 22 - 10
client/src/pages/collections/Create.tsx

@@ -23,32 +23,34 @@ const useStyles = makeStyles((theme: Theme) => ({
   fieldset: {
     width: '100%',
     display: 'flex',
-    justifyContent: 'space-between',
     alignItems: 'center',
-
+    marginBottom: '16px',
+    gap: '8px',
     '&:nth-last-child(2)': {
       flexDirection: 'column',
       alignItems: 'flex-start',
     },
 
     '& legend': {
-      marginBottom: theme.spacing(2),
+      marginBottom: theme.spacing(1),
       color: `#82838e`,
       lineHeight: '20px',
       fontSize: '14px',
     },
   },
   input: {
-    width: '48%',
+    width: '100%',
   },
   select: {
     width: '160px',
-    marginBottom: '22px',
 
     '&:first-child': {
       marginLeft: 0,
     },
   },
+  dialog: {
+    minWidth: '100%',
+  },
 }));
 
 const CreateCollection: FC<CollectionCreateProps> = ({ handleCreate }) => {
@@ -130,9 +132,7 @@ const CreateCollection: FC<CollectionCreateProps> = ({ handleCreate }) => {
         // cannot be empty
         {
           rule: 'require',
-          errorText: warningTrans('required', {
-            name: collectionTrans('name'),
-          }),
+          errorText: warningTrans('requiredOnly'),
         },
         // length <= 255
         {
@@ -157,6 +157,10 @@ const CreateCollection: FC<CollectionCreateProps> = ({ handleCreate }) => {
           errorText: collectionTrans('nameFirstLetterWarning'),
         },
       ],
+      InputLabelProps: {
+        shrink: true,
+      },
+      size: 'small',
       className: classes.input,
     },
     {
@@ -166,7 +170,11 @@ const CreateCollection: FC<CollectionCreateProps> = ({ handleCreate }) => {
       onChange: (value: string) => handleInputChange('description', value),
       variant: 'filled',
       validations: [],
+      size: 'small',
       className: classes.input,
+      InputLabelProps: {
+        shrink: true,
+      },
     },
   ];
 
@@ -196,10 +204,13 @@ const CreateCollection: FC<CollectionCreateProps> = ({ handleCreate }) => {
   return (
     <DialogTemplate
       title={collectionTrans('createTitle')}
-      handleClose={handleCloseDialog}
+      handleClose={() => {
+        handleCloseDialog();
+      }}
       confirmLabel={btnTrans('create')}
       handleConfirm={handleCreateCollection}
       confirmDisabled={disabled || !allFieldsValid}
+      dialogClass={classes.dialog}
     >
       <form>
         <fieldset className={classes.fieldset}>
@@ -230,13 +241,14 @@ const CreateCollection: FC<CollectionCreateProps> = ({ handleCreate }) => {
           <legend>{collectionTrans('consistency')}</legend>
           <CustomSelector
             wrapperClass={classes.select}
+            size="small"
             options={CONSISTENCY_LEVEL_OPTIONS}
             onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
               setConsistencyLevel(e.target.value as ConsistencyLevelEnum);
             }}
+            hiddenLabel={true}
             value={consistencyLevel}
             variant="filled"
-            label={'Consistency'}
           />
         </fieldset>
       </form>

+ 24 - 11
client/src/pages/collections/CreateAlias.tsx

@@ -1,10 +1,8 @@
 import { FC, useContext, useMemo, useState } from 'react';
-
+import { Typography, makeStyles, Theme } from '@material-ui/core';
 import { useTranslation } from 'react-i18next';
 import { rootContext } from '../../context/Root';
-
 import DialogTemplate from '../../components/customDialog/DialogTemplate';
-
 import CustomInput from '../../components/customInput/CustomInput';
 import { formatForm } from '../../utils/Form';
 import { useFormValidation } from '../../hooks/Form';
@@ -12,12 +10,20 @@ import { ITextfieldConfig } from '../../components/customInput/Types';
 import { CollectionHttp } from '../../http/Collection';
 import { CreateAliasProps } from './Types';
 
+const useStyles = makeStyles((theme: Theme) => ({
+  desc: {
+    margin: '8px 0 16px 0',
+  },
+}));
+
 const CreateAlias: FC<CreateAliasProps> = props => {
   const { cb, collectionName } = props;
   const [form, setForm] = useState({
     alias: '',
   });
 
+  const classes = useStyles();
+
   const checkedForm = useMemo(() => {
     const { alias } = form;
     return formatForm({ alias });
@@ -29,6 +35,7 @@ const CreateAlias: FC<CreateAliasProps> = props => {
   const { t: dialogTrans } = useTranslation('dialog');
   const { t: warningTrans } = useTranslation('warning');
   const { t: collectionTrans } = useTranslation('collection');
+  const { t: btnTrans } = useTranslation('btn');
 
   const handleInputChange = (value: string) => {
     setForm({ alias: value });
@@ -41,11 +48,11 @@ const CreateAlias: FC<CreateAliasProps> = props => {
   };
 
   const aliasInputConfig: ITextfieldConfig = {
-    label: 'Collection Alias',
+    label: collectionTrans('alias'),
     key: 'alias',
     onChange: handleInputChange,
     variant: 'filled',
-    placeholder: 'alias name',
+    placeholder: collectionTrans('aliasCreatePlaceholder'),
     fullWidth: true,
     validations: [
       {
@@ -68,13 +75,19 @@ const CreateAlias: FC<CreateAliasProps> = props => {
       })}
       handleClose={handleCloseDialog}
       children={
-        <CustomInput
-          type="text"
-          textConfig={aliasInputConfig}
-          checkValid={checkIsValid}
-          validInfo={validation}
-        />
+        <>
+          <Typography variant="body1" component="p" className={classes.desc}>
+            {collectionTrans('aliasInfo')}
+          </Typography>
+          <CustomInput
+            type="text"
+            textConfig={aliasInputConfig}
+            checkValid={checkIsValid}
+            validInfo={validation}
+          />
+        </>
       }
+      confirmLabel={btnTrans('create')}
       handleConfirm={handleConfirm}
       confirmDisabled={disabled}
     />

+ 103 - 77
client/src/pages/collections/CreateFields.tsx

@@ -1,7 +1,6 @@
 import { makeStyles, Theme, TextField, IconButton } from '@material-ui/core';
 import { FC, Fragment, ReactElement, useMemo } from 'react';
 import { useTranslation } from 'react-i18next';
-import CustomButton from '../../components/customButton/CustomButton';
 import CustomSelector from '../../components/customSelector/CustomSelector';
 import icons from '../../components/icons/Icons';
 import { generateId } from '../../utils/Common';
@@ -35,57 +34,50 @@ const useStyles = makeStyles((theme: Theme) => ({
     display: 'flex',
     flexWrap: 'nowrap',
     alignItems: 'center',
-    // only Safari 14.1+ support flexbox gap
-    // gap: '10px',
-    width: '100%',
-
-    '& > *': {
-      marginLeft: '10px',
-    },
-
-    '& .dimension': {
-      maxWidth: '160px',
-    },
+    gap: '8px',
+    flex: '1 0 auto',
   },
   input: {
     fontSize: '14px',
   },
-  primaryInput: {
-    maxWidth: '160px',
-
-    '&:first-child': {
-      marginLeft: 0,
-    },
+  fieldInput: {
+    width: '160px',
   },
   select: {
-    width: '160px',
-    marginBottom: '22px',
+    width: '140px',
+    marginTop: '-20px',
 
     '&:first-child': {
       marginLeft: 0,
     },
   },
+  autoIdSelect: {
+    width: '120px',
+    marginTop: '-20px',
+  },
+  numberBox: {
+    width: '97px',
+  },
+  maxLength: {
+    maxWidth: '80px',
+  },
   descInput: {
-    minWidth: '100px',
-    flexGrow: 1,
+    width: '120px',
   },
   btnTxt: {
     textTransform: 'uppercase',
   },
   iconBtn: {
     marginLeft: 0,
-
     padding: 0,
-    width: '20px',
-    height: '20px',
-  },
-  mb2: {
-    marginBottom: theme.spacing(2),
+    width: '16px',
+    height: '16px',
   },
   helperText: {
     lineHeight: '20px',
-    margin: theme.spacing(0.25, 0),
-    marginLeft: '12px',
+    fontSize: '10px',
+    margin: theme.spacing(0),
+    marginLeft: '11px',
   },
 }));
 
@@ -112,7 +104,7 @@ const CreateFields: FC<CreateFieldsProps> = ({
 
   const classes = useStyles();
 
-  const AddIcon = icons.add;
+  const AddIcon = icons.addOutline;
   const RemoveIcon = icons.remove;
 
   const { requiredFields, optionalFields } = useMemo(
@@ -161,6 +153,7 @@ const CreateFields: FC<CreateFieldsProps> = ({
             ? ALL_OPTIONS
             : VECTOR_FIELDS_OPTIONS
         }
+        size="small"
         onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
           onChange(e.target.value as DataTypeEnum);
         }}
@@ -196,6 +189,10 @@ const CreateFields: FC<CreateFieldsProps> = ({
             input: inputClassName,
           },
         }}
+        InputLabelProps={{
+          shrink: true,
+        }}
+        size="small"
         disabled={isReadOnly}
         error={validate(value) !== ' '}
         helperText={validate(value)}
@@ -208,10 +205,15 @@ const CreateFields: FC<CreateFieldsProps> = ({
     );
   };
 
-  const generateFieldName = (field: Field) => {
+  const generateFieldName = (
+    field: Field,
+    label?: string,
+    className?: string
+  ) => {
     return getInput({
-      label: collectionTrans('fieldName'),
+      label: label || collectionTrans('fieldName'),
       value: field.name,
+      className: className || classes.fieldInput,
       handleChange: (value: string) => {
         const isValid = checkEmptyValid(value);
         setFieldsValidation(v =>
@@ -226,9 +228,7 @@ const CreateFields: FC<CreateFieldsProps> = ({
         if (value === null) return ' ';
         const isValid = checkEmptyValid(value);
 
-        return isValid
-          ? ' '
-          : warningTrans('required', { name: collectionTrans('fieldName') });
+        return isValid ? ' ' : warningTrans('requiredOnly');
       },
     });
   };
@@ -239,7 +239,7 @@ const CreateFields: FC<CreateFieldsProps> = ({
       value: field.description,
       handleChange: (value: string) =>
         changeFields(field.id!, 'description', value),
-      className: classes.descInput,
+      inputClassName: classes.descInput,
     });
   };
 
@@ -269,6 +269,7 @@ const CreateFields: FC<CreateFieldsProps> = ({
     return getInput({
       label: collectionTrans('dimension'),
       value: field.dimension as number,
+      inputClassName: classes.numberBox,
       handleChange: (value: string) => {
         const { isPositive, isMutiple } = validateDimension(value);
         const isValid =
@@ -301,6 +302,7 @@ const CreateFields: FC<CreateFieldsProps> = ({
       label: 'Max Length',
       value: field.max_length!,
       type: 'number',
+      inputClassName: classes.maxLength,
       handleChange: (value: string) =>
         changeFields(field.id!, 'max_length', value),
       validate: (value: any) => {
@@ -313,9 +315,7 @@ const CreateFields: FC<CreateFieldsProps> = ({
           type: 'number',
         });
         return !isEmptyValid
-          ? warningTrans('required', {
-              name: collectionTrans('fieldName'),
-            })
+          ? warningTrans('requiredOnly')
           : !isRangeValid
           ? warningTrans('range', {
               min: 1,
@@ -339,7 +339,7 @@ const CreateFields: FC<CreateFieldsProps> = ({
     setFields(newFields);
   };
 
-  const handleAddNewField = () => {
+  const handleAddNewField = (index: number) => {
     const id = generateId();
     const newDefaultItem: Field = {
       name: null,
@@ -356,14 +356,16 @@ const CreateFields: FC<CreateFieldsProps> = ({
       name: false,
       dimension: true,
     };
-    setFields([...fields, newDefaultItem]);
+
+    fields.splice(index + 1, 0, newDefaultItem);
+    setFields([...fields]);
     setFieldsValidation(v => [...v, newValidation]);
   };
 
-  const handleRemoveField = (field: Field) => {
-    const newFields = fields.filter(f => f.id !== field.id);
+  const handleRemoveField = (id: string) => {
+    const newFields = fields.filter(f => f.id !== id);
     setFields(newFields);
-    setFieldsValidation(v => v.filter(item => item.id !== field.id));
+    setFieldsValidation(v => v.filter(item => item.id !== id));
   };
 
   const generatePrimaryKeyRow = (
@@ -374,9 +376,10 @@ const CreateFields: FC<CreateFieldsProps> = ({
     const autoIdOff = isVarChar ? 'false' : autoID ? 'true' : 'false';
     return (
       <div className={`${classes.rowWrapper}`}>
+        {generateFieldName(field, collectionTrans('idFieldName'))}
         {getSelector(
           'vector',
-          `Primary ${collectionTrans('fieldType')} `,
+          `${collectionTrans('idType')} `,
           field.data_type,
           (value: DataTypeEnum) => {
             changeFields(field.id!, 'data_type', value);
@@ -386,8 +389,7 @@ const CreateFields: FC<CreateFieldsProps> = ({
           },
           PRIMARY_FIELDS_OPTIONS
         )}
-
-        {generateFieldName(field)}
+        {generateDesc(field)}
 
         <CustomSelector
           label={collectionTrans('autoId')}
@@ -398,53 +400,51 @@ const CreateFields: FC<CreateFieldsProps> = ({
             setAutoID(autoId);
           }}
           variant="filled"
-          wrapperClass={classes.select}
+          wrapperClass={classes.autoIdSelect}
           disabled={isVarChar}
+          size="small"
         />
-        {isVarChar && generateMaxLength(field)}
 
-        {generateDesc(field)}
+        {isVarChar && generateMaxLength(field)}
       </div>
     );
   };
 
-  const generateDefaultVectorRow = (field: Field): ReactElement => {
+  const generateDefaultVectorRow = (
+    field: Field,
+    index: number
+  ): ReactElement => {
     return (
       <>
         <div className={`${classes.rowWrapper}`}>
+          {generateFieldName(field, collectionTrans('vectorFieldName'))}
+
           {getSelector(
             'vector',
-            collectionTrans('fieldType'),
+            `${collectionTrans('vectorType')} `,
             field.data_type,
             (value: DataTypeEnum) => changeFields(field.id!, 'data_type', value)
           )}
-
-          {generateFieldName(field)}
+          {generateDesc(field)}
 
           {generateDimension(field)}
 
-          {generateDesc(field)}
+          <IconButton
+            onClick={() => handleAddNewField(index)}
+            classes={{ root: classes.iconBtn }}
+            aria-label="add"
+          >
+            <AddIcon />
+          </IconButton>
         </div>
-
-        <CustomButton onClick={handleAddNewField} className={classes.mb2}>
-          <AddIcon />
-          <span className={classes.btnTxt}>{collectionTrans('newBtn')}</span>
-        </CustomButton>
       </>
     );
   };
 
-  const generateNumberRow = (field: Field): ReactElement => {
+  const generateNumberRow = (field: Field, index: number): ReactElement => {
     const isVarChar = field.data_type === DataTypeEnum.VarChar;
     return (
       <div className={`${classes.rowWrapper}`}>
-        <IconButton
-          onClick={() => handleRemoveField(field)}
-          classes={{ root: classes.iconBtn }}
-          aria-label="delete"
-        >
-          <RemoveIcon />
-        </IconButton>
         {generateFieldName(field)}
         {getSelector(
           'all',
@@ -452,8 +452,28 @@ const CreateFields: FC<CreateFieldsProps> = ({
           field.data_type,
           (value: DataTypeEnum) => changeFields(field.id!, 'data_type', value)
         )}
-        {isVarChar && generateMaxLength(field)}
         {generateDesc(field)}
+
+        {isVarChar && generateMaxLength(field)}
+        <IconButton
+          onClick={() => {
+            handleAddNewField(index);
+          }}
+          classes={{ root: classes.iconBtn }}
+          aria-label="add"
+        >
+          <AddIcon />
+        </IconButton>
+        <IconButton
+          onClick={() => {
+            const id = field.id || '';
+            handleRemoveField(id);
+          }}
+          classes={{ root: classes.iconBtn }}
+          aria-label="delete"
+        >
+          <RemoveIcon />
+        </IconButton>
       </div>
     );
   };
@@ -478,35 +498,41 @@ const CreateFields: FC<CreateFieldsProps> = ({
     );
   };
 
-  const generateRequiredFieldRow = (field: Field, autoID: boolean) => {
+  const generateRequiredFieldRow = (
+    field: Field,
+    autoID: boolean,
+    index: number
+  ) => {
     // required type is primaryKey or defaultVector
     if (field.createType === 'primaryKey') {
       return generatePrimaryKeyRow(field, autoID);
     }
     // use defaultVector as default return type
-    return generateDefaultVectorRow(field);
+    return generateDefaultVectorRow(field, index);
   };
 
-  const generateOptionalFieldRow = (field: Field) => {
+  const generateOptionalFieldRow = (field: Field, index: number) => {
     // optional type is vector or number
     if (field.createType === 'vector') {
       return generateVectorRow(field);
     }
 
     // use number as default createType
-    return generateNumberRow(field);
+    return generateNumberRow(field, index);
   };
 
   return (
     <>
       {requiredFields.map((field, index) => (
-        <Fragment key={index}>
-          {generateRequiredFieldRow(field, autoID)}
+        <Fragment key={field.id}>
+          {generateRequiredFieldRow(field, autoID, index)}
         </Fragment>
       ))}
       <div className={classes.optionalWrapper}>
         {optionalFields.map((field, index) => (
-          <Fragment key={index}>{generateOptionalFieldRow(field)}</Fragment>
+          <Fragment key={field.id}>
+            {generateOptionalFieldRow(field, index + requiredFields.length)}
+          </Fragment>
         ))}
       </div>
     </>

+ 8 - 0
client/src/pages/collections/Types.ts

@@ -13,6 +13,7 @@ export interface CollectionData {
   _indexState: ChildrenStatusType;
   _fields?: FieldData[];
   _consistencyLevel?: string;
+  _aliases: string[];
 }
 
 export interface CollectionView extends CollectionData {
@@ -120,3 +121,10 @@ export interface DeleteEntitiesReq {
   expr: string;
   partition_name?: string;
 }
+
+export interface AliasesProps {
+  aliases: string[];
+  collectionName: string;
+  onCreate?: Function;
+  onDelete?: Function;
+}

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

@@ -10,7 +10,7 @@ import { useFormValidation } from '../../hooks/Form';
 import { formatForm } from '../../utils/Form';
 import { MilvusHttp } from '../../http/Milvus';
 import { formatAddress } from '../../utils/Format';
-import { useHistory } from 'react-router-dom';
+import { useNavigate } from 'react-router-dom';
 import { rootContext } from '../../context/Root';
 import { authContext } from '../../context/Auth';
 import { MILVUS_ADDRESS } from '../../consts/Localstorage';
@@ -42,7 +42,7 @@ const useStyles = makeStyles((theme: Theme) => ({
     width: '42px',
     height: 'auto',
     marginBottom: '8px',
-    display: 'block'
+    display: 'block',
   },
   input: {
     margin: theme.spacing(3, 0, 0.5),
@@ -54,7 +54,7 @@ const useStyles = makeStyles((theme: Theme) => ({
   },
 }));
 export const AuthForm = (props: any) => {
-  const history = useHistory();
+  const navigate = useNavigate();
   const classes = useStyles();
 
   const { openSnackBar } = useContext(rootContext);
@@ -161,7 +161,7 @@ export const AuthForm = (props: any) => {
 
       openSnackBar(successTrans('connect'));
       window.localStorage.setItem(MILVUS_ADDRESS, address);
-      history.push('/');
+      navigate('/');
     } catch (error: any) {
       if (error?.response?.status === CODE_STATUS.UNAUTHORIZED) {
         showAuthForm

+ 11 - 3
client/src/pages/connect/Connect.tsx

@@ -1,11 +1,19 @@
+import { useContext } from 'react';
+import { Navigate } from 'react-router-dom';
 import ConnectContainer from './ConnectContainer';
 import { AuthForm } from './AuthForm';
+import { authContext } from '../../context/Auth';
 
 const Connect = () => {
+  const { isAuth } = useContext(authContext);
+
   return (
-    <ConnectContainer>
-      <AuthForm />
-    </ConnectContainer>
+    <>
+      {isAuth && <Navigate to="/" replace={true} />}
+      <ConnectContainer>
+        <AuthForm />
+      </ConnectContainer>
+    </>
   );
 };
 

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

@@ -4,7 +4,7 @@ import { ReactElement } from 'react';
 const getContainerStyles = makeStyles((theme: Theme) => ({
   wrapper: {
     width: '100%',
-    height: '90%',
+    height: '100vh',
     backgroundRepeat: 'no-repeat',
     backgroundSize: 'cover',
   },

+ 135 - 0
client/src/pages/index.tsx

@@ -0,0 +1,135 @@
+import { useMemo, useContext } from 'react';
+import { Outlet, useNavigate, useLocation, Navigate } from 'react-router-dom';
+import { useTranslation } from 'react-i18next';
+import { makeStyles, Theme, createStyles } from '@material-ui/core';
+import GlobalEffect from '../components/layout/GlobalEffect';
+import Header from '../components/layout/Header';
+import NavMenu from '../components/menu/NavMenu';
+import { NavMenuItem } from '../components/menu/Types';
+import icons from '../components/icons/Icons';
+import { authContext } from '../context/Auth';
+import { rootContext } from '../context/Root';
+import Overview from '../pages/overview/Overview';
+
+const useStyles = makeStyles((theme: Theme) =>
+  createStyles({
+    root: {
+      minHeight: '100vh',
+      backgroundColor: '#f5f5f5',
+    },
+    content: {
+      display: 'flex',
+
+      '& .active': {
+        '& path': {
+          fill: theme.palette.attuGrey.dark,
+        },
+      },
+
+      '& .normal': {
+        '& path': {
+          fill: theme.palette.primary.main,
+        },
+      },
+    },
+    body: {
+      flex: 1,
+      display: 'flex',
+      flexDirection: 'column',
+      height: '100vh',
+      overflowY: 'scroll',
+    },
+  })
+);
+
+function Index() {
+  const navigate = useNavigate();
+  const { isAuth } = useContext(authContext);
+  const { versionInfo } = useContext(rootContext);
+  const { t: navTrans } = useTranslation('nav');
+  const classes = useStyles();
+  const location = useLocation();
+  const isIndex = location.pathname === '/';
+  const defaultActive = useMemo(() => {
+    if (location.pathname.includes('collection')) {
+      return navTrans('collection');
+    }
+
+    if (location.pathname.includes('search')) {
+      return navTrans('search');
+    }
+
+    if (location.pathname.includes('system')) {
+      return navTrans('system');
+    }
+
+    if (location.pathname.includes('users')) {
+      return navTrans('user');
+    }
+
+    return navTrans('overview');
+  }, [location, navTrans]);
+
+  const menuItems: NavMenuItem[] = [
+    {
+      icon: icons.navOverview,
+      label: navTrans('overview'),
+      onClick: () => navigate('/'),
+    },
+    {
+      icon: icons.navCollection,
+      label: navTrans('collection'),
+      onClick: () => navigate('/collections'),
+    },
+    {
+      icon: icons.navSearch,
+      label: navTrans('search'),
+      onClick: () => navigate('/search'),
+      iconActiveClass: 'normal',
+      iconNormalClass: 'active',
+    },
+    {
+      icon: icons.navSystem,
+      label: navTrans('system'),
+      onClick: () => navigate('/system'),
+      iconActiveClass: 'normal',
+      iconNormalClass: 'active',
+    },
+    {
+      icon: icons.navPerson,
+      label: navTrans('user'),
+      onClick: () => navigate('/users'),
+    },
+  ];
+
+  // check if is connected
+  if (!isAuth) {
+    return <Navigate to="/connect" />;
+  }
+
+  return (
+    <>
+      <div className={classes.root}>
+        <GlobalEffect>
+          <div className={classes.content}>
+            <NavMenu
+              width="200px"
+              data={menuItems}
+              defaultActive={defaultActive}
+              // used for nested child menu
+              defaultOpen={{ [navTrans('overview')]: true }}
+              versionInfo={versionInfo}
+            />
+
+            <div className={classes.body}>
+              <Header />
+              {isIndex ? <Overview /> : <Outlet />}
+            </div>
+          </div>
+        </GlobalEffect>
+      </div>
+    </>
+  );
+}
+
+export default Index;

+ 7 - 6
client/src/pages/overview/collectionCard/CollectionCard.tsx

@@ -7,7 +7,7 @@ import CustomToolTip from '../../../components/customToolTip/CustomToolTip';
 import { CollectionCardProps } from './Types';
 import { useTranslation } from 'react-i18next';
 import CustomIconButton from '../../../components/customButton/CustomIconButton';
-import { useHistory, Link } from 'react-router-dom';
+import { useNavigate, Link } from 'react-router-dom';
 import { LOADING_STATE } from '../../../consts/Milvus';
 
 const useStyles = makeStyles((theme: Theme) => ({
@@ -84,7 +84,7 @@ const CollectionCard: FC<CollectionCardProps> = ({
     _rowCount: rowCount,
     _loadedPercentage,
   } = data;
-  const history = useHistory();
+  const navigate = useNavigate();
   // icons
   const RightArrowIcon = icons.rightArrow;
   const InfoIcon = icons.info;
@@ -99,13 +99,14 @@ const CollectionCard: FC<CollectionCardProps> = ({
   };
 
   const onVectorSearchClick = () => {
-    history.push({ pathname: '/search', search: `?collectionName=${name}` });
+    navigate({ pathname: '/search', search: `?collectionName=${name}` });
   };
 
   return (
     <div
-      className={`card-wrapper ${classes.wrapper} ${wrapperClass} ${data._status === LOADING_STATE.LOADING && classes.loading
-        }`}
+      className={`card-wrapper ${classes.wrapper} ${wrapperClass} ${
+        data._status === LOADING_STATE.LOADING && classes.loading
+      }`}
     >
       <div>
         <Status status={status} percentage={_loadedPercentage} />
@@ -116,7 +117,7 @@ const CollectionCard: FC<CollectionCardProps> = ({
       </Link>
       <div className={classes.content}>
         <Typography>{collectionTrans('rowCount')}</Typography>
-        <CustomToolTip title={collectionTrans('tooltip')} placement="bottom">
+        <CustomToolTip title={collectionTrans('entityCountInfo')} placement="bottom">
           <InfoIcon classes={{ root: classes.icon }} />
         </CustomToolTip>
         <Typography className={classes.rowCount}>{rowCount}</Typography>

+ 42 - 62
client/src/pages/partitions/Partitions.tsx

@@ -1,10 +1,7 @@
 import { makeStyles, Theme } from '@material-ui/core';
-import { FC, useCallback, useContext, useEffect, useState } from 'react';
-import {
-  PartitionManageParam,
-  // PartitionParam,
-  PartitionView,
-} from './Types';
+import { FC, useContext, useEffect, useState } from 'react';
+import { useSearchParams } from 'react-router-dom';
+import { PartitionManageParam, PartitionView } from './Types';
 import AttuGrid from '../../components/grid/Grid';
 import { ColDefinitionsType, ToolBarConfig } from '../../components/grid/Types';
 import { useTranslation } from 'react-i18next';
@@ -14,13 +11,9 @@ import CustomToolTip from '../../components/customToolTip/CustomToolTip';
 import { rootContext } from '../../context/Root';
 import CreatePartition from './Create';
 import { PartitionHttp } from '../../http/Partition';
-import Status from '../../components/status/Status';
 import { ManageRequestMethods } from '../../types/Common';
-// import { StatusEnum } from '../../components/status/Types';
-// import { useDialogHook } from '../../hooks/Dialog';
 import DeleteTemplate from '../../components/customDialog/DeleteDialogTemplate';
 import Highlighter from 'react-highlight-words';
-import { parseLocationSearch } from '../../utils/Format';
 import { useInsertDialogHook } from '../../hooks/Dialog';
 import InsertContainer from '../../components/insert/Container';
 import { CollectionHttp } from '../../http/Collection';
@@ -45,8 +38,7 @@ const useStyles = makeStyles((theme: Theme) => ({
 
 let timer: NodeJS.Timeout | null = null;
 // get init search value from url
-const { search = '' } = parseLocationSearch(window.location.search);
-
+// const { search = '' } = parseLocationSearch(window.location.search);
 const Partitions: FC<{
   collectionName: string;
 }> = ({ collectionName }) => {
@@ -55,6 +47,10 @@ const Partitions: FC<{
   const { t: successTrans } = useTranslation('success');
   const { t: btnTrans } = useTranslation('btn');
   const { t: dialogTrans } = useTranslation('dialog');
+  const [searchParams] = useSearchParams();
+  const [search, setSearch] = useState<string>(
+    (searchParams.get('search') as string) || ''
+  );
   const InfoIcon = icons.info;
 
   const { handleInsertDialog } = useInsertDialogHook();
@@ -81,35 +77,15 @@ const Partitions: FC<{
   const { setDialog, handleCloseDialog, openSnackBar } =
     useContext(rootContext);
 
-  const fetchPartitions = useCallback(
-    async (collectionName: string) => {
-      try {
-        const res = await PartitionHttp.getPartitions(collectionName);
-
-        const partitions: PartitionView[] = res.map(p =>
-          Object.assign(p, {
-            _nameElement: (
-              <Highlighter
-                textToHighlight={p._formatName}
-                searchWords={[search]}
-                highlightClassName={classes.highlight}
-              />
-            ),
-            _statusElement: <Status status={p._status} />,
-          })
-        );
-        const filteredPartitions = partitions.filter(p =>
-          p._formatName.includes(search)
-        );
-        setLoading(false);
-        setPartitions(partitions);
-        setSearchedPartitions(filteredPartitions);
-      } catch (err) {
-        setLoading(false);
-      }
-    },
-    [classes.highlight]
-  );
+  const fetchPartitions = async (collectionName: string) => {
+    try {
+      const res = await PartitionHttp.getPartitions(collectionName);
+      setLoading(false);
+      setPartitions(res);
+    } catch (err) {
+      setLoading(false);
+    }
+  };
 
   const fetchCollectionDetail = async (name: string) => {
     const res = await CollectionHttp.getCollection(name);
@@ -118,33 +94,18 @@ const Partitions: FC<{
 
   useEffect(() => {
     fetchPartitions(collectionName);
-  }, [collectionName, fetchPartitions]);
-
-  const handleDelete = async () => {
-    for (const partition of selectedPartitions) {
-      const param: PartitionManageParam = {
-        partitionName: partition._name,
-        collectionName,
-        type: ManageRequestMethods.DELETE,
-      };
-      await PartitionHttp.managePartition(param);
-    }
-
-    openSnackBar(successTrans('delete', { name: t('partition') }));
-    fetchPartitions(collectionName);
-    handleCloseDialog();
-  };
+  }, [collectionName]);
 
-  const handleSearch = (value: string) => {
+  useEffect(() => {
     if (timer) {
       clearTimeout(timer);
     }
     // add loading manually
     setLoading(true);
     timer = setTimeout(() => {
-      const searchWords = [value];
-      const list = value
-        ? partitions.filter(p => p._formatName.includes(value))
+      const searchWords = [search];
+      const list = search
+        ? partitions.filter(p => p._formatName.includes(search))
         : partitions;
 
       const highlightList = list.map(c => {
@@ -162,6 +123,25 @@ const Partitions: FC<{
       setLoading(false);
       setSearchedPartitions(highlightList);
     }, 300);
+  }, [search, partitions]);
+
+  const handleDelete = async () => {
+    for (const partition of selectedPartitions) {
+      const param: PartitionManageParam = {
+        partitionName: partition._name,
+        collectionName,
+        type: ManageRequestMethods.DELETE,
+      };
+      await PartitionHttp.managePartition(param);
+    }
+
+    openSnackBar(successTrans('delete', { name: t('partition') }));
+    fetchPartitions(collectionName);
+    handleCloseDialog();
+  };
+
+  const handleSearch = (value: string) => {
+    setSearch(value);
   };
 
   const handleInsert = async (
@@ -248,7 +228,7 @@ const Partitions: FC<{
           params: {
             component: (
               <DeleteTemplate
-                label={btnTrans('delete')}
+                label={btnTrans('drop')}
                 title={dialogTrans('deleteTitle', { type: t('partition') })}
                 text={t('deleteWarning')}
                 handleDelete={handleDelete}

+ 7 - 9
client/src/pages/preview/Preview.tsx

@@ -11,6 +11,7 @@ import { ToolBarConfig } from '../../components/grid/Types';
 import CustomToolBar from '../../components/grid/ToolBar';
 import { DataTypeStringEnum } from '../collections/Types';
 import { generateVector } from '../../utils/Common';
+import { DataTypeEnum } from '../../pages/collections/Types';
 
 import {
   INDEX_CONFIG,
@@ -89,10 +90,11 @@ const Preview: FC<{
     const vectorField = schemaList.find(
       v => v.data_type === 'FloatVector' || v.data_type === 'BinaryVector'
     );
-
     const anns_field = vectorField?._fieldName!;
     const dim = Number(vectorField?._dimension);
-    const vectors = [generateVector(dim)];
+    const vectors = [
+      generateVector(vectorField?.data_type === 'FloatVector' ? dim : dim / 8),
+    ];
     // get search params
     const indexesInfo = await IndexHttp.getIndexInfo(collectionName);
     const indexType =
@@ -105,13 +107,9 @@ const Preview: FC<{
     const searchParam = { [searchParamKey]: searchParamValue };
     const params = `${JSON.stringify(searchParam)}`;
     setPrimaryKey(primaryKey);
-    // Temporarily hide bool field due to incorrect return from SDK.
-    const fieldWithoutBool = nameList.filter(
-      i => i.type !== DataTypeStringEnum.Bool
-    );
 
     // set fields
-    setFields(fieldWithoutBool);
+    setFields(nameList);
 
     // set loading
     setTableLoading(true);
@@ -128,7 +126,7 @@ const Preview: FC<{
         expr: '',
         vectors,
         output_fields: [primaryKey],
-        vector_type: Number(vectorField?._fieldId),
+        vector_type: DataTypeEnum[vectorField!._fieldType],
       });
 
       // compose random id list expression
@@ -139,7 +137,7 @@ const Preview: FC<{
       // query by random id
       const res = await CollectionHttp.queryData(collectionName, {
         expr: expr,
-        output_fields: fieldWithoutBool.map(i => i.name),
+        output_fields: nameList.map(i => i.name),
       });
 
       const result = res.data;

+ 2 - 6
client/src/pages/query/Query.tsx

@@ -115,11 +115,7 @@ const Query: FC<{
     const primaryKey =
       schemaList.find(v => v._isPrimaryKey === true)?._fieldName || '';
     setPrimaryKey(primaryKey);
-    // Temporarily hide bool field due to incorrect return from SDK.
-    const fieldWithoutBool = nameList.filter(
-      i => i.type !== DataTypeStringEnum.Bool
-    );
-    setFields(fieldWithoutBool);
+    setFields(nameList);
   };
 
   // Get fields at first or collection name changed.
@@ -185,7 +181,7 @@ const Query: FC<{
           params: {
             component: (
               <DeleteTemplate
-                label={btnTrans('delete')}
+                label={btnTrans('drop')}
                 title={dialogTrans('deleteTitle', {
                   type: collectionTrans('entites'),
                 })}

+ 1 - 0
client/src/pages/schema/Create.tsx

@@ -256,6 +256,7 @@ const CreateIndex = (props: {
     // setDisabled(true);
     setIndexSetting(v => ({
       ...v,
+      index_name: '',
       metric_type: defaultMetricType,
       M: '',
       m: '4',

+ 1 - 0
client/src/pages/schema/CreateForm.tsx

@@ -174,6 +174,7 @@ const CreateForm = (
         type="text"
         textConfig={indexNameConfig}
         checkValid={checkIsValid}
+        validInfo={validation}
       />
       <CustomSelector
         label={indexTrans('type')}

+ 1 - 1
client/src/pages/schema/IndexTypeElement.tsx

@@ -183,7 +183,7 @@ const IndexTypeElement: FC<{
       params: {
         component: (
           <DeleteTemplate
-            label={btnTrans('delete')}
+            label={btnTrans('drop')}
             title={dialogTrans('deleteTitle', { type: indexTrans('index') })}
             text={indexTrans('deleteWarning')}
             handleDelete={requestDeleteIndex}

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


+ 5 - 6
client/src/plugins/search/SearchParams.tsx → client/src/pages/search/SearchParams.tsx

@@ -176,7 +176,7 @@ const SearchParams: FC<SearchParamsProps> = ({
           max: 10,
           isInt: true,
           handleChange: value => {
-            handleInputChange('round_decimal', value);
+            handleInputChange('round_decimal', Number(value));
           },
           className: classes.inlineInput,
         },
@@ -188,11 +188,10 @@ const SearchParams: FC<SearchParamsProps> = ({
           max: nlist,
           isInt: true,
           handleChange: value => {
-            handleInputChange('nprobe', value);
+            handleInputChange('nprobe', Number(value));
           },
           className: classes.inlineInput,
         },
-
         ef: {
           label: 'ef',
           key: 'ef',
@@ -201,7 +200,7 @@ const SearchParams: FC<SearchParamsProps> = ({
           max: 32768,
           isInt: true,
           handleChange: value => {
-            handleInputChange('ef', value);
+            handleInputChange('ef', Number(value));
           },
         },
         search_k: {
@@ -213,7 +212,7 @@ const SearchParams: FC<SearchParamsProps> = ({
           max: Infinity,
           isInt: true,
           handleChange: value => {
-            handleInputChange('search_k', value);
+            handleInputChange('search_k', Number(value));
           },
         },
         search_length: {
@@ -224,7 +223,7 @@ const SearchParams: FC<SearchParamsProps> = ({
           max: 300,
           isInt: true,
           handleChange: value => {
-            handleInputChange('search_length', value);
+            handleInputChange('search_length', Number(value));
           },
         },
       };

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


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

@@ -3,8 +3,8 @@ import { searchKeywordsType } from '../../consts/Milvus';
 import {
   DataTypeEnum,
   DataTypeStringEnum,
-} from '../../pages/collections/Types';
-import { IndexView } from '../../pages/schema/Types';
+} from '../collections/Types';
+import { IndexView } from '../schema/Types';
 
 export interface SearchParamsProps {
   // if user created index, pass metric type choosed when creating

+ 5 - 2
client/src/plugins/search/VectorSearch.tsx → client/src/pages/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 '../../pages/collections/Types';
+import { CollectionData, DataTypeEnum } from '../collections/Types';
 import { IndexHttp } from '../../http/Index';
 import { getVectorSearchStyles } from './Styles';
 import { parseValue } from '../../utils/Insert';
@@ -33,6 +33,7 @@ import Filter from '../../components/advancedSearch';
 import { Field } from '../../components/advancedSearch/Types';
 import { useLocation } from 'react-router-dom';
 import { parseLocationSearch } from '../../utils/Format';
+import { cloneObj } from '../../utils/Common';
 import { CustomDatePicker } from '../../components/customDatePicker/CustomDatePicker';
 import { useTimeTravelHook } from '../../hooks/TimeTravel';
 
@@ -296,8 +297,10 @@ const VectorSearch = () => {
   };
 
   const handleSearch = async (topK: number, expr = expression) => {
+    const clonedSearchParams = cloneObj(searchParam);
+    delete clonedSearchParams.round_decimal;
     const searhParamPairs = {
-      params: JSON.stringify(searchParam),
+      params: JSON.stringify(clonedSearchParams),
       anns_field: selectedField,
       topk: topK,
       metric_type: selectedMetricType,

+ 0 - 0
client/src/plugins/system/BaseCard.tsx → client/src/pages/system/BaseCard.tsx


+ 0 - 0
client/src/plugins/system/DataCard.tsx → client/src/pages/system/DataCard.tsx


+ 0 - 0
client/src/plugins/system/LineChartCard.tsx → client/src/pages/system/LineChartCard.tsx


+ 0 - 0
client/src/plugins/system/MiniTopology.tsx → client/src/pages/system/MiniTopology.tsx


+ 0 - 0
client/src/plugins/system/NodeListView.tsx → client/src/pages/system/NodeListView.tsx


+ 0 - 0
client/src/plugins/system/Progress.tsx → client/src/pages/system/Progress.tsx


+ 0 - 0
client/src/plugins/system/ProgressCard.tsx → client/src/pages/system/ProgressCard.tsx


+ 0 - 0
client/src/plugins/system/SystemView.tsx → client/src/pages/system/SystemView.tsx


+ 0 - 0
client/src/plugins/system/Topology.tsx → client/src/pages/system/Topology.tsx


+ 0 - 0
client/src/plugins/system/Types.ts → client/src/pages/system/Types.ts


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

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

+ 0 - 11
client/src/plugins/system/config.json

@@ -1,11 +0,0 @@
-{
-  "name": "system-view",
-  "version": "0.1.0",
-  "client": {
-    "path": "system",
-    "entry": "SystemView",
-    "label": "System View",
-    "iconName": "navSystem",
-    "auth": true
-  }
-}

+ 0 - 58
client/src/router/Config.ts

@@ -1,58 +0,0 @@
-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 { RouterConfigType } from './Types';
-import loadable from '@loadable/component';
-import Users from '../pages/user/User';
-
-const RouterConfig: RouterConfigType[] = [
-  {
-    path: '/',
-    component: Overview,
-    auth: true,
-  },
-  {
-    path: '/connect',
-    component: Connect,
-    auth: false,
-  },
-  {
-    path: '/collections',
-    component: Collections,
-    auth: true,
-  },
-  {
-    path: '/collections/:collectionName',
-    component: Collection,
-    auth: true,
-  },
-  { path: '/users', component: Users, auth: true },
-];
-
-async function importAll(r: any) {
-  Object.keys(r).forEach((key: any) => {
-    const content = r[key];
-    const dirName = key.split('/config.json').shift().split('/')[1];
-    const pathName = content.client?.path;
-    const fileEntry = content.client?.entry;
-    if (!pathName || !fileEntry) return;
-    const auth = content.client?.auth || false;
-    const OtherComponent = loadable(
-      () => import(`../${dirName}/${pathName}/${fileEntry}.tsx`)
-    );
-    RouterConfig.push({
-      path: `/${pathName}`,
-      component: OtherComponent,
-      auth,
-    });
-  });
-}
-
-const pluginConfigs = import.meta.glob(`../plugins/**/config.json`, {
-  eager: true,
-});
-importAll(pluginConfigs);
-
-export default RouterConfig;

+ 40 - 51
client/src/router/Router.tsx

@@ -1,54 +1,43 @@
-import { Switch, Route, Redirect, HashRouter } from 'react-router-dom';
-import routerConfig from './Config';
-import Layout from '../components/layout/Layout';
-import { useContext } from 'react';
-import { authContext } from '../context/Auth';
-/**
- * Global responsible for global effect
- * Layout responsible for ui view
- *
- */
-const RouterWrapper = () => {
-  const { isAuth } = useContext(authContext);
+import { createHashRouter, RouterProvider } from 'react-router-dom';
+import Collection from '../pages/collections/Collection';
+import Collections from '../pages/collections/Collections';
+import Connect from '../pages/connect/Connect';
+import Users from '../pages/user/User';
+import Index from '../pages/index';
+import Search from '../pages/search/VectorSearch';
+import System from '../pages/system/SystemView';
 
-  return (
-    <HashRouter>
-      <Layout>
-        <Switch>
-          {routerConfig.map(v => (
-            <Route
-              exact
-              key={v.path}
-              path={v.path}
-              render={() => {
-                const Page = v.component;
-                return isAuth || !v.auth ? (
-                  <Page />
-                ) : (
-                  <Redirect
-                    to={{
-                      pathname: '/connect',
-                    }}
-                  />
-                );
-              }}
-            />
-          ))}
+const router = createHashRouter([
+  {
+    path: '/',
+    element: <Index />,
+    children: [
+      {
+        path: '/collections',
+        element: <Collections />,
+      },
+      {
+        path: '/collections/:collectionName',
+        element: <Collection />,
+      },
+      {
+        path: '/users',
+        element: <Users />,
+      },
+      {
+        path: '/search',
+        element: <Search />,
+      },
+      {
+        path: '/system',
+        element: <System />,
+      },
+    ],
+  },
+  { path: '/connect', element: <Connect /> },
+]);
 
-          <Route
-            render={() => {
-              return (
-                <Redirect
-                  to={{
-                    pathname: '/connect',
-                  }}
-                />
-              );
-            }}
-          ></Route>
-        </Switch>
-      </Layout>
-    </HashRouter>
-  );
+const Router = () => {
+  return <RouterProvider router={router}></RouterProvider>;
 };
-export default RouterWrapper;
+export default Router;

+ 0 - 6
client/src/router/Types.ts

@@ -18,9 +18,3 @@ export type NavInfo = {
   navTitle: string;
   backPath: string;
 };
-
-export type RouterConfigType = {
-  path: string;
-  component: any;
-  auth: boolean;
-};

+ 5 - 1
client/src/utils/Common.ts

@@ -63,5 +63,9 @@ export const generateIdByHash = (salt?: string) => {
 };
 
 export const generateVector = (dim: number) => {
-  return Array.from({ length: dim }).map(() => Math.random());
+  return Array.from({ length: dim }).map(() => (Math.random() > 0.5 ? 1 : 0));
+};
+
+export const cloneObj = (obj: any) => {
+  return JSON.parse(JSON.stringify(obj));
 };

+ 63 - 126
client/yarn.lock

@@ -378,7 +378,7 @@
     "@babel/plugin-syntax-jsx" "^7.18.6"
     "@babel/types" "^7.19.0"
 
-"@babel/runtime@^7.1.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.8", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.7", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.7":
+"@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.8", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.7", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.7":
   version "7.16.0"
   resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.0.tgz#e27b977f2e2088ba24748bf99b5e1dece64e4f0b"
   integrity sha512-Nht8L0O8YCktmsDV6FqFue7vQLRx3Hb0B37lS5y0jDRqRxlBG4wIJHnf9/bgSE2UyipKFA01YtS+npRdTWBUyw==
@@ -692,6 +692,11 @@
     prop-types "^15.7.2"
     reselect "^4.0.0"
 
+"@remix-run/router@1.0.3":
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.0.3.tgz#953b88c20ea00d0eddaffdc1b115c08474aa295d"
+  integrity sha512-ceuyTSs7PZ/tQqi19YZNBc5X7kj1f8p+4DIyrcIYFY9h+hd1OKm4RqtiWldR9eGEvIiJfsqwM4BsuCtRIuEw6Q==
+
 "@rollup/pluginutils@^4.1.1":
   version "4.2.1"
   resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz#e6c6c3aba0744edce3fb2074922d3776c0af2a6d"
@@ -884,6 +889,11 @@
   resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.9.tgz#1cfb6d60ef3822c589f18e70f8b12f9a28ce8724"
   integrity sha512-MUc6zSmU3tEVnkQ78q0peeEjKWPUADMlC/t++2bI8WnAG2tvYRPIgHG8lWkXwqc8MsUF6Z2MOf+Mh5sazOmhiQ==
 
+"@types/history@^4.7.11":
+  version "4.7.11"
+  resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64"
+  integrity sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==
+
 "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762"
@@ -950,13 +960,20 @@
   resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11"
   integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==
 
-"@types/react-dom@>=16.9.0", "@types/react-dom@^17.0.0":
+"@types/react-dom@>=16.9.0":
   version "17.0.11"
   resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.11.tgz#e1eadc3c5e86bdb5f7684e00274ae228e7bcc466"
   integrity sha512-f96K3k+24RaLGVu/Y2Ng3e1EbZ8/cVJvypZWd7cy0ofCBaf2lcM46xNhycMZ2xGwbBjRql7hOlZ+e2WlJ5MH3Q==
   dependencies:
     "@types/react" "*"
 
+"@types/react-dom@^18.0.8":
+  version "18.0.8"
+  resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.8.tgz#d2606d855186cd42cc1b11e63a71c39525441685"
+  integrity sha512-C3GYO0HLaOkk9dDAz3Dl4sbe4AKUGTCfFIZsz3n/82dPNN8Du533HzKatDxeUYWu24wJgMP1xICqkWk1YOLOIw==
+  dependencies:
+    "@types/react" "*"
+
 "@types/react-highlight-words@^0.16.2":
   version "0.16.3"
   resolved "https://registry.yarnpkg.com/@types/react-highlight-words/-/react-highlight-words-0.16.3.tgz#a62e8d31197a33559c38fcbe4c7c1e10d3f78cd8"
@@ -971,12 +988,12 @@
   dependencies:
     "@types/react" "*"
 
-"@types/react-router-dom@^5.1.7":
-  version "5.3.2"
-  resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.2.tgz#ebd8e145cf056db5c66eb1dac63c72f52e8542ee"
-  integrity sha512-ELEYRUie2czuJzaZ5+ziIp9Hhw+juEw8b7C11YNA4QdLCVbQ3qLi2l4aq8XnlqM7V31LZX8dxUuFUCrzHm6sqQ==
+"@types/react-router-dom@^5.3.3":
+  version "5.3.3"
+  resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.3.tgz#e9d6b4a66fcdbd651a5f106c2656a30088cc1e83"
+  integrity sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==
   dependencies:
-    "@types/history" "*"
+    "@types/history" "^4.7.11"
     "@types/react" "*"
     "@types/react-router" "*"
 
@@ -1009,7 +1026,7 @@
   dependencies:
     "@types/react" "*"
 
-"@types/react@*", "@types/react@>=16.9.0", "@types/react@^17.0.0":
+"@types/react@*", "@types/react@>=16.9.0":
   version "17.0.34"
   resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.34.tgz#797b66d359b692e3f19991b6b07e4b0c706c0102"
   integrity sha512-46FEGrMjc2+8XhHXILr+3+/sTe3OfzSPU9YGKILLrUYbQ1CLQC9Daqo1KzENGXAWwrFwiY0l4ZbF20gRvgpWTg==
@@ -1018,6 +1035,15 @@
     "@types/scheduler" "*"
     csstype "^3.0.2"
 
+"@types/react@^18.0.25":
+  version "18.0.25"
+  resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.25.tgz#8b1dcd7e56fe7315535a4af25435e0bb55c8ae44"
+  integrity sha512-xD6c0KDT4m7n9uD4ZHi02lzskaiqcBxf4zi+tXZY98a04wvc0hi/TcCPC2FOESZi51Nd7tlUeOJY8RofL799/g==
+  dependencies:
+    "@types/prop-types" "*"
+    "@types/scheduler" "*"
+    csstype "^3.0.2"
+
 "@types/scheduler@*":
   version "0.16.2"
   resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39"
@@ -2030,19 +2056,7 @@ highlight.js@^10.4.1, highlight.js@~10.7.0:
   resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531"
   integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==
 
-history@^4.9.0:
-  version "4.10.1"
-  resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3"
-  integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==
-  dependencies:
-    "@babel/runtime" "^7.1.2"
-    loose-envify "^1.2.0"
-    resolve-pathname "^3.0.0"
-    tiny-invariant "^1.0.2"
-    tiny-warning "^1.0.0"
-    value-equal "^1.0.1"
-
-hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2:
+hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2:
   version "3.3.2"
   resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
   integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
@@ -2227,23 +2241,11 @@ is-number@^7.0.0:
   resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
   integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
 
-is-plain-object@^2.0.4:
-  version "2.0.4"
-  resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
-  integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==
-  dependencies:
-    isobject "^3.0.1"
-
 is-potential-custom-element-name@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5"
   integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==
 
-is-primitive@^3.0.1:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-3.0.1.tgz#98c4db1abff185485a657fc2905052b940524d05"
-  integrity sha512-GljRxhWvlCNRfZyORiH77FwdFwGcMO620o37EOYC0ORWdq+WYNVqW0w2Juzew4M+L81l6/QS3t5gkkihyRqv9w==
-
 is-regex@^1.1.4:
   version "1.1.4"
   resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958"
@@ -2295,11 +2297,6 @@ is-weakset@^2.0.1:
     call-bind "^1.0.2"
     get-intrinsic "^1.1.1"
 
-isarray@0.0.1:
-  version "0.0.1"
-  resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
-  integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
-
 isarray@^2.0.5:
   version "2.0.5"
   resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
@@ -2310,11 +2307,6 @@ isexe@^2.0.0:
   resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
   integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
 
-isobject@^3.0.1:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
-  integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8=
-
 istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3"
@@ -2548,7 +2540,7 @@ lodash@^4.17.15:
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
   integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
 
-loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0:
+loose-envify@^1.1.0, loose-envify@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
   integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@@ -2619,14 +2611,6 @@ min-indent@^1.0.0:
   resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
   integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
 
-mini-create-react-context@^0.4.0:
-  version "0.4.1"
-  resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz#072171561bfdc922da08a60c2197a497cc2d1d5e"
-  integrity sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==
-  dependencies:
-    "@babel/runtime" "^7.12.1"
-    tiny-warning "^1.0.3"
-
 minimatch@^3.0.4, minimatch@^3.1.1:
   version "3.1.2"
   resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
@@ -2801,13 +2785,6 @@ path-parse@^1.0.7:
   resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
   integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
 
-path-to-regexp@^1.7.0:
-  version "1.8.0"
-  resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a"
-  integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==
-  dependencies:
-    isarray "0.0.1"
-
 path-type@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
@@ -2906,14 +2883,13 @@ querystringify@^2.1.1:
   resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
   integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
 
-react-dom@^17.0.2:
-  version "17.0.2"
-  resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"
-  integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==
+react-dom@^18.2.0:
+  version "18.2.0"
+  resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
+  integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
   dependencies:
     loose-envify "^1.1.0"
-    object-assign "^4.1.1"
-    scheduler "^0.20.2"
+    scheduler "^0.23.0"
 
 react-error-boundary@^3.1.0:
   version "3.1.4"
@@ -2939,7 +2915,7 @@ react-i18next@^12.0.0:
     "@babel/runtime" "^7.14.5"
     html-parse-stringify "^3.0.1"
 
-react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1:
+react-is@^16.12.0, react-is@^16.7.0, react-is@^16.8.1:
   version "16.13.1"
   resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
   integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@@ -2964,34 +2940,20 @@ react-refresh@^0.14.0:
   resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e"
   integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==
 
-react-router-dom@^5.2.0:
-  version "5.3.0"
-  resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.3.0.tgz#da1bfb535a0e89a712a93b97dd76f47ad1f32363"
-  integrity sha512-ObVBLjUZsphUUMVycibxgMdh5jJ1e3o+KpAZBVeHcNQZ4W+uUGGWsokurzlF4YOldQYRQL4y6yFRWM4m3svmuQ==
+react-router-dom@^6.4.3:
+  version "6.4.3"
+  resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.4.3.tgz#70093b5f65f85f1df9e5d4182eb7ff3a08299275"
+  integrity sha512-MiaYQU8CwVCaOfJdYvt84KQNjT78VF0TJrA17SIQgNHRvLnXDJO6qsFqq8F/zzB1BWZjCFIrQpu4QxcshitziQ==
   dependencies:
-    "@babel/runtime" "^7.12.13"
-    history "^4.9.0"
-    loose-envify "^1.3.1"
-    prop-types "^15.6.2"
-    react-router "5.2.1"
-    tiny-invariant "^1.0.2"
-    tiny-warning "^1.0.0"
+    "@remix-run/router" "1.0.3"
+    react-router "6.4.3"
 
-react-router@5.2.1:
-  version "5.2.1"
-  resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.2.1.tgz#4d2e4e9d5ae9425091845b8dbc6d9d276239774d"
-  integrity sha512-lIboRiOtDLFdg1VTemMwud9vRVuOCZmUIT/7lUoZiSpPODiiH1UQlfXy+vPLC/7IWdFYnhRwAyNqA/+I7wnvKQ==
-  dependencies:
-    "@babel/runtime" "^7.12.13"
-    history "^4.9.0"
-    hoist-non-react-statics "^3.1.0"
-    loose-envify "^1.3.1"
-    mini-create-react-context "^0.4.0"
-    path-to-regexp "^1.7.0"
-    prop-types "^15.6.2"
-    react-is "^16.6.0"
-    tiny-invariant "^1.0.2"
-    tiny-warning "^1.0.0"
+react-router@6.4.3:
+  version "6.4.3"
+  resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.4.3.tgz#9ed3ee4d6e95889e9b075a5d63e29acc7def0d49"
+  integrity sha512-BT6DoGn6aV1FVP5yfODMOiieakp3z46P1Fk0RNzJMACzE7C339sFuHebfvWtnB4pzBvXXkHP2vscJzWRuUjTtA==
+  dependencies:
+    "@remix-run/router" "1.0.3"
 
 react-syntax-highlighter@^15.4.4:
   version "15.4.4"
@@ -3014,13 +2976,12 @@ react-transition-group@^4.0.0, react-transition-group@^4.4.0:
     loose-envify "^1.4.0"
     prop-types "^15.6.2"
 
-react@^17.0.2:
-  version "17.0.2"
-  resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
-  integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==
+react@^18.2.0:
+  version "18.2.0"
+  resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
+  integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
   dependencies:
     loose-envify "^1.1.0"
-    object-assign "^4.1.1"
 
 redent@^3.0.0:
   version "3.0.0"
@@ -3078,11 +3039,6 @@ resolve-from@^4.0.0:
   resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
   integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
 
-resolve-pathname@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd"
-  integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==
-
 resolve@^1.22.1:
   version "1.22.1"
   resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
@@ -3130,27 +3086,18 @@ saxes@^6.0.0:
   dependencies:
     xmlchars "^2.2.0"
 
-scheduler@^0.20.2:
-  version "0.20.2"
-  resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91"
-  integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==
+scheduler@^0.23.0:
+  version "0.23.0"
+  resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe"
+  integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==
   dependencies:
     loose-envify "^1.1.0"
-    object-assign "^4.1.1"
 
 semver@^6.0.0, semver@^6.3.0:
   version "6.3.0"
   resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
   integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
 
-set-value@^4.1.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/set-value/-/set-value-4.1.0.tgz#aa433662d87081b75ad88a4743bd450f044e7d09"
-  integrity sha512-zTEg4HL0RwVrqcWs3ztF+x1vkxfm0lP+MQQFPiMJTKVceBwEV0A569Ou8l9IYQG8jOZdMVI1hGsc0tmeD2o/Lw==
-  dependencies:
-    is-plain-object "^2.0.4"
-    is-primitive "^3.0.1"
-
 shebang-command@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
@@ -3302,12 +3249,7 @@ test-exclude@^6.0.0:
     glob "^7.1.4"
     minimatch "^3.0.4"
 
-tiny-invariant@^1.0.2:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.2.0.tgz#a1141f86b672a9148c72e978a19a73b9b94a15a9"
-  integrity sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg==
-
-tiny-warning@^1.0.0, tiny-warning@^1.0.2, tiny-warning@^1.0.3:
+tiny-warning@^1.0.2:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
   integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
@@ -3403,11 +3345,6 @@ v8-to-istanbul@^9.0.0:
     "@types/istanbul-lib-coverage" "^2.0.1"
     convert-source-map "^1.6.0"
 
-value-equal@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c"
-  integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==
-
 vite-plugin-svgr@^0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/vite-plugin-svgr/-/vite-plugin-svgr-0.3.0.tgz#c81ac7541df98d361249f9ac06b901ae22744a1b"

+ 2 - 2
server/package.json

@@ -1,6 +1,6 @@
 {
   "name": "attu",
-  "version": "2.1.4",
+  "version": "2.2.0",
   "license": "MIT",
   "author": {
     "name": "zilliz",
@@ -12,7 +12,7 @@
     "url": "https://github.com/zilliztech/attu"
   },
   "dependencies": {
-    "@zilliz/milvus2-sdk-node": "^2.1.3",
+    "@zilliz/milvus2-sdk-node": "^2.2.0",
     "chalk": "^4.1.2",
     "class-sanitizer": "^1.0.1",
     "class-transformer": "^0.4.0",

+ 11 - 0
server/src/collections/collections.controller.ts

@@ -47,6 +47,7 @@ export class CollectionController {
     );
 
     this.router.delete('/:name', this.dropCollection.bind(this));
+    this.router.delete('/:name/alias/:alias', this.dropAlias.bind(this));
 
     this.router.get('/:name', this.describeCollection.bind(this));
 
@@ -294,4 +295,14 @@ export class CollectionController {
       next(error);
     }
   }
+
+  async dropAlias(req: Request, res: Response, next: NextFunction) {
+    const alias = req.params?.alias;
+    try {
+      const result = await this.collectionsService.dropAlias({ alias });
+      res.send(result);
+    } catch (error) {
+      next(error);
+    }
+  }
 }

+ 1 - 0
server/src/collections/collections.service.ts

@@ -176,6 +176,7 @@ export class CollectionsService {
           : loadCollection.loadedPercentage;
 
         data.push({
+          aliases: collectionInfo.aliases,
           collection_name: name,
           schema: collectionInfo.schema,
           description: collectionInfo.schema.description,

+ 3 - 1
server/src/collections/dto.ts

@@ -72,7 +72,9 @@ export class VectorSearchDto {
   @IsOptional()
   output_fields?: string[];
 
-  @IsEnum(VectorTypes, { message: 'Type allow all->0 inmemory->1' })
+  @IsEnum(VectorTypes, {
+    message: ({ value }) => `Wrong vector type, ${value}`,
+  })
   vector_type: DataType.BinaryVector | DataType.FloatVector;
 }
 

+ 4 - 0
server/src/utils/Helper.ts

@@ -20,6 +20,10 @@ export const genDataByType = ({ data_type, type_params }: FieldSchema) => {
       return Array.from({ length: (type_params as any)[0].value }).map(() =>
         Math.random()
       );
+    case 'BinaryVector':
+      return Array.from({ length: (type_params as any)[0].value / 8 }).map(() =>
+        Math.random() > 0.5 ? 1 : 0
+      );
     case 'VarChar':
       return makeRandomId((type_params as any)[0].value);
   }

+ 28 - 13
server/yarn.lock

@@ -412,7 +412,7 @@
     "@grpc/proto-loader" "^0.6.4"
     "@types/node" ">=12.12.47"
 
-"@grpc/proto-loader@^0.6.0", "@grpc/proto-loader@^0.6.4":
+"@grpc/proto-loader@^0.6.4":
   version "0.6.6"
   resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.6.6.tgz#d8e51ea808ec5fa54d9defbecbf859336fb2da71"
   integrity sha512-cdMaPZ8AiFz6ua6PUbP+LKbhwJbFXnrQ/mlnKGUyzDUZ3wp7vPLksnmLCBX6SHgSmjX7CbNVNLFYD5GmmjO4GQ==
@@ -423,6 +423,17 @@
     protobufjs "^6.10.0"
     yargs "^16.1.1"
 
+"@grpc/proto-loader@^0.7.3":
+  version "0.7.3"
+  resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.7.3.tgz#75a6f95b51b85c5078ac7394da93850c32d36bb8"
+  integrity sha512-5dAvoZwna2Py3Ef96Ux9jIkp3iZ62TUsV00p3wVBPNX5K178UbNi8Q7gQVqwXT1Yq9RejIGG9G2IPEo93T6RcA==
+  dependencies:
+    "@types/long" "^4.0.1"
+    lodash.camelcase "^4.3.0"
+    long "^4.0.0"
+    protobufjs "^7.0.0"
+    yargs "^16.2.0"
+
 "@istanbuljs/load-nyc-config@^1.0.0":
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"
@@ -1151,18 +1162,18 @@
   dependencies:
     "@types/node" "*"
 
-"@zilliz/milvus2-sdk-node@^2.1.3":
-  version "2.1.3"
-  resolved "https://registry.yarnpkg.com/@zilliz/milvus2-sdk-node/-/milvus2-sdk-node-2.1.3.tgz#f8f54e1385f67edc4485ba3724b8b20f45537303"
-  integrity sha512-HZEd+iQ/YfYxsrxqA8fdozNHPXqrIbMW9Zo8LEFcCSqMtLx4dFhw2gQNA6KWdGyRICCNffUtAazHWTKBrRXBmA==
+"@zilliz/milvus2-sdk-node@^2.2.0":
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/@zilliz/milvus2-sdk-node/-/milvus2-sdk-node-2.2.0.tgz#c90eef9af424ec804e3b9b891f35251cb6e9209d"
+  integrity sha512-Vs5eIp5ydoHI2TztY7ilpEfj+SlNK7oMVtyDLZE0lVP253NF+fm/1jc/RKuKM+Jy8pQSkiEP1BFz85wPB0qemQ==
   dependencies:
     "@grpc/grpc-js" "^1.2.12"
-    "@grpc/proto-loader" "^0.6.0"
+    "@grpc/proto-loader" "^0.7.3"
     "@microsoft/api-documenter" "^7.13.39"
     "@microsoft/api-extractor" "^7.18.5"
     json-schema "^0.4.0"
     node-forge "^1.0.0"
-    protobufjs "^6.11.3"
+    protobufjs "^7.1.2"
 
 abab@^2.0.3, abab@^2.0.5:
   version "2.0.5"
@@ -3805,6 +3816,11 @@ long@^4.0.0:
   resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
   integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==
 
+long@^5.0.0:
+  version "5.2.1"
+  resolved "https://registry.yarnpkg.com/long/-/long-5.2.1.tgz#e27595d0083d103d2fa2c20c7699f8e0c92b897f"
+  integrity sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==
+
 lowercase-keys@^1.0.0, lowercase-keys@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f"
@@ -4318,10 +4334,10 @@ protobufjs@^6.10.0:
     "@types/node" ">=13.7.0"
     long "^4.0.0"
 
-protobufjs@^6.11.3:
-  version "6.11.3"
-  resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.3.tgz#637a527205a35caa4f3e2a9a4a13ddffe0e7af74"
-  integrity sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==
+protobufjs@^7.0.0, protobufjs@^7.1.2:
+  version "7.1.2"
+  resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.1.2.tgz#a0cf6aeaf82f5625bffcf5a38b7cd2a7de05890c"
+  integrity sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==
   dependencies:
     "@protobufjs/aspromise" "^1.1.2"
     "@protobufjs/base64" "^1.1.2"
@@ -4333,9 +4349,8 @@ protobufjs@^6.11.3:
     "@protobufjs/path" "^1.1.2"
     "@protobufjs/pool" "^1.1.0"
     "@protobufjs/utf8" "^1.1.0"
-    "@types/long" "^4.0.1"
     "@types/node" ">=13.7.0"
-    long "^4.0.0"
+    long "^5.0.0"
 
 proxy-addr@~2.0.5:
   version "2.0.7"