Browse Source

add overview ui

tumao 4 years ago
parent
commit
858dbfd588
35 changed files with 495 additions and 70 deletions
  1. 26 21
      client/public/index.html
  2. 5 0
      client/src/assets/icons/info.svg
  3. 17 0
      client/src/assets/icons/release.svg
  4. 6 6
      client/src/components/__test__/status/Status.spec.tsx
  5. 33 0
      client/src/components/cards/EmptyCard.tsx
  6. 7 0
      client/src/components/cards/Types.ts
  7. 1 5
      client/src/components/customInput/SearchInput.tsx
  8. 2 1
      client/src/components/customToolTip/CustomToolTip.tsx
  9. 10 0
      client/src/components/icons/Icons.tsx
  10. 4 1
      client/src/components/icons/Types.ts
  11. 2 7
      client/src/components/layout/Header.tsx
  12. 5 2
      client/src/components/menu/NavMenu.tsx
  13. 7 7
      client/src/components/status/Status.tsx
  14. 2 2
      client/src/components/status/Types.ts
  15. 0 1
      client/src/http/Axios.ts
  16. 1 0
      client/src/i18n/cn/button.ts
  17. 7 0
      client/src/i18n/cn/collection.ts
  18. 2 2
      client/src/i18n/cn/common.ts
  19. 8 0
      client/src/i18n/cn/overview.ts
  20. 1 0
      client/src/i18n/en/button.ts
  21. 7 0
      client/src/i18n/en/collection.ts
  22. 2 2
      client/src/i18n/en/common.ts
  23. 8 0
      client/src/i18n/en/overview.ts
  24. 8 0
      client/src/i18n/index.ts
  25. 2 0
      client/src/index.css
  26. 6 3
      client/src/pages/connect/Connect.tsx
  27. 1 5
      client/src/pages/connect/ConnectContainer.tsx
  28. 121 1
      client/src/pages/overview/Overview.tsx
  29. 97 0
      client/src/pages/overview/collectionCard/CollectionCard.tsx
  30. 13 0
      client/src/pages/overview/collectionCard/Types.ts
  31. 52 0
      client/src/pages/overview/statisticsCard/StatisticsCard.tsx
  32. 10 0
      client/src/pages/overview/statisticsCard/Types.ts
  33. 19 0
      client/src/styles/common.css
  34. 1 2
      client/src/utils/Common.ts
  35. 2 2
      client/src/utils/Status.ts

+ 26 - 21
client/public/index.html

@@ -1,21 +1,27 @@
 <!DOCTYPE html>
 <html lang="en">
-
-<head>
-  <meta charset="utf-8" />
-  <link rel="icon" href="%PUBLIC_URL%/favicon.png" />
-  <meta name="viewport" content="width=device-width, initial-scale=1" />
-  <meta name="theme-color" content="#000000" />
-  <meta name="description" content="Web site created using create-react-app" />
-  <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
-  <!--
+  <head>
+    <meta charset="utf-8" />
+    <link rel="icon" href="%PUBLIC_URL%/favicon.png" />
+    <meta name="viewport" content="width=device-width, initial-scale=1" />
+    <meta name="theme-color" content="#000000" />
+    <meta
+      name="description"
+      content="Web site created using create-react-app"
+    />
+    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
+    <!--
       manifest.json provides metadata used when your web app is installed on a
       user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
     -->
-  <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
-  <script src="%PUBLIC_URL%/env-config.js"></script>
+    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
+    <link
+      href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700;900&display=swap"
+      rel="stylesheet"
+    />
+    <script src="%PUBLIC_URL%/env-config.js"></script>
 
-  <!--
+    <!--
       Notice the use of %PUBLIC_URL% in the tags above.
       It will be replaced with the URL of the `public` folder during the build.
       Only files inside the `public` folder can be referenced from the HTML.
@@ -24,13 +30,13 @@
       work correctly both with client-side routing and a non-root public URL.
       Learn how to configure a non-root public URL by running `npm run build`.
     -->
-  <title>Milvus Admin</title>
-</head>
+    <title>Milvus Admin</title>
+  </head>
 
-<body>
-  <noscript>You need to enable JavaScript to run this app.</noscript>
-  <div id="root"></div>
-  <!--
+  <body>
+    <noscript>You need to enable JavaScript to run this app.</noscript>
+    <div id="root"></div>
+    <!--
       This HTML file is a template.
       If you open it directly in the browser, you will see an empty page.
 
