Browse Source

add collection table ui

tumao 4 years ago
parent
commit
5a8a387fe7

+ 2 - 2
client/package.json

@@ -27,7 +27,7 @@
     "web-vitals": "^1.0.1"
     "web-vitals": "^1.0.1"
   },
   },
   "scripts": {
   "scripts": {
-    "start": "react-app-rewired start",
+    "start": "react-app-rewired start -FAST_REFRESH=true",
     "build": "react-app-rewired build",
     "build": "react-app-rewired build",
     "test": "react-app-rewired test",
     "test": "react-app-rewired test",
     "eject": "react-app-rewired eject"
     "eject": "react-app-rewired eject"
@@ -50,4 +50,4 @@
       "last 1 safari version"
       "last 1 safari version"
     ]
     ]
   }
   }
-}
+}

+ 6 - 0
client/src/assets/icons/load.svg

@@ -0,0 +1,6 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M11.2929 11.2929C11.6834 10.9024 12.3166 10.9024 12.7071 11.2929L16.7071 15.2929C17.0976 15.6834 17.0976 16.3166 16.7071 16.7071C16.3166 17.0976 15.6834 17.0976 15.2929 16.7071L12 13.4142L8.70711 16.7071C8.31658 17.0976 7.68342 17.0976 7.29289 16.7071C6.90237 16.3166 6.90237 15.6834 7.29289 15.2929L11.2929 11.2929Z" fill="white"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M11.9994 11.2C12.5416 11.2 12.9812 11.6151 12.9812 12.1272V20.4727C12.9812 20.9848 12.5416 21.4 11.9994 21.4C11.4572 21.4 11.0176 20.9848 11.0176 20.4727V12.1272C11.0176 11.6151 11.4572 11.2 11.9994 11.2Z" fill="white"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M9.3289 4.00488C10.4168 3.9648 11.4996 4.17186 12.496 4.6105C13.4923 5.04914 14.3762 5.70794 15.0813 6.53738C15.6506 7.2071 16.0914 7.974 16.3835 8.79935H16.8003C17.867 8.80006 18.9037 9.15598 19.7457 9.81094C20.5876 10.4659 21.1875 11.3826 21.4506 12.4164C21.7138 13.4501 21.6251 14.5421 21.1987 15.5199C20.7723 16.4977 20.0325 17.3056 19.0959 17.8162C18.7078 18.0278 18.2218 17.8847 18.0102 17.4967C17.7987 17.1086 17.9418 16.6226 18.3298 16.411C18.9542 16.0706 19.4474 15.532 19.7317 14.8801C20.016 14.2283 20.0751 13.5003 19.8996 12.8111C19.7242 12.122 19.3243 11.5108 18.763 11.0742C18.2017 10.6375 17.5109 10.4003 16.7998 10.3998H15.792C15.4272 10.3998 15.1086 10.1531 15.0173 9.79987C14.8054 8.98012 14.4103 8.21907 13.8619 7.57396C13.3135 6.92884 12.626 6.41644 11.8511 6.07527C11.0762 5.73411 10.234 5.57306 9.38783 5.60424C8.54169 5.63541 7.71363 5.858 6.9659 6.25527C6.21817 6.65253 5.57023 7.21413 5.07079 7.89786C4.57135 8.58158 4.23341 9.36963 4.08237 10.2028C3.93133 11.0359 3.97113 11.8924 4.19877 12.708C4.42641 13.5235 4.83596 14.2768 5.39665 14.9113C5.6893 15.2424 5.65808 15.7482 5.32692 16.0408C4.99575 16.3335 4.49004 16.3022 4.19738 15.9711C3.4765 15.1553 2.94993 14.1868 2.65725 13.1382C2.36458 12.0897 2.31341 10.9884 2.5076 9.91727C2.70179 8.8461 3.13629 7.8329 3.77843 6.95382C4.42057 6.07475 5.25363 5.35269 6.215 4.84192C7.17636 4.33115 8.24101 4.04497 9.3289 4.00488Z" fill="white"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M11.4333 11.4345C11.7458 11.1219 12.2525 11.1219 12.565 11.4345L15.7659 14.6353C16.0784 14.9478 16.0784 15.4545 15.7659 15.767C15.4534 16.0795 14.9467 16.0795 14.6342 15.767L11.9991 13.132L9.36411 15.767C9.0516 16.0795 8.54493 16.0795 8.23243 15.767C7.91992 15.4545 7.91992 14.9478 8.23243 14.6353L11.4333 11.4345Z" fill="white"/>
+</svg>

+ 4 - 3
client/src/components/customDialog/CustomDialog.tsx