@@ -40,6 +46,5 @@
       To begin the development, run `npm start` or `yarn start`.
       To create a production bundle, use `npm run build` or `yarn build`.
     -->
-</body>
-
-</html>
+  </body>
+</html>

+ 5 - 0
client/src/assets/icons/info.svg

@@ -0,0 +1,5 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M7.99996 1.99999C4.68625 1.99999 1.99996 4.68628 1.99996 7.99999C1.99996 11.3137 4.68625 14 7.99996 14C11.3137 14 14 11.3137 14 7.99999C14 4.68628 11.3137 1.99999 7.99996 1.99999ZM0.666626 7.99999C0.666626 3.9499 3.94987 0.666656 7.99996 0.666656C12.05 0.666656 15.3333 3.9499 15.3333 7.99999C15.3333 12.0501 12.05 15.3333 7.99996 15.3333C3.94987 15.3333 0.666626 12.0501 0.666626 7.99999Z" fill="#06AFF2"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M8.00004 7.33334C8.36823 7.33334 8.66671 7.63182 8.66671 8.00001V10.6667C8.66671 11.0349 8.36823 11.3333 8.00004 11.3333C7.63185 11.3333 7.33337 11.0349 7.33337 10.6667V8.00001C7.33337 7.63182 7.63185 7.33334 8.00004 7.33334Z" fill="#06AFF2"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M7.33337 5.33332C7.33337 4.96513 7.63185 4.66666 8.00004 4.66666H8.00671C8.3749 4.66666 8.67337 4.96513 8.67337 5.33332C8.67337 5.70151 8.3749 5.99999 8.00671 5.99999H8.00004C7.63185 5.99999 7.33337 5.70151 7.33337 5.33332Z" fill="#06AFF2"/>
+</svg>

+ 17 - 0
client/src/assets/icons/release.svg

@@ -0,0 +1,17 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0)">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M2.74339 13.2866C1.57182 12.115 1.57182 10.2155 2.74339 9.04396L4.39331 7.39405C4.65366 7.1337 5.07577 7.1337 5.33612 7.39405C5.59647 7.6544 5.59647 8.07651 5.33612 8.33686L3.6862 9.98677C3.03533 10.6376 3.03533 11.6929 3.6862 12.3438C4.33707 12.9947 5.39235 12.9947 6.04322 12.3438L7.69314 10.6939C7.95349 10.4335 8.3756 10.4335 8.63595 10.6939C8.8963 10.9542 8.8963 11.3763 8.63595 11.6367L6.98603 13.2866C5.81446 14.4582 3.91496 14.4582 2.74339 13.2866Z" fill="white"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M12.6428 3.3871C13.8144 4.55868 13.8144 6.45817 12.6428 7.62974L10.9929 9.27966C10.7326 9.54001 10.3105 9.54001 10.0501 9.27966C9.78977 9.01931 9.78977 8.5972 10.0501 8.33685L11.7 6.68693C12.3509 6.03606 12.3509 4.98079 11.7 4.32991C11.0492 3.67904 9.99388 3.67904 9.34301 4.32991L7.69309 5.97983C7.43274 6.24018 7.01063 6.24018 6.75028 5.97983C6.48993 5.71948 6.48993 5.29737 6.75028 5.03702L8.4002 3.3871C9.57177 2.21553 11.4713 2.21553 12.6428 3.3871Z" fill="white"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M1.29944 6.37793C1.29944 6.10179 1.5233 5.87793 1.79944 5.87793H3.05963C3.33577 5.87793 3.55963 6.10179 3.55963 6.37793C3.55963 6.65407 3.33577 6.87793 3.05963 6.87793H1.79944C1.5233 6.87793 1.29944 6.65407 1.29944 6.37793Z" fill="white"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M5.73157 1.98645C5.45543 1.98645 5.23157 2.21031 5.23157 2.48645V3.74645C5.23157 4.02259 5.45543 4.24645 5.73157 4.24645C6.00771 4.24645 6.23157 4.02259 6.23157 3.74645V2.48645C6.23157 2.21031 6.00771 1.98645 5.73157 1.98645Z" fill="white"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M2.51681 3.06168C2.71207 2.86642 3.02865 2.86642 3.22391 3.06168L4.64773 4.4855C4.843 4.68076 4.843 4.99735 4.64773 5.19261C4.45247 5.38787 4.13589 5.38787 3.94063 5.19261L2.51681 3.76879C2.32155 3.57353 2.32155 3.25695 2.51681 3.06168Z" fill="white"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M9.91736 14.2424C10.1935 14.2424 10.4174 14.0186 10.4174 13.7424V12.4822C10.4174 12.2061 10.1935 11.9822 9.91736 11.9822C9.64122 11.9822 9.41736 12.2061 9.41736 12.4822V13.7424C9.41736 14.0186 9.64122 14.2424 9.91736 14.2424Z" fill="white"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M14.3088 9.37524C14.3088 9.65139 14.085 9.87524 13.8088 9.87524H12.5487C12.2725 9.87524 12.0487 9.65139 12.0487 9.37524C12.0487 9.0991 12.2725 8.87524 12.5487 8.87524H13.8088C14.085 8.87524 14.3088 9.0991 14.3088 9.37524Z" fill="white"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M13.2337 13.0252C13.429 12.8299 13.429 12.5133 13.2337 12.3181L11.8099 10.8943C11.6146 10.699 11.2981 10.699 11.1028 10.8943C10.9075 11.0895 10.9075 11.4061 11.1028 11.6014L12.5266 13.0252C12.7219 13.2204 13.0385 13.2204 13.2337 13.0252Z" fill="white"/>
+</g>
+<defs>
+<clipPath id="clip0">
+<rect width="16" height="16" fill="white"/>
+</clipPath>
+</defs>
+</svg>

+ 6 - 6
client/src/components/__test__/status/Status.spec.tsx

@@ -11,8 +11,8 @@ jest.mock('react-i18next', () => {
       return {
         t: name => {
           return {
-            creating: 'creating',
-            running: 'running',
+            loaded: 'loaded',
+            unloaded: 'unloaded',
             error: 'error',
           };
         },
@@ -41,15 +41,15 @@ describe('Test Status', () => {
 
   it('Test props status', () => {
     act(() => {
-      render(<Status status={StatusEnum.creating} />, container);
+      render(<Status status={StatusEnum.loaded} />, container);
     });
 
-    expect(container.querySelector('.label').textContent).toEqual('creating');
+    expect(container.querySelector('.label').textContent).toEqual('loaded');
 
     act(() => {
-      render(<Status status={StatusEnum.running} />, container);
+      render(<Status status={StatusEnum.unloaded} />, container);
     });
 
-    expect(container.querySelector('.label').textContent).toEqual('running');
+    expect(container.querySelector('.label').textContent).toEqual('unloaded');
   });
 });

+ 33 - 0
client/src/components/cards/EmptyCard.tsx

@@ -0,0 +1,33 @@
+import { makeStyles, Theme, Typography } from '@material-ui/core';
+import { FC } from 'react';
+import { EmptyCardProps } from './Types';
+
+const useStyles = makeStyles((theme: Theme) => ({
+  wrapper: {
+    backgroundColor: '#fff',
+    flexDirection: 'column',
+  },
+  text: {
+    marginTop: theme.spacing(4),
+    fontSize: '36px',
+    lineHeight: '42px',
+    color: '#82838e',
+    fontWeight: 'bold',
+    letterSpacing: '-0.02em',
+  },
+}));
+
+const EmptyCard: FC<EmptyCardProps> = ({ icon, text, wrapperClass = '' }) => {
+  const classes = useStyles();
+
+  return (
+    <section
+      className={`flex-center card-wrapper ${classes.wrapper} ${wrapperClass}`}
+    >
+      {icon}
+      <Typography className={classes.text}>{text}</Typography>
+    </section>
+  );
+};
+
+export default EmptyCard;

+ 7 - 0
client/src/components/cards/Types.ts

@@ -0,0 +1,7 @@
+import { ReactElement } from 'react';
+
+export interface EmptyCardProps {
+  icon: ReactElement;
+  text: string;
+  wrapperClass?: string;
+}

+ 1 - 5
client/src/components/customInput/SearchInput.tsx

@@ -38,10 +38,6 @@ const useSearchStyles = makeStyles(theme => ({
     cursor: 'pointer',
   },
   iconWrapper: {
-    display: 'flex',
-    justifyContent: 'center',
-    alignItems: 'center',
-
     opacity: (props: any) => `${props.searched ? 1 : 0}`,
     transition: 'opacity 0.2s',
   },
@@ -94,7 +90,7 @@ const SearchInput: FC<SearchType> = props => {
           endAdornment: (
             <InputAdornment position="end">
               <span
-                className={classes.iconWrapper}
+                className={`flex-center ${classes.iconWrapper}`}
                 onClick={e => {
                   setSearchValue('');
                   inputRef.current.focus();

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

@@ -20,8 +20,9 @@ const CustomToolTip: FC<CustomToolTipType> = props => {
       leaveDelay={leaveDelay}
       title={title}
       placement={placement}
+      arrow
     >
-      <span>{children}</span>
+      <span className="flex-center">{children}</span>
     </Tooltip>
   );
 };

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

@@ -16,11 +16,14 @@ import ExpandLess from '@material-ui/icons/ExpandLess';
 import ExpandMore from '@material-ui/icons/ExpandMore';
 import ArrowBackIosIcon from '@material-ui/icons/ArrowBackIos';
 import ExitToAppIcon from '@material-ui/icons/ExitToApp';
+import ArrowForwardIosIcon from '@material-ui/icons/ArrowForwardIos';
 import { SvgIcon } from '@material-ui/core';
 import { ReactComponent as MilvusIcon } from '../../assets/icons/milvus.svg';
 import { ReactComponent as OverviewIcon } from '../../assets/icons/overview.svg';
 import { ReactComponent as CollectionIcon } from '../../assets/icons/collecion.svg';
 import { ReactComponent as ConsoleIcon } from '../../assets/icons/console.svg';
+import { ReactComponent as InfoIcon } from '../../assets/icons/info.svg';
+import { ReactComponent as ReleaseIcon } from '../../assets/icons/release.svg';
 
 const icons: { [x in IconsType]: (props?: any) => React.ReactElement } = {
   search: (props = {}) => <SearchIcon {...props} />,
@@ -39,6 +42,7 @@ const icons: { [x in IconsType]: (props?: any) => React.ReactElement } = {
   expandMore: (props = {}) => <ExpandMore {...props} />,
   back: (props = {}) => <ArrowBackIosIcon {...props} />,
   logout: (props = {}) => <ExitToAppIcon {...props} />,
+  rightArrow: (props = {}) => <ArrowForwardIosIcon {...props} />,
 
   milvus: (props = {}) => (
     <SvgIcon viewBox="0 0 44 31" component={MilvusIcon} {...props} />
@@ -52,6 +56,12 @@ const icons: { [x in IconsType]: (props?: any) => React.ReactElement } = {
   navConsole: (props = {}) => (
     <SvgIcon viewBox="0 0 20 20" component={ConsoleIcon} {...props} />
   ),
+  info: (props = {}) => (
+    <SvgIcon viewBox="0 0 16 16" component={InfoIcon} {...props} />
+  ),
+  release: (props = {}) => (
+    <SvgIcon viewBox="0 0 16 16" component={ReleaseIcon} {...props} />
+  ),
 };
 
 export default icons;

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

@@ -18,4 +18,7 @@ export type IconsType =
   | 'expandLess'
   | 'expandMore'
   | 'back'
-  | 'logout';
+  | 'logout'
+  | 'rightArrow'
+  | 'info'
+  | 'release';

+ 2 - 7
client/src/components/layout/Header.tsx

@@ -26,7 +26,6 @@ const useStyles = makeStyles((theme: Theme) =>
     navigation: {
       display: 'flex',
       alignItems: 'center',
-      fontWeight: 'bold',
     },
     icon: {
       color: theme.palette.primary.main,
@@ -39,10 +38,6 @@ const useStyles = makeStyles((theme: Theme) =>
       '& .text': {
         marginRight: theme.spacing(3),
 
-        '& p': {
-          margin: 0,
-        },
-
         '& .address': {
           fontSize: '14px',
           lineHeight: '20px',
@@ -97,8 +92,8 @@ const Header: FC<HeaderType> = props => {
 
         <div className={classes.addressWrapper}>
           <div className="text">
-            <p className="address">{address}</p>
-            <p className="status">{statusTrans.running}</p>
+            <Typography className="address">{address}</Typography>
+            <Typography className="status">{statusTrans.running}</Typography>
           </div>
           <LogoutIcon classes={{ root: classes.icon }} onClick={handleLogout} />
         </div>

+ 5 - 2
client/src/components/menu/NavMenu.tsx

@@ -8,6 +8,7 @@ import Collapse from '@material-ui/core/Collapse';
 import { NavMenuItem, NavMenuType } from './Types';
 import icons from '../icons/Icons';
 import { useTranslation } from 'react-i18next';
+import Typography from '@material-ui/core/Typography';
 
 const useStyles = makeStyles((theme: Theme) =>
   createStyles({
@@ -63,7 +64,7 @@ const useStyles = makeStyles((theme: Theme) =>
 
       marginBottom: theme.spacing(8),
 
-      '& h3': {
+      '& .title': {
         margin: 0,
         fontSize: '16px',
         lineHeight: '19px',
@@ -159,7 +160,9 @@ const NavMenu: FC<NavMenuType> = props => {
       <div>
         <div className={classes.logoWrapper}>
           <Logo classes={{ root: classes.logo }} />
-          <h3>{milvusTrans.admin}</h3>
+          <Typography variant="h3" className="title">
+            {milvusTrans.admin}
+          </Typography>
         </div>
 
         <NestList data={data} />

+ 7 - 7
client/src/components/status/Status.tsx

@@ -10,8 +10,8 @@ const useStyles = makeStyles((theme: Theme) =>
       alignItems: 'center',
     },
     label: {
-      fontWeight: 'bold',
-      textTransform: 'uppercase',
+      color: '#82838e',
+      textTransform: 'capitalize',
     },
     circle: {
       backgroundColor: (props: any) => props.color,
@@ -45,15 +45,15 @@ const Status: FC<StatusType> = props => {
   const statusTrans: { [key in string]: string } = t('status');
   const { label, color } = useMemo(() => {
     switch (status) {
-      case StatusEnum.creating:
+      case StatusEnum.unloaded:
         return {
-          label: statusTrans.creating,
+          label: statusTrans.unloaded,
           color: '#06aff2',
         };
 
-      case StatusEnum.running:
+      case StatusEnum.loaded:
         return {
-          label: statusTrans.running,
+          label: statusTrans.loaded,
           color: '#06f3af',
         };
       case StatusEnum.error:
@@ -76,7 +76,7 @@ const Status: FC<StatusType> = props => {
     <div className={classes.root}>
       <div
         className={`${classes.circle} ${
-          status === StatusEnum.creating ? classes.flash : ''
+          status === StatusEnum.unloaded ? classes.flash : ''
         }`}
       ></div>
       <Typography variant="body2" className={classes.label}>

+ 2 - 2
client/src/components/status/Types.ts

@@ -1,6 +1,6 @@
 export enum StatusEnum {
-  'creating',
-  'running',
+  'unloaded',
+  'loaded',
   'error',
 }
 export type StatusType = {

+ 0 - 1
client/src/http/Axios.ts

@@ -21,7 +21,6 @@ axiosInstance.interceptors.request.use(
     // Do something before request is sent
     const session = window.localStorage.getItem(SESSION);
 
-    // console.log('in----', token);
     session && (config.headers[SESSION] = session);
 
     return config;

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

@@ -8,6 +8,7 @@ const btnTrans = {
   connect: '连接',
   import: '导入',
   delete: '删除',
+  release: 'Release',
 };
 
 export default btnTrans;

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

@@ -0,0 +1,7 @@
+const collectionTrans = {
+  noLoadData: 'No Loaded Collection',
+  rowCount: 'Row Count',
+  tooltip: 'data in one row',
+};
+
+export default collectionTrans;

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

@@ -4,8 +4,8 @@ const commonTrans = {
     address: 'Milvus Address',
   },
   status: {
-    creating: 'creating',
-    running: 'running',
+    loaded: 'loaded',
+    unloaded: 'unloaded',
     error: 'error',
   },
   grid: {

+ 8 - 0
client/src/i18n/cn/overview.ts

@@ -0,0 +1,8 @@
+const overviewTrans = {
+  load: 'Loaded Collections',
+  all: 'All Collections',
+  data: 'Data',
+  rows: '{{number}} Rows',
+};
+
+export default overviewTrans;

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

@@ -9,6 +9,7 @@ const btnTrans = {
   connect: 'Connect',
   import: 'Import',
   delete: 'Delete',
+  release: 'Release',
 };
 
 export default btnTrans;

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

@@ -0,0 +1,7 @@
+const collectionTrans = {
+  noLoadData: 'No Loaded Collection',
+  rowCount: 'Row Count',
+  tooltip: 'data in one row',
+};
+
+export default collectionTrans;

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

@@ -4,8 +4,8 @@ const commonTrans = {
     address: 'Milvus Address',
   },
   status: {
-    creating: 'creating',
-    running: 'running',
+    loaded: 'loaded',
+    unloaded: 'unloaded',
     error: 'error',
   },
   grid: {

+ 8 - 0
client/src/i18n/en/overview.ts

@@ -0,0 +1,8 @@
+const overviewTrans = {
+  load: 'Loaded Collections',
+  all: 'All Collections',
+  data: 'Data',
+  rows: '{{number}} Rows',
+};
+
+export default overviewTrans;

+ 8 - 0
client/src/i18n/index.ts

@@ -9,6 +9,10 @@ import warningCn from './cn/warning';
 import warningEn from './en/warning';
 import navCn from './cn/nav';
 import navEn from './en/nav';
+import overviewCn from './cn/overview';
+import overviewEn from './en/overview';
+import collectionCn from './cn/collection';
+import collectionEn from './en/collection';
 
 export const resources = {
   cn: {
@@ -16,12 +20,16 @@ export const resources = {
     btn: buttonCn,
     warning: warningCn,
     nav: navCn,
+    overview: overviewCn,
+    collection: collectionCn,
   },
   en: {
     translation: commonEn,
     btn: buttonEn,
     warning: warningEn,
     nav: navEn,
+    overview: overviewEn,
+    collection: collectionEn,
   },
 };
 

+ 2 - 0
client/src/index.css

@@ -1,3 +1,5 @@
+@import './styles/common.css';
+
 body {
   margin: 0;
   font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',

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

@@ -1,4 +1,4 @@
-import { Theme, makeStyles } from '@material-ui/core';
+import { Theme, makeStyles, Typography } from '@material-ui/core';
 import { useTranslation } from 'react-i18next';
 import { ITextfieldConfig } from '../../components/customInput/Types';
 import icons from '../../components/icons/Icons';
@@ -24,9 +24,10 @@ const useStyles = makeStyles((theme: Theme) => ({
     padding: theme.spacing(3),
     margin: '0 auto',
 
-    '& h2': {
+    '& .title': {
       margin: 0,
       color: '#323232',
+      fontWeight: 'bold',
     },
   },
   logo: {
@@ -90,7 +91,9 @@ const Connect = () => {
       <section className={classes.wrapper}>
         <div className={classes.titleWrapper}>
           <Logo classes={{ root: classes.logo }} />
-          <h2>{milvusTrans.admin}</h2>
+          <Typography variant="h2" className="title">
+            {milvusTrans.admin}
+          </Typography>
         </div>
         <CustomInput
           type="text"

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

@@ -9,10 +9,6 @@ const getContainerStyles = makeStyles((theme: Theme) => ({
     backgroundImage: `url(${backgroundPath})`,
     backgroundRepeat: 'no-repeat',
     backgroundSize: 'cover',
-
-    display: 'flex',
-    justifyContent: 'center',
-    alignItems: 'center',
   },
   card: {
     width: '480px',
@@ -28,7 +24,7 @@ const ConnectContainer = ({ children }: { children: ReactElement }) => {
   const classes = getContainerStyles();
 
   return (
-    <main className={classes.wrapper}>
+    <main className={`flex-center ${classes.wrapper}`}>
       <section className={classes.card}>{children}</section>
     </main>
   );

+ 121 - 1
client/src/pages/overview/Overview.tsx

@@ -1,10 +1,130 @@
+import { makeStyles, Theme, Typography } from '@material-ui/core';
+import { useTranslation } from 'react-i18next';
+import EmptyCard from '../../components/cards/EmptyCard';
+import icons from '../../components/icons/Icons';
+import { StatusEnum } from '../../components/status/Types';
 import { useNavigationHook } from '../../hooks/Navigation';
 import { ALL_ROUTER_TYPES } from '../../router/Types';
+import { formatNumber } from '../../utils/Common';
+import CollectionCard from './collectionCard/CollectionCard';
+import { CollectionData } from './collectionCard/Types';
+import StatisticsCard from './statisticsCard/StatisticsCard';
+import { StatisticsCardProps } from './statisticsCard/Types';
+
+const useStyles = makeStyles((theme: Theme) => ({
+  wrapper: {
+    margin: theme.spacing(2, 5),
+  },
+  collectionTitle: {
+    margin: theme.spacing(2, 0),
+    lineHeight: '20px',
+    fontSize: '14px',
+    color: '#82838e',
+  },
+  empty: {
+    flexGrow: 1,
+  },
+  emptyIcon: {
+    width: '48px',
+    height: '48px',
+    fill: 'transparent',
+
+    '& path': {
+      stroke: '#aeaebb',
+    },
+  },
+  cardsWrapper: {
+    display: 'grid',
+    gridTemplateColumns: 'repeat(auto-fill, minmax(380px, 1fr))',
+    gap: '10px',
+  },
+}));
 
 const Overview = () => {
   useNavigationHook(ALL_ROUTER_TYPES.OVERVIEW);
+  const classes = useStyles();
+  const { t } = useTranslation('overview');
+  const { t: collectionTrans } = useTranslation('collection');
+
+  const mockStatistics: StatisticsCardProps = {
+    data: [
+      {
+        label: t('load'),
+        value: formatNumber(4337),
+        valueColor: '#07d197',
+      },
+      {
+        label: t('all'),
+        value: formatNumber(30000),
+        valueColor: '#06aff2',
+      },
+      {
+        label: t('data'),
+        value: t('rows', { number: formatNumber(209379100) }),
+        valueColor: '#0689d2',
+      },
+    ],
+  };
+
+  const mockCollections: CollectionData[] = [
+    {
+      name: 'collection1',
+      id: 'c1',
+      status: StatusEnum.loaded,
+      rowCount: 2,
+    },
+    {
+      name: 'collection2',
+      id: 'c2',
+      status: StatusEnum.loaded,
+      rowCount: 2,
+    },
+    {
+      name: 'collection3',
+      id: 'c3',
+      status: StatusEnum.loaded,
+      rowCount: 2,
+    },
+    {
+      name: 'collection4',
+      id: 'c4',
+      status: StatusEnum.loaded,
+      rowCount: 2,
+    },
+    {
+      name: 'collection5',
+      id: 'c5',
+      status: StatusEnum.loaded,
+      rowCount: 2,
+    },
+    {
+      name: 'collection6',
+      id: 'c6',
+      status: StatusEnum.loaded,
+      rowCount: 2,
+    },
+  ];
+  const CollectionIcon = icons.navCollection;
 
-  return <section>overview</section>;
+  return (
+    <section className={`page-wrapper ${classes.wrapper}`}>
+      <StatisticsCard data={mockStatistics.data} />
+      <Typography className={classes.collectionTitle}>{t('load')}</Typography>
+      {mockCollections.length > 0 ? (
+        <div className={classes.cardsWrapper}>
+          {mockCollections.map(collection => (
+            <CollectionCard key={collection.id} data={collection} />
+          ))}
+        </div>
+      ) : (
+        <EmptyCard
+          wrapperClass={classes.empty}
+          icon={<CollectionIcon classes={{ root: classes.emptyIcon }} />}
+          text={collectionTrans('noLoadData')}
+        />
+      )}
+    </section>
+  );
 };
 
 export default Overview;

+ 97 - 0
client/src/pages/overview/collectionCard/CollectionCard.tsx

@@ -0,0 +1,97 @@
+import {
+  makeStyles,
+  Theme,
+  Link,
+  Typography,
+  Divider,
+} from '@material-ui/core';
+import { FC } from 'react';
+import CustomButton from '../../../components/customButton/CustomButton';
+import icons from '../../../components/icons/Icons';
+import Status from '../../../components/status/Status';
+import CustomToolTip from '../../../components/customToolTip/CustomToolTip';
+import { CollectionCardProps } from './Types';
+import { useTranslation } from 'react-i18next';
+
+const useStyles = makeStyles((theme: Theme) => ({
+  wrapper: {
+    padding: theme.spacing(2),
+    textAlign: 'end',
+  },
+  link: {
+    display: 'flex',
+    alignItems: 'center',
+
+    margin: theme.spacing(2, 0),
+
+    color: '#010e29',
+    fontSize: '20px',
+    lineHeight: '24px',
+    fontWeight: 'bold',
+  },
+  icon: {
+    color: theme.palette.primary.main,
+    marginLeft: theme.spacing(0.5),
+    fontSize: '16px',
+  },
+  content: {
+    display: 'flex',
+    alignItems: 'center',
+    marginBottom: theme.spacing(2),
+  },
+  rowCount: {
+    marginLeft: theme.spacing(3),
+  },
+  divider: {
+    marginBottom: theme.spacing(2),
+  },
+  release: {
+    marginLeft: 0,
+    marginRight: theme.spacing(0.5),
+  },
+}));
+
+const CollectionCard: FC<CollectionCardProps> = ({
+  data,
+  wrapperClass = '',
+}) => {
+  const classes = useStyles();
+  const { name, status, id, rowCount } = data;
+  const RightArrowIcon = icons.rightArrow;
+  const InfoIcon = icons.info;
+  const ReleaseIcon = icons.release;
+  const { t } = useTranslation('collection');
+  const { t: btnTrans } = useTranslation('btn');
+
+  const handleRelease = () => {};
+
+  return (
+    <div className={`card-wrapper ${classes.wrapper} ${wrapperClass}`}>
+      <div>
+        <Status status={status} />
+      </div>
+      <Link
+        classes={{ root: classes.link }}
+        underline="none"
+        href={`/collection/${id}`}
+      >
+        {name}
+        <RightArrowIcon classes={{ root: classes.icon }} />
+      </Link>
+      <div className={classes.content}>
+        <Typography>{t('rowCount')}</Typography>
+        <CustomToolTip title={t('tooltip')} placement="bottom">
+          <InfoIcon classes={{ root: classes.icon }} />
+        </CustomToolTip>
+        <Typography className={classes.rowCount}>{rowCount}</Typography>
+      </div>
+      <Divider classes={{ root: classes.divider }} />
+      <CustomButton variant="contained" onClick={handleRelease}>
+        <ReleaseIcon classes={{ root: `${classes.icon} ${classes.release}` }} />
+        {btnTrans('release')}
+      </CustomButton>
+    </div>
+  );
+};
+
+export default CollectionCard;

+ 13 - 0
client/src/pages/overview/collectionCard/Types.ts

@@ -0,0 +1,13 @@
+import { StatusEnum } from '../../../components/status/Types';
+
+export interface CollectionCardProps {
+  data: CollectionData;
+  wrapperClass?: string;
+}
+
+export interface CollectionData {
+  name: string;
+  status: StatusEnum;
+  id: string;
+  rowCount: number;
+}

+ 52 - 0
client/src/pages/overview/statisticsCard/StatisticsCard.tsx

@@ -0,0 +1,52 @@
+import { makeStyles, Theme, Typography } from '@material-ui/core';
+import { FC } from 'react';
+import { StatisticsCardProps } from './Types';
+
+const useStyles = makeStyles((theme: Theme) => ({
+  wrapper: {
+    display: `grid`,
+    gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))',
+    columnGap: '20px',
+
+    padding: theme.spacing(3),
+  },
+  itemWrapper: {
+    paddingLeft: theme.spacing(1),
+    borderLeft: '3px solid #f0f4f9',
+  },
+  label: {
+    fontSize: '12px',
+    lineHeight: '16px',
+    color: '#010e29',
+  },
+  value: {
+    fontSize: '24px',
+    lineHeight: '28px',
+    fontWeight: 'bold',
+  },
+}));
+
+const StatisticsCard: FC<StatisticsCardProps> = ({
+  data = [],
+  wrapperClass = '',
+}) => {
+  const classes = useStyles();
+
+  return (
+    <div className={`card-wrapper ${classes.wrapper} ${wrapperClass}`}>
+      {data.map(item => (
+        <div key={item.label} className={classes.itemWrapper}>
+          <Typography className={classes.label}>{item.label}</Typography>
+          <Typography
+            className={classes.value}
+            style={{ color: item.valueColor }}
+          >
+            {item.value}
+          </Typography>
+        </div>
+      ))}
+    </div>
+  );
+};
+
+export default StatisticsCard;

+ 10 - 0
client/src/pages/overview/statisticsCard/Types.ts

@@ -0,0 +1,10 @@
+import { KeyValuePair } from '../../../types/Common';
+
+export interface StatisticsCardProps {
+  wrapperClass?: string;
+  data: Item[];
+}
+
+export interface Item extends KeyValuePair {
+  valueColor: string;
+}

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

@@ -0,0 +1,19 @@
+/* horizontal and vertical center */
+.flex-center {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+.card-wrapper {
+  background-color: #fff;
+  border-radius: 4px;
+  box-shadow: 3px 3px 10px rgba(0, 0, 0, 0.05);
+}
+
+/* used for fill remaining height of page */
+.page-wrapper {
+  flex-grow: 1;
+  display: flex;
+  flex-direction: column;
+}

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

@@ -22,8 +22,7 @@ export const generateId = (prefix = 'id') =>
   `${prefix}_${Math.random().toString(36).substr(2, 16)}`;
 
 export const formatNumber = (number: number): string => {
-  const n = number / 100;
-  return (Math.floor(n * 100) / 100).toFixed(2);
+  return new Intl.NumberFormat().format(number);
 };
 
 /**

+ 2 - 2
client/src/utils/Status.ts

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