@@ -16,9 +16,10 @@ import CustomDialogTitle from './CustomDialogTitle';
 const useStyles = makeStyles((theme: Theme) =>
 const useStyles = makeStyles((theme: Theme) =>
   createStyles({
   createStyles({
     paper: {
     paper: {
-      maxWidth: '480px',
-      width: '100%',
-      borderRadius: '0px',
+      // maxWidth: '480px',
+      minWidth: '480px',
+      // width: '100%',
+      borderRadius: '8px',
       padding: 0,
       padding: 0,
     },
     },
     title: {
     title: {

+ 42 - 0
client/src/components/customDialog/DialogTemplate.tsx

@@ -0,0 +1,42 @@
+import { FC } from 'react';
+import { useTranslation } from 'react-i18next';
+import { DialogContent, DialogActions } from '@material-ui/core';
+import { DialogContainerProps } from './Types';
+import CustomDialogTitle from './CustomDialogTitle';
+import CustomButton from '../customButton/CustomButton';
+
+const DialogTemplate: FC<DialogContainerProps> = ({
+  title,
+  cancelLabel,
+  handleCancel,
+  confirmLabel,
+  handleConfirm,
+  confirmDisabled,
+  children,
+}) => {
+  const { t } = useTranslation('btn');
+  const cancel = cancelLabel || t('cancel');
+  const confirm = confirmLabel || t('confirm');
+
+  return (
+    <>
+      <CustomDialogTitle onClose={handleCancel}>{title}</CustomDialogTitle>
+      <DialogContent>{children}</DialogContent>
+      <DialogActions>
+        <CustomButton onClick={handleCancel} color="default">
+          {cancel}
+        </CustomButton>
+        <CustomButton
+          variant="contained"
+          onClick={handleConfirm}
+          color="primary"
+          disabled={confirmDisabled}
+        >
+          {confirm}
+        </CustomButton>
+      </DialogActions>
+    </>
+  );
+};
+
+export default DialogTemplate;

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

@@ -18,3 +18,12 @@ export type DeleteDialogContentType = {
   handleCancel?: () => void;
   handleCancel?: () => void;
   handleDelete: () => void;
   handleDelete: () => void;
 };
 };
+
+export type DialogContainerProps = {
+  title: string;
+  cancelLabel?: string;
+  confirmLabel?: string;
+  handleCancel: () => void;
+  handleConfirm: (param: any) => void;
+  confirmDisabled?: boolean;
+};

+ 37 - 18
client/src/components/grid/ActionBar.tsx

@@ -24,32 +24,51 @@ const useStyles = makeStyles((theme: Theme) =>
       color: theme.palette.common.black,
       color: theme.palette.common.black,
       opacity: 0.15,
       opacity: 0.15,
     },
     },
+    hoverType: {
+      marginRight: 0,
+
+      '& button': {
+        color: '#fff',
+      },
+    },
   })
   })
 );
 );
 
 
 const ActionBar: FC<ActionBarType> = props => {
 const ActionBar: FC<ActionBarType> = props => {
   const classes = useStyles();
   const classes = useStyles();
-  const { configs, row } = props;
+  const { configs, row, isHoverType = false } = props;
 
 
   return (
   return (
     <>
     <>
-      {configs.map(v => (
-        <span className={`${classes.root} ${v.className}`} key={v.icon}>
-          <CustomToolTip title={v.label || ''} placement="top">
-            <IconButton
-              aria-label={v.label || ''}
-              onClickCapture={e => {
-                e.stopPropagation();
-                v.onClick(e, row);
-              }}
-              disabled={v.disabled ? v.disabled(row) : false}
-              classes={{ disabled: classes.disabled }}
-            >
-              {Icons[v.icon]()}
-            </IconButton>
-          </CustomToolTip>
-        </span>
-      ))}
+      {configs.map(v => {
+        const label = v.getLabel ? v.getLabel(row) : v.label;
+        return (
+          <span
+            className={`${classes.root} ${v.className} ${
+              isHoverType ? classes.hoverType : ''
+            }`}
+            key={label}
+          >
+            <CustomToolTip title={label || ''} placement="bottom">
+              <IconButton
+                aria-label={label || ''}
+                onClickCapture={e => {
+                  e.stopPropagation();
+                  v.onClick(e, row);
+                }}
+                disabled={v.disabled ? v.disabled(row) : false}
+                classes={{
+                  disabled: classes.disabled,
+                }}
+              >
+                {v.showIconMethod === 'renderFn'
+                  ? v.renderIconFn && v.renderIconFn(row)
+                  : Icons[v.icon]()}
+              </IconButton>
+            </CustomToolTip>
+          </span>
+        );
+      })}
     </>
     </>
   );
   );
 };
 };

+ 29 - 6
client/src/components/grid/Table.tsx

@@ -50,15 +50,31 @@ const useStyles = makeStyles(theme => ({
     background: theme.palette.common.white,
     background: theme.palette.common.white,
     paddingLeft: theme.spacing(2),
     paddingLeft: theme.spacing(2),
   },
   },
+  hoverActionCell: {
+    transition: '0.2s all',
+    padding: 0,
+    width: '50px',
+    backgroundColor: '#fff',
+    '& span': {
+      opacity: 0,
+    },
+  },
   checkbox: {
   checkbox: {
     background: theme.palette.common.white,
     background: theme.palette.common.white,
   },
   },
   rowHover: {
   rowHover: {
     '&:hover': {
     '&:hover': {
-      backgroundColor: `#f3fcfe`,
+      backgroundColor: '#f3fcfe !important',
       '& td': {
       '& td': {
         background: 'inherit',
         background: 'inherit',
       },
       },
+
+      '& $hoverActionCell': {
+        backgroundColor: theme.palette.primary.main,
+        '& span': {
+          opacity: 1,
+        },
+      },
     },
     },
   },
   },
   cell: {
   cell: {
@@ -167,16 +183,16 @@ const EnhancedTable: FC<TableType> = props => {
 
 
                     return (
                     return (
                       <TableRow
                       <TableRow
-                        hover
+                        hover={showHoverStyle}
                         key={'row' + row[primaryKey] + index}
                         key={'row' + row[primaryKey] + index}
                         onClick={event => onSelected(event, row)}
                         onClick={event => onSelected(event, row)}
                         role="checkbox"
                         role="checkbox"
                         aria-checked={isItemSelected}
                         aria-checked={isItemSelected}
                         tabIndex={-1}
                         tabIndex={-1}
                         selected={isItemSelected && !disableSelect}
                         selected={isItemSelected && !disableSelect}
-                        classes={
-                          showHoverStyle ? { hover: classes.rowHover } : {}
-                        }
+                        classes={{
+                          hover: classes.rowHover,
+                        }}
                         onMouseEnter={handleMouseEnter}
                         onMouseEnter={handleMouseEnter}
                         onMouseLeave={handleMouseLeave}
                         onMouseLeave={handleMouseLeave}
                       >
                       >
@@ -201,13 +217,20 @@ const EnhancedTable: FC<TableType> = props => {
                             : {};
                             : {};
                           return colDef.showActionCell ? (
                           return colDef.showActionCell ? (
                             <TableCell
                             <TableCell
-                              className={`${classes.cell} ${classes.tableCell}`}
+                              className={`${classes.cell} ${
+                                classes.tableCell
+                              } ${
+                                colDef.isHoverAction
+                                  ? classes.hoverActionCell
+                                  : ''
+                              }`}
                               key="manage"
                               key="manage"
                               style={cellStyle}
                               style={cellStyle}
                             >
                             >
                               <ActionBar
                               <ActionBar
                                 showLabel={tableMouseStatus[index]}
                                 showLabel={tableMouseStatus[index]}
                                 configs={actionBarConfigs}
                                 configs={actionBarConfigs}
+                                isHoverType={colDef.isHoverAction}
                                 row={row}
                                 row={row}
                               ></ActionBar>
                               ></ActionBar>
                             </TableCell>
                             </TableCell>

+ 1 - 1
client/src/components/grid/TableHead.tsx

@@ -77,7 +77,7 @@ const EnhancedTableHead: FC<TableHeadType> = props => {
             sortDirection={orderBy === headCell.id ? order : false}
             sortDirection={orderBy === headCell.id ? order : false}
             className={classes.tableCell}
             className={classes.tableCell}
           >
           >
-            {headCell.label ? (
+            {headCell.label && !headCell.notSort ? (
               <TableSortLabel
               <TableSortLabel
                 active={orderBy === headCell.id}
                 active={orderBy === headCell.id}
                 direction={orderBy === headCell.id ? order : 'asc'}
                 direction={orderBy === headCell.id ? order : 'asc'}

+ 1 - 1
client/src/components/grid/ToolBar.tsx

@@ -59,7 +59,7 @@ const CustomToolBar: FC<ToolBarType> = props => {
 
 
   return (
   return (
     <>
     <>
-      <Grid container spacing={2}>
+      <Grid container>
         <Grid item xs={8}>
         <Grid item xs={8}>
           {leftConfigs.map((c, i) => {
           {leftConfigs.map((c, i) => {
             const isSelect = c.type === 'select' || c.type === 'groupSelect';
             const isSelect = c.type === 'select' || c.type === 'groupSelect';

+ 6 - 0
client/src/components/grid/Types.ts

@@ -75,6 +75,8 @@ export type ColDefinitionsType = {
   label: React.ReactNode;
   label: React.ReactNode;
   needCopy?: boolean;
   needCopy?: boolean;
   showActionCell?: boolean;
   showActionCell?: boolean;
+  isHoverAction?: boolean;
+  notSort?: boolean;
   onClick?: (
   onClick?: (
     e: React.MouseEvent<HTMLButtonElement, MouseEvent>,
     e: React.MouseEvent<HTMLButtonElement, MouseEvent>,
     data?: any
     data?: any
@@ -113,12 +115,16 @@ export type ActionBarType = {
   configs: ActionBarConfig[];
   configs: ActionBarConfig[];
   row: any;
   row: any;
   showLabel?: boolean;
   showLabel?: boolean;
+  isHoverType?: boolean;
 };
 };
 
 
 type ActionBarConfig = {
 type ActionBarConfig = {
   onClick: (e: React.MouseEvent, row: any) => void;
   onClick: (e: React.MouseEvent, row: any) => void;
   icon: IconsType;
   icon: IconsType;
+  showIconMethod?: 'iconType' | 'renderFn';
+  renderIconFn?: (row: any) => ReactElement;
   label?: string;
   label?: string;
+  getLabel?: (row: any) => string;
   className?: string;
   className?: string;
   disabled?: (row: any) => boolean;
   disabled?: (row: any) => boolean;
 };
 };

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

@@ -24,6 +24,7 @@ import { ReactComponent as CollectionIcon } from '../../assets/icons/collecion.s
 import { ReactComponent as ConsoleIcon } from '../../assets/icons/console.svg';
 import { ReactComponent as ConsoleIcon } from '../../assets/icons/console.svg';
 import { ReactComponent as InfoIcon } from '../../assets/icons/info.svg';
 import { ReactComponent as InfoIcon } from '../../assets/icons/info.svg';
 import { ReactComponent as ReleaseIcon } from '../../assets/icons/release.svg';
 import { ReactComponent as ReleaseIcon } from '../../assets/icons/release.svg';
+import { ReactComponent as LoadIcon } from '../../assets/icons/load.svg';
 
 
 const icons: { [x in IconsType]: (props?: any) => React.ReactElement } = {
 const icons: { [x in IconsType]: (props?: any) => React.ReactElement } = {
   search: (props = {}) => <SearchIcon {...props} />,
   search: (props = {}) => <SearchIcon {...props} />,
@@ -62,6 +63,9 @@ const icons: { [x in IconsType]: (props?: any) => React.ReactElement } = {
   release: (props = {}) => (
   release: (props = {}) => (
     <SvgIcon viewBox="0 0 16 16" component={ReleaseIcon} {...props} />
     <SvgIcon viewBox="0 0 16 16" component={ReleaseIcon} {...props} />
   ),
   ),
+  load: (props = {}) => (
+    <SvgIcon viewBox="0 0 24 24" component={LoadIcon} {...props} />
+  ),
 };
 };
 
 
 export default icons;
 export default icons;

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

@@ -21,4 +21,5 @@ export type IconsType =
   | 'logout'
   | 'logout'
   | 'rightArrow'
   | 'rightArrow'
   | 'info'
   | 'info'
-  | 'release';
+  | 'release'
+  | 'load';

+ 1 - 5
client/src/components/status/Status.tsx

@@ -74,11 +74,7 @@ const Status: FC<StatusType> = props => {
 
 
   return (
   return (
     <div className={classes.root}>
     <div className={classes.root}>
-      <div
-        className={`${classes.circle} ${
-          status === StatusEnum.unloaded ? classes.flash : ''
-        }`}
-      ></div>
+      {status === StatusEnum.loaded && <div className={classes.circle}></div>}
       <Typography variant="body2" className={classes.label}>
       <Typography variant="body2" className={classes.label}>
         {label}
         {label}
       </Typography>
       </Typography>

+ 9 - 12
client/src/components/status/StatusIcon.tsx

@@ -1,8 +1,6 @@
 import { CircularProgress, makeStyles, Theme } from '@material-ui/core';
 import { CircularProgress, makeStyles, Theme } from '@material-ui/core';
 import { FC, ReactElement } from 'react';
 import { FC, ReactElement } from 'react';
-import { getStatusType } from '../../utils/Status';
-import icons from '../icons/Icons';
-import { StatusEnum, StatusType } from './Types';
+import { ChildrenStatusType, StatusIconType } from './Types';
 
 
 const useStyles = makeStyles((theme: Theme) => ({
 const useStyles = makeStyles((theme: Theme) => ({
   wrapper: {
   wrapper: {
@@ -16,14 +14,13 @@ const useStyles = makeStyles((theme: Theme) => ({
   },
   },
 }));
 }));
 
 
-const StatusIcon: FC<StatusType> = props => {
+const StatusIcon: FC<StatusIconType> = props => {
   const classes = useStyles();
   const classes = useStyles();
-  const { status } = props;
+  const { type } = props;
 
 
-  const getElement = (status: StatusEnum): ReactElement => {
-    const type = getStatusType(status);
+  const getElement = (type: ChildrenStatusType): ReactElement => {
     switch (type) {
     switch (type) {
-      case 'loading':
+      case 'creating':
         return (
         return (
           <CircularProgress
           <CircularProgress
             size={24}
             size={24}
@@ -31,14 +28,14 @@ const StatusIcon: FC<StatusType> = props => {
             classes={{ svg: classes.svg }}
             classes={{ svg: classes.svg }}
           />
           />
         );
         );
-      case 'success':
-        return icons.success({ style: { color: '#34b78f' } });
+      case 'finish':
+        return <></>;
       default:
       default:
-        return icons.error({ style: { color: '#fc4c02' } });
+        return <></>;
     }
     }
   };
   };
 
 
-  return <div className={classes.wrapper}>{getElement(status)}</div>;
+  return <div className={classes.wrapper}>{getElement(type)}</div>;
 };
 };
 
 
 export default StatusIcon;
 export default StatusIcon;

+ 6 - 0
client/src/components/status/Types.ts

@@ -6,3 +6,9 @@ export enum StatusEnum {
 export type StatusType = {
 export type StatusType = {
   status: StatusEnum;
   status: StatusEnum;
 };
 };
+
+export type ChildrenStatusType = 'creating' | 'finish' | 'error';
+
+export type StatusIconType = {
+  type: ChildrenStatusType;
+};

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

@@ -9,6 +9,7 @@ const btnTrans = {
   import: '导入',
   import: '导入',
   delete: '删除',
   delete: '删除',
   release: 'Release',
   release: 'Release',
+  create: 'Create',
 };
 };
 
 
 export default btnTrans;
 export default btnTrans;

+ 17 - 0
client/src/i18n/cn/collection.ts

@@ -1,7 +1,24 @@
 const collectionTrans = {
 const collectionTrans = {
   noLoadData: 'No Loaded Collection',
   noLoadData: 'No Loaded Collection',
+  noData: 'No Collection',
+
   rowCount: 'Row Count',
   rowCount: 'Row Count',
   tooltip: 'data in one row',
   tooltip: 'data in one row',
+
+  create: 'Create Collection',
+  delete: 'delete',
+
+  // table
+  id: 'ID',
+  name: 'Name',
+  status: 'Status',
+  desc: 'Description',
+
+  // create dialog
+  createTitle: 'Create Collection',
+  general: '1. General Info',
+  structure: '2. Define Structure',
+  description: 'Description (Optional)',
 };
 };
 
 
 export default collectionTrans;
 export default collectionTrans;

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

@@ -4,7 +4,7 @@ const commonTrans = {
     address: 'Milvus Address',
     address: 'Milvus Address',
   },
   },
   status: {
   status: {
-    loaded: 'loaded',
+    loaded: 'loaded for search',
     unloaded: 'unloaded',
     unloaded: 'unloaded',
     error: 'error',
     error: 'error',
     running: 'running',
     running: 'running',

+ 17 - 0
client/src/i18n/en/collection.ts

@@ -1,7 +1,24 @@
 const collectionTrans = {
 const collectionTrans = {
   noLoadData: 'No Loaded Collection',
   noLoadData: 'No Loaded Collection',
+  noData: 'No Collection',
+
   rowCount: 'Row Count',
   rowCount: 'Row Count',
   tooltip: 'data in one row',
   tooltip: 'data in one row',
+
+  create: 'Create Collection',
+  delete: 'delete',
+
+  // table
+  id: 'ID',
+  name: 'Name',
+  status: 'Status',
+  desc: 'Description',
+
+  // create dialog
+  createTitle: 'Create Collection',
+  general: '1. General Info',
+  structure: '2. Define Structure',
+  description: 'Description (Optional)',
 };
 };
 
 
 export default collectionTrans;
 export default collectionTrans;

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

@@ -4,7 +4,7 @@ const commonTrans = {
     address: 'Milvus Address',
     address: 'Milvus Address',
   },
   },
   status: {
   status: {
-    loaded: 'loaded',
+    loaded: 'loaded for search',
     unloaded: 'unloaded',
     unloaded: 'unloaded',
     error: 'error',
     error: 'error',
     running: 'running',
     running: 'running',

+ 225 - 1
client/src/pages/collections/Collections.tsx

@@ -1,9 +1,233 @@
+import { useContext, useEffect, useState } from 'react';
 import { useNavigationHook } from '../../hooks/Navigation';
 import { useNavigationHook } from '../../hooks/Navigation';
 import { ALL_ROUTER_TYPES } from '../../router/Types';
 import { ALL_ROUTER_TYPES } from '../../router/Types';
+import MilvusGrid from '../../components/grid';
+import CustomToolBar from '../../components/grid/ToolBar';
+import { CollectionCreateParam, CollectionView } from './Types';
+import { ColDefinitionsType, ToolBarConfig } from '../../components/grid/Types';
+import { usePaginationHook } from '../../hooks/Pagination';
+import icons from '../../components/icons/Icons';
+import EmptyCard from '../../components/cards/EmptyCard';
+import Status from '../../components/status/Status';
+import { useTranslation } from 'react-i18next';
+import { StatusEnum } from '../../components/status/Types';
+import { makeStyles, Theme, Link } from '@material-ui/core';
+import StatusIcon from '../../components/status/StatusIcon';
+import CustomToolTip from '../../components/customToolTip/CustomToolTip';
+import { rootContext } from '../../context/Root';
+import CreateCollection from './Create';
+
+const useStyles = makeStyles((theme: Theme) => ({
+  emptyWrapper: {
+    marginTop: theme.spacing(2),
+  },
+
+  icon: {
+    fontSize: '20px',
+    marginLeft: theme.spacing(0.5),
+  },
+}));
 
 
 const Collections = () => {
 const Collections = () => {
   useNavigationHook(ALL_ROUTER_TYPES.COLLECTIONS);
   useNavigationHook(ALL_ROUTER_TYPES.COLLECTIONS);
-  return <section>collection</section>;
+  const {
+    pageSize,
+    currentPage,
+    handleCurrentPage,
+    // offset,
+    total,
+    // setTotal
+  } = usePaginationHook();
+  const [collections, setCollections] = useState<CollectionView[]>([]);
+  // const [loading, setLoading] = useState<boolean>(false);
+  const [selectedCollections, setSelectedCollections] = useState<
+    CollectionView[]
+  >([]);
+
+  const { setDialog } = useContext(rootContext);
+  const { t } = useTranslation('collection');
+
+  const classes = useStyles();
+
+  const loading = false;
+
+  const LoadIcon = icons.load;
+  const ReleaseIcon = icons.release;
+  const InfoIcon = icons.info;
+
+  useEffect(() => {
+    const mockCollections: CollectionView[] = [
+      {
+        name: 'collection',
+        nameElement: (
+          <Link href="/overview" underline="always" color="textPrimary">
+            collection
+          </Link>
+        ),
+        id: 'c1',
+        status: StatusEnum.unloaded,
+        statusElement: <Status status={StatusEnum.unloaded} />,
+        rowCount: '200,000',
+        desc: 'description',
+        indexCreatingElement: <StatusIcon type="creating" />,
+      },
+      {
+        name: 'collection 2',
+        nameElement: (
+          <Link href="/overview" underline="always" color="textPrimary">
+            collection 2
+          </Link>
+        ),
+        id: 'c2',
+        status: StatusEnum.loaded,
+        statusElement: <Status status={StatusEnum.loaded} />,
+        rowCount: '300,000',
+        desc: 'description 2',
+        indexCreatingElement: <StatusIcon type="finish" />,
+      },
+    ];
+    setCollections(mockCollections);
+  }, []);
+
+  const handleCreateCollection = (param: CollectionCreateParam) => {
+    console.log('===== param', param);
+  };
+
+  const toolbarConfigs: ToolBarConfig[] = [
+    {
+      label: t('create'),
+      onClick: () => {
+        setDialog({
+          open: true,
+          type: 'custom',
+          params: {
+            component: (
+              <CreateCollection handleCreate={handleCreateCollection} />
+            ),
+          },
+        });
+      },
+      icon: 'add',
+    },
+    {
+      type: 'iconBtn',
+      onClick: () => {
+        console.log('delete collections');
+      },
+      label: t('delete'),
+      icon: 'delete',
+    },
+  ];
+
+  const colDefinitions: ColDefinitionsType[] = [
+    {
+      id: 'id',
+      align: 'left',
+      disablePadding: true,
+      label: t('id'),
+    },
+    {
+      id: 'nameElement',
+      align: 'left',
+      disablePadding: true,
+      label: t('name'),
+    },
+    {
+      id: 'statusElement',
+      align: 'left',
+      disablePadding: false,
+      label: t('status'),
+    },
+    {
+      id: 'rowCount',
+      align: 'left',
+      disablePadding: false,
+      label: (
+        <span className="flex-center">
+          {t('rowCount')}
+          <CustomToolTip title={t('tooltip')}>
+            <InfoIcon classes={{ root: classes.icon }} />
+          </CustomToolTip>
+        </span>
+      ),
+    },
+    {
+      id: 'desc',
+      align: 'left',
+      disablePadding: false,
+      label: t('desc'),
+    },
+    {
+      id: 'indexCreatingElement',
+      align: 'left',
+      disablePadding: false,
+      label: '',
+    },
+    {
+      id: 'action',
+      align: 'center',
+      disablePadding: false,
+      label: '',
+      showActionCell: true,
+      isHoverAction: true,
+      actionBarConfigs: [
+        {
+          onClick: (e: React.MouseEvent, row: CollectionView) => {
+            console.log('action row', row);
+          },
+          icon: 'load',
+          label: 'load',
+          showIconMethod: 'renderFn',
+          getLabel: (row: CollectionView) =>
+            row.status === StatusEnum.loaded ? 'release' : 'load',
+          renderIconFn: (row: CollectionView) =>
+            row.status === StatusEnum.loaded ? <ReleaseIcon /> : <LoadIcon />,
+        },
+      ],
+    },
+  ];
+
+  const handleSelectChange = (value: any) => {
+    setSelectedCollections(value);
+  };
+
+  const handlePageChange = (e: any, page: number) => {
+    handleCurrentPage(page);
+    setSelectedCollections([]);
+  };
+
+  const CollectionIcon = icons.navCollection;
+
+  return (
+    <section className="page-wrapper">
+      {collections.length > 0 || loading ? (
+        <MilvusGrid
+          toolbarConfigs={toolbarConfigs}
+          colDefinitions={colDefinitions}
+          rows={collections}
+          rowCount={total}
+          primaryKey="id"
+          openCheckBox={true}
+          showHoverStyle={true}
+          selected={selectedCollections}
+          setSelected={handleSelectChange}
+          page={currentPage}
+          onChangePage={handlePageChange}
+          rowsPerPage={pageSize}
+          // isLoading={loading}
+        />
+      ) : (
+        <>
+          <CustomToolBar toolbarConfigs={toolbarConfigs} />
+          <EmptyCard
+            wrapperClass={`page-empty-card ${classes.emptyWrapper}`}
+            icon={<CollectionIcon />}
+            text={t('noData')}
+          />
+        </>
+      )}
+    </section>
+  );
 };
 };
 
 
 export default Collections;
 export default Collections;

+ 41 - 0
client/src/pages/collections/Constants.ts

@@ -0,0 +1,41 @@
+import { KeyValuePair } from '../../types/Common';
+import { DataTypeEnum } from './Types';
+
+export const VECTOR_FIELDS_OPTIONS: KeyValuePair[] = [
+  {
+    label: 'Binary Vector',
+    value: DataTypeEnum.BinaryVector,
+  },
+  {
+    label: 'Float Vector',
+    value: DataTypeEnum.FloatVector,
+  },
+];
+
+export const ALL_OPTIONS: KeyValuePair[] = [
+  ...VECTOR_FIELDS_OPTIONS,
+  {
+    label: 'Int8',
+    value: DataTypeEnum.Int8,
+  },
+  {
+    label: 'Int16',
+    value: DataTypeEnum.Int16,
+  },
+  {
+    label: 'Int32',
+    value: DataTypeEnum.Int32,
+  },
+  {
+    label: 'Int64',
+    value: DataTypeEnum.Int64,
+  },
+  {
+    label: 'Float',
+    value: DataTypeEnum.Float,
+  },
+  {
+    label: 'Double',
+    value: DataTypeEnum.Double,
+  },
+];

+ 129 - 0
client/src/pages/collections/Create.tsx

@@ -0,0 +1,129 @@
+import { makeStyles, Theme } from '@material-ui/core';
+import { FC, useContext, useMemo, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import DialogTemplate from '../../components/customDialog/DialogTemplate';
+import CustomInput from '../../components/customInput/CustomInput';
+import { ITextfieldConfig } from '../../components/customInput/Types';
+import { rootContext } from '../../context/Root';
+import { useFormValidation } from '../../hooks/Form';
+import { formatForm } from '../../utils/Form';
+import CreateFields from './CreateFields';
+import { CollectionCreateProps, DataTypeEnum, Field } from './Types';
+
+const useStyles = makeStyles((theme: Theme) => ({
+  fieldset: {
+    width: '100%',
+    display: 'flex',
+    justifyContent: 'space-between',
+    alignItems: 'center',
+
+    '& legend': {
+      marginBottom: theme.spacing(2),
+      color: `#82838e`,
+      lineHeight: '20px',
+      fontSize: '14px',
+    },
+  },
+}));
+
+const CreateCollection: FC<CollectionCreateProps> = ({ handleCreate }) => {
+  const classes = useStyles();
+  const { handleCloseDialog } = useContext(rootContext);
+  const { t } = useTranslation('collection');
+  const { t: btnTrans } = useTranslation('btn');
+  const { t: warningTrans } = useTranslation('warning');
+
+  const [form, setForm] = useState({
+    name: '',
+    desc: '',
+    autoID: true,
+  });
+  const [fields, setFields] = useState<Field[]>([
+    {
+      type: DataTypeEnum.Int64,
+      isPrimaryKey: true,
+      name: '',
+      desc: '',
+      isDefault: true,
+    },
+    {
+      type: DataTypeEnum.FloatVector,
+      isPrimaryKey: false,
+      name: '',
+      dimension: '',
+      desc: '',
+      isDefault: true,
+    },
+  ]);
+  const [fieldsAllValid, setFieldsAllValid] = useState<boolean>(false);
+
+  const checkedForm = useMemo(() => {
+    const { name } = form;
+    return formatForm({ name });
+  }, [form]);
+  const { validation, checkIsValid, disabled } = useFormValidation(checkedForm);
+
+  const handleInputChange = (key: string, value: string) => {
+    setForm(v => ({ ...v, [key]: value }));
+  };
+
+  const generalInfoConfigs: ITextfieldConfig[] = [
+    {
+      label: t('name'),
+      key: 'name',
+      value: form.name,
+      onChange: (value: string) => handleInputChange('name', value),
+      variant: 'filled',
+      validations: [
+        {
+          rule: 'require',
+          errorText: warningTrans('required', { name: t('name') }),
+        },
+      ],
+    },
+    {
+      label: t('description'),
+      key: 'description',
+      value: form.desc,
+      onChange: (value: string) => handleInputChange('desc', value),
+      variant: 'filled',
+      validations: [],
+    },
+  ];
+
+  return (
+    <DialogTemplate
+      title={t('createTitle')}
+      handleCancel={handleCloseDialog}
+      confirmLabel={btnTrans('create')}
+      handleConfirm={handleCreate}
+      confirmDisabled={disabled || !fieldsAllValid}
+    >
+      <form>
+        <fieldset className={classes.fieldset}>
+          <legend>{t('general')}</legend>
+          {generalInfoConfigs.map(config => (
+            <CustomInput
+              key={config.key}
+              type="text"
+              textConfig={config}
+              checkValid={checkIsValid}
+              validInfo={validation}
+            />
+          ))}
+        </fieldset>
+
+        <fieldset className={classes.fieldset}>
+          <legend>{t('structure')}</legend>
+          <CreateFields
+            fields={fields}
+            setFields={setFields}
+            setfieldsAllValid={setFieldsAllValid}
+          />
+        </fieldset>
+      </form>
+    </DialogTemplate>
+  );
+};
+
+export default CreateCollection;

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

@@ -0,0 +1,52 @@
+import { Dispatch, ReactElement, SetStateAction } from 'react';
+import { StatusEnum } from '../../components/status/Types';
+
+export interface CollectionView {
+  name: string;
+  nameElement: ReactElement;
+  id: string;
+  status: StatusEnum;
+  statusElement: ReactElement;
+  rowCount: string;
+  desc: string;
+  indexCreatingElement: ReactElement;
+}
+
+export interface CollectionCreateProps {
+  handleCreate: (param: CollectionCreateParam) => void;
+}
+
+export interface CollectionCreateParam {
+  name: string;
+  desc: string;
+  autoID: boolean;
+  fields: Field[];
+}
+
+export enum DataTypeEnum {
+  Int8 = 2,
+  Int16 = 3,
+  Int32 = 4,
+  Int64 = 5,
+  Float = 10,
+  Double = 11,
+  BinaryVector = 100,
+  FloatVector = 101,
+}
+
+export interface Field {
+  name: string;
+  type: DataTypeEnum;
+  isPrimaryKey: boolean;
+  desc: string;
+  dimension?: number | string;
+  isDefault?: boolean;
+}
+
+export type CreateFieldType = 'primaryKey' | 'vector' | 'number';
+
+export interface CreateFieldsProps {
+  fields: Field[];
+  setFields: Dispatch<SetStateAction<Field[]>>;
+  setfieldsAllValid: Dispatch<SetStateAction<boolean>>;
+}

+ 3 - 18
client/src/pages/overview/Overview.tsx

@@ -12,27 +12,12 @@ import StatisticsCard from './statisticsCard/StatisticsCard';
 import { StatisticsCardProps } from './statisticsCard/Types';
 import { StatisticsCardProps } from './statisticsCard/Types';
 
 
 const useStyles = makeStyles((theme: Theme) => ({
 const useStyles = makeStyles((theme: Theme) => ({
-  wrapper: {
-    margin: theme.spacing(2, 5),
-  },
   collectionTitle: {
   collectionTitle: {
     margin: theme.spacing(2, 0),
     margin: theme.spacing(2, 0),
     lineHeight: '20px',
     lineHeight: '20px',
     fontSize: '14px',
     fontSize: '14px',
     color: '#82838e',
     color: '#82838e',
   },
   },
-  empty: {
-    flexGrow: 1,
-  },
-  emptyIcon: {
-    width: '48px',
-    height: '48px',
-    fill: 'transparent',
-
-    '& path': {
-      stroke: '#aeaebb',
-    },
-  },
   cardsWrapper: {
   cardsWrapper: {
     display: 'grid',
     display: 'grid',
     gridTemplateColumns: 'repeat(auto-fill, minmax(380px, 1fr))',
     gridTemplateColumns: 'repeat(auto-fill, minmax(380px, 1fr))',
@@ -107,7 +92,7 @@ const Overview = () => {
   const CollectionIcon = icons.navCollection;
   const CollectionIcon = icons.navCollection;
 
 
   return (
   return (
-    <section className={`page-wrapper ${classes.wrapper}`}>
+    <section className="page-wrapper">
       <StatisticsCard data={mockStatistics.data} />
       <StatisticsCard data={mockStatistics.data} />
       <Typography className={classes.collectionTitle}>{t('load')}</Typography>
       <Typography className={classes.collectionTitle}>{t('load')}</Typography>
       {mockCollections.length > 0 ? (
       {mockCollections.length > 0 ? (
@@ -118,8 +103,8 @@ const Overview = () => {
         </div>
         </div>
       ) : (
       ) : (
         <EmptyCard
         <EmptyCard
-          wrapperClass={classes.empty}
-          icon={<CollectionIcon classes={{ root: classes.emptyIcon }} />}
+          wrapperClass="page-empty-card"
+          icon={<CollectionIcon />}
           text={collectionTrans('noLoadData')}
           text={collectionTrans('noLoadData')}
         />
         />
       )}
       )}

+ 25 - 0
client/src/styles/common.css

@@ -1,3 +1,11 @@
+/* reset some elements styles */
+fieldset {
+  border: 0;
+  padding: 0;
+  margin: 0;
+  min-width: 0;
+}
+
 /* horizontal and vertical center */
 /* horizontal and vertical center */
 .flex-center {
 .flex-center {
   display: flex;
   display: flex;
@@ -16,4 +24,21 @@
   flex-grow: 1;
   flex-grow: 1;
   display: flex;
   display: flex;
   flex-direction: column;
   flex-direction: column;
+
+  margin: 16px 40px;
+}
+
+.page-empty-card {
+  flex-grow: 1;
+}
+
+/* default empty card style, can be overrided by adding icon class */
+.page-empty-card .MuiSvgIcon-root {
+  width: 48px;
+  height: 48px;
+  fill: transparent;
+}
+
+.page-empty-card .MuiSvgIcon-root path {
+  stroke: #aeaebb;
 }
 }

+ 18 - 0
client/src/utils/Format.ts

@@ -1,4 +1,9 @@
 import { BYTE_UNITS } from '../consts/Util';
 import { BYTE_UNITS } from '../consts/Util';
+import {
+  CreateFieldType,
+  DataTypeEnum,
+  Field,
+} from '../pages/collections/Types';
 import { KeyValuePair } from '../types/Common';
 import { KeyValuePair } from '../types/Common';
 
 
 /**
 /**
@@ -93,3 +98,16 @@ export const getKeyValueListFromJSON = (
 export const checkIsBinarySubstructure = (metricLabel: string): boolean => {
 export const checkIsBinarySubstructure = (metricLabel: string): boolean => {
   return metricLabel === 'Superstructure' || metricLabel === 'Substructure';
   return metricLabel === 'Superstructure' || metricLabel === 'Substructure';
 };
 };
+
+export const getCreateFieldType = (config: Field): CreateFieldType => {
+  if (config.isPrimaryKey) {
+    return 'primaryKey';
+  }
+
+  const vectorTypes = [DataTypeEnum.BinaryVector, DataTypeEnum.FloatVector];
+  if (vectorTypes.includes(config.type)) {
+    return 'vector';
+  }
+
+  return 'number';
+};

+ 0 - 18
client/src/utils/Status.ts

@@ -1,18 +0,0 @@
-import { StatusEnum } from '../components/status/Types';
-
-type TaskStatusType = 'success' | 'error' | 'loading';
-
-export const getStatusType = (status: StatusEnum): TaskStatusType => {
-  const loadingList: StatusEnum[] = [];
-  const successList = [StatusEnum.loaded];
-
-  if (loadingList.includes(status)) {
-    return 'loading';
-  }
-
-  if (successList.includes(status)) {
-    return 'success';
-  }
-
-  return 'error';
-};