Browse Source

add connect

tumao 4 years ago
parent
commit
8dcd871776
32 changed files with 224 additions and 480 deletions
  1. 5 0
      client/src/assets/icons/milvus.svg
  2. BIN
      client/src/assets/imgs/connectContainer/background.png
  3. BIN
      client/src/assets/imgs/logo.png
  4. 1 2
      client/src/components/__test__/grid/Toolbar.spec.tsx
  5. 2 2
      client/src/components/__test__/textField/customInput.spec.tsx
  6. 0 180
      client/src/components/customCard/CustomCard.tsx
  7. 0 19
      client/src/components/customCard/Types.ts
  8. 2 8
      client/src/components/customInput/CustomInput.tsx
  9. 0 0
      client/src/components/customInput/SearchInput.tsx
  10. 0 0
      client/src/components/customInput/Types.ts
  11. 1 1
      client/src/components/grid/ToolBar.tsx
  12. 1 1
      client/src/components/grid/Types.ts
  13. 6 0
      client/src/components/icons/Icons.tsx
  14. 2 1
      client/src/components/icons/Types.ts
  15. 1 48
      client/src/components/layout/Header.tsx
  16. 10 10
      client/src/components/layout/Layout.tsx
  17. 4 2
      client/src/components/menu/NavMenu.tsx
  18. 1 3
      client/src/hooks/Form.ts
  19. 6 1
      client/src/hooks/__test__/Form.spec.tsx
  20. 3 15
      client/src/i18n/cn/button.ts
  21. 7 63
      client/src/i18n/cn/common.ts
  22. 3 17
      client/src/i18n/cn/warning.ts
  23. 3 16
      client/src/i18n/en/button.ts
  24. 7 64
      client/src/i18n/en/common.ts
  25. 3 18
      client/src/i18n/en/warning.ts
  26. 1 0
      client/src/index.tsx
  27. 107 1
      client/src/pages/connect/Connect.tsx
  28. 7 7
      client/src/pages/connect/ConnectContainer.tsx
  29. 6 0
      client/src/router/Config.ts
  30. 1 1
      client/src/router/Router.tsx
  31. 17 0
      client/src/utils/Form.ts
  32. 17 0
      client/src/utils/__test__/Form.spec.ts

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

@@ -0,0 +1,5 @@
+<svg width="44" height="31" viewBox="0 0 44 31" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M30.4251 5.17564C24.7152 -0.554964 15.4562 -0.554964 9.74634 5.17564L0.41376 14.5419C-0.13792 15.0962 -0.13792 15.9886 0.41376 16.5429L9.74634 25.9091C15.4562 31.6397 24.7152 31.6397 30.4251 25.9185C36.1442 20.1973 36.1442 10.9062 30.4251 5.17564ZM28.2184 23.166C24.0256 27.3747 17.2216 27.3747 13.0288 23.166L6.16042 16.2798C5.75586 15.8759 5.75586 15.2183 6.16042 14.8049L13.0196 7.9282C17.2124 3.7195 24.0165 3.7195 28.2092 7.9282C32.4112 12.1369 32.4112 18.9573 28.2184 23.166Z" fill="#4FC4F9"/>
+<path d="M42.6532 14.5515L38.5431 10.3522C38.2949 10.0986 37.8811 10.3334 37.9639 10.681C38.6719 13.8845 38.6719 17.2289 37.9639 20.4324C37.8903 20.78 38.3041 21.0055 38.5431 20.7612L42.6532 16.5619C43.1956 15.9983 43.1956 15.1058 42.6532 14.5515Z" fill="#4FC4F9"/>
+<path d="M20.6691 22.9126C24.6453 22.9126 27.8686 19.6193 27.8686 15.5568C27.8686 11.4942 24.6453 8.20093 20.6691 8.20093C16.693 8.20093 13.4697 11.4942 13.4697 15.5568C13.4697 19.6193 16.693 22.9126 20.6691 22.9126Z" fill="#4FC4F9"/>
+</svg>

BIN
client/src/assets/imgs/connectContainer/background.png


BIN
client/src/assets/imgs/logo.png


+ 1 - 2
client/src/components/__test__/grid/Toolbar.spec.tsx

@@ -15,14 +15,13 @@ jest.mock('../../customButton/CustomButton', () => {
   };
 });
 
-jest.mock('../../textField/SearchInput', () => {
+jest.mock('../../customInput/SearchInput', () => {
   return props => {
     return <div>{props.children}</div>;
   };
 });
 
 jest.mock('@material-ui/core/TextField', () => {
-  console.log('test enter');
   return props => {
     return <input {...props} className="input" />;
   };

+ 2 - 2
client/src/components/__test__/textField/customInput.spec.tsx

@@ -1,12 +1,12 @@
 import { fireEvent } from '@testing-library/react';
 import { render, unmountComponentAtNode } from 'react-dom';
 import { act } from 'react-dom/test-utils';
-import CustomInput from '../../textField/CustomInput';
+import CustomInput from '../../customInput/CustomInput';
 import {
   IAdornmentConfig,
   IIconConfig,
   ITextfieldConfig,
-} from '../../textField/Types';
+} from '../../customInput/Types';
 
 let container: any = null;
 

+ 0 - 180
client/src/components/customCard/CustomCard.tsx

@@ -1,180 +0,0 @@
-import {
-  Card,
-  CardActions,
-  CardContent,
-  CardHeader,
-  IconButton,
-  makeStyles,
-  Menu,
-  MenuItem,
-  Theme,
-  Tooltip,
-} from '@material-ui/core';
-import { FC } from 'react';
-import React from 'react';
-import icons from '../icons/Icons';
-import { ICustomCardProps, IMenu } from './Types';
-
-const getStyles = makeStyles((theme: Theme) => ({
-  root: {
-    boxShadow: 'none',
-    filter: 'drop-shadow(0px 8px 24px rgba(0, 0, 0, 0.1))',
-    width: '100%',
-
-    position: 'relative',
-  },
-  menuItem: {
-    minWidth: '160px',
-    textTransform: 'capitalize',
-
-    '&:hover': {
-      backgroundColor: theme.palette.primary.light,
-    },
-  },
-  menuPaper: {
-    boxShadow: '0px 8px 16px rgba(0, 0, 0, 0.15)',
-  },
-  menuContent: {
-    padding: 0,
-
-    '&:last-child': {
-      paddingBottom: 0,
-    },
-  },
-  actions: {
-    '&:hover': {
-      background:
-        'linear-gradient(0deg, rgba(18, 195, 244, 0.05), rgba(18, 195, 244, 0.05)), #fff',
-
-      transition: 'all 0.3s',
-    },
-  },
-
-  actionsDisable: {
-    '&:hover': {
-      backgroundColor: '#fff',
-    },
-  },
-  actionBtn: {
-    color: theme.palette.primary.main,
-  },
-
-  mask: {
-    position: 'absolute',
-    top: 0,
-    right: 0,
-    left: 0,
-    bottom: 0,
-    backgroundColor: 'rgba(196, 196, 196, 0.5)',
-    zIndex: theme.zIndex.modal,
-  },
-}));
-
-const CustomCard: FC<ICustomCardProps> = props => {
-  const {
-    showCardHeaderTitle = true,
-    cardHeaderTitle = '',
-    menu = [],
-    content,
-    actions,
-    wrapperClassName = '',
-    showMask,
-    actionsDisabled = false,
-  } = props;
-  const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
-
-  const handleMoreClick = (event: React.MouseEvent<HTMLButtonElement>) => {
-    setAnchorEl(event.currentTarget);
-  };
-
-  const handleMenuClose = () => {
-    setAnchorEl(null);
-  };
-
-  const classes = getStyles();
-
-  const handleClick = (event: any, m: IMenu) => {
-    if (m.onClick) {
-      m.onClick(event);
-    }
-
-    handleMenuClose();
-  };
-
-  return (
-    <Card className={`${classes.root} ${wrapperClassName}`}>
-      {showMask && <div className={classes.mask}></div>}
-      <CardHeader
-        action={
-          menu.length > 0 && (
-            <>
-              <IconButton aria-label="settings" onClick={handleMoreClick}>
-                {icons.more({ classes: { root: classes.actionBtn } })}
-              </IconButton>
-              <Menu
-                anchorEl={anchorEl}
-                disableScrollLock={true}
-                keepMounted
-                open={Boolean(anchorEl)}
-                onClose={handleMenuClose}
-                getContentAnchorEl={null}
-                anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
-                transformOrigin={{ vertical: 'top', horizontal: 'center' }}
-                classes={{ paper: classes.menuPaper }}
-              >
-                {menu.map((m, index) =>
-                  typeof m.label === 'string' ? (
-                    m.tip ? (
-                      <Tooltip
-                        key={m.label}
-                        title={m.tip}
-                        placement="right-end"
-                      >
-                        <span style={{ display: 'block' }}>
-                          <MenuItem
-                            classes={{ root: classes.menuItem }}
-                            onClick={event => handleClick(event, m)}
-                            disabled={m.disabled}
-                          >
-                            {m.label}
-                          </MenuItem>
-                        </span>
-                      </Tooltip>
-                    ) : (
-                      <MenuItem
-                        classes={{ root: classes.menuItem }}
-                        key={m.label}
-                        onClick={event => handleClick(event, m)}
-                        disabled={m.disabled}
-                      >
-                        {m.label}
-                      </MenuItem>
-                    )
-                  ) : (
-                    <span key={index}>{m.label}</span>
-                  )
-                )}
-              </Menu>
-            </>
-          )
-        }
-        title={showCardHeaderTitle ? cardHeaderTitle : null}
-      />
-      <CardContent classes={{ root: classes.menuContent }}>
-        {content}
-      </CardContent>
-      {actions && (
-        <CardActions
-          classes={{
-            root: actionsDisabled ? classes.actionsDisable : classes.actions,
-          }}
-          disableSpacing
-        >
-          {actions}
-        </CardActions>
-      )}
-    </Card>
-  );
-};
-
-export default CustomCard;

+ 0 - 19
client/src/components/customCard/Types.ts

@@ -1,19 +0,0 @@
-import { ReactElement } from 'react';
-
-export interface IMenu {
-  label: string | ReactElement;
-  onClick?: (event: any) => void;
-  disabled?: boolean;
-  tip?: string | null;
-}
-
-export interface ICustomCardProps {
-  showCardHeaderTitle?: boolean;
-  cardHeaderTitle?: string | ReactElement;
-  menu?: IMenu[];
-  content: string | ReactElement;
-  actions?: ReactElement;
-  wrapperClassName?: string;
-  showMask?: boolean;
-  actionsDisabled?: boolean;
-}

+ 2 - 8
client/src/components/textField/CustomInput.tsx → client/src/components/customInput/CustomInput.tsx

@@ -148,14 +148,8 @@ const getIconInput = (
   checkValid: Function,
   validInfo: IValidInfo
 ): ReactElement => {
-  const {
-    icon,
-    inputType,
-    inputConfig,
-    containerClass,
-    spacing,
-    alignItems,
-  } = config;
+  const { icon, inputType, inputConfig, containerClass, spacing, alignItems } =
+    config;
   return (
     <Grid
       classes={{ container: `${containerClass || {}}` }}

+ 0 - 0
client/src/components/textField/SearchInput.tsx → client/src/components/customInput/SearchInput.tsx


+ 0 - 0
client/src/components/textField/Types.ts → client/src/components/customInput/Types.ts


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

@@ -10,7 +10,7 @@ import {
 import CustomButton from '../customButton/CustomButton';
 import Icons from '../icons/Icons';
 import { ToolBarConfig, ToolBarType } from './Types';
-import SearchInput from '../textField/SearchInput';
+import SearchInput from '../customInput/SearchInput';
 import TableSwitch from './TableSwitch';
 import { throwErrorForDev } from '../../utils/Common';
 import CustomIconButton from '../customButton/CustomIconButton';

+ 1 - 1
client/src/components/grid/Types.ts

@@ -1,5 +1,5 @@
 import { IconsType } from '../icons/Types';
-import { SearchType } from '../textField/Types';
+import { SearchType } from '../customInput/Types';
 import { ReactElement } from 'react';
 
 export type IconConfigType = {

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

@@ -13,6 +13,9 @@ import MoreVertIcon from '@material-ui/icons/MoreVert';
 import CancelIcon from '@material-ui/icons/Cancel';
 import CheckCircleIcon from '@material-ui/icons/CheckCircle';
 
+import { SvgIcon } from '@material-ui/core';
+import { ReactComponent as MilvusIcon } from '../../assets/icons/milvus.svg';
+
 const icons: { [x in IconsType]: (props?: any) => React.ReactElement } = {
   search: (props = {}) => <SearchIcon {...props} />,
   add: (props = {}) => <AddIcon {...props} />,
@@ -26,6 +29,9 @@ const icons: { [x in IconsType]: (props?: any) => React.ReactElement } = {
   more: (props = {}) => <MoreVertIcon {...props} />,
   app: (props = {}) => <AppsIcon {...props} />,
   success: (props = {}) => <CheckCircleIcon {...props} />,
+  milvus: (props = {}) => (
+    <SvgIcon viewBox="0 0 44 31" component={MilvusIcon} {...props} />
+  ),
 };
 
 export default icons;

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

@@ -10,4 +10,5 @@ export type IconsType =
   | 'clear'
   | 'app'
   | 'more'
-  | 'success';
+  | 'success'
+  | 'milvus';

+ 1 - 48
client/src/components/layout/Header.tsx

@@ -11,60 +11,13 @@ const useStyles = makeStyles((theme: Theme) =>
       color: theme.palette.common.black,
       marginRight: theme.spacing(5),
     },
-    contentWrapper: {
-      display: 'flex',
-      justifyContent: 'space-between',
-      alignItems: 'center',
-      paddingTop: theme.spacing(3),
-      paddingLeft: theme.spacing(6),
-      flex: 1,
-    },
-    navigation: {
-      display: 'flex',
-      alignItems: 'center',
-      fontWeight: 'bold',
-      '& svg': {
-        fontSize: '16px',
-        cursor: 'pointer',
-      },
-    },
-    changePwdTip: {
-      width: '420px',
-      textAlign: 'center',
-      '& span': {
-        fontStyle: 'italic',
-      },
-    },
-    user: {
-      display: 'flex',
-    },
-    menuLabel: {
-      height: '100%',
-      color: '#010e29',
-      fontSize: '14px',
-      lineHeight: '20px',
-
-      '&:hover': {
-        backgroundColor: 'transparent',
-      },
-    },
-    arrow: {
-      color: theme.palette.primary.main,
-    },
-    icon: {
-      color: theme.palette.primary.main,
-    },
   })
 );
 
 const Header: FC<HeaderType> = props => {
   const classes = useStyles();
 
-  return (
-    <header className={classes.header}>
-      <div className={classes.contentWrapper}>header</div>
-    </header>
-  );
+  return <header className={classes.header}>header</header>;
 };
 
 export default Header;

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

@@ -26,29 +26,29 @@ const useStyles = makeStyles((theme: Theme) =>
 );
 
 const Layout = (props: any) => {
-  // const history = useHistory();
   const path = window.location.hash.slice(2);
   const greyPaths = ['', 'billing'];
   const bgColor = greyPaths.includes(path) ? '#f5f5f5' : '#fff';
   const classes = useStyles({ backgroundColor: bgColor });
 
-  // const { t } = useTranslation();
-
   const data: NavMenuItem[] = [];
+  const isAuth = false;
 
   return (
     <div className={classes.root}>
       <GlobalEffect>
         <div className={classes.content}>
-          <NavMenu
-            width="200px"
-            data={data}
-            defaultActive="Lock"
-            defaultOpen={{ security: true }}
-          />
+          {isAuth && (
+            <NavMenu
+              width="200px"
+              data={data}
+              defaultActive="Lock"
+              defaultOpen={{ security: true }}
+            />
+          )}
 
           <div className={classes.body}>
-            <Header />
+            {isAuth && <Header />}
             {props.children}
           </div>
         </div>

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

@@ -8,9 +8,9 @@ import Collapse from '@material-ui/core/Collapse';
 import ExpandLess from '@material-ui/icons/ExpandLess';
 import ExpandMore from '@material-ui/icons/ExpandMore';
 import { NavMenuItem, NavMenuType } from './Types';
-import logoPath from '../../assets/imgs/logo.png';
 import { useTranslation } from 'react-i18next';
 import { useLocation } from 'react-router';
+import icons from '../icons/Icons';
 
 const useStyles = makeStyles((theme: Theme) =>
   createStyles({
@@ -170,11 +170,13 @@ const NavMenu: FC<NavMenuType> = props => {
     );
   };
 
+  const Logo = icons.milvus;
+
   return (
     <List component="nav" className={classes.root}>
       <div>
         <div className={classes.logoWrapper}>
-          <img className={classes.logo} src={logoPath} alt="cloud logo" />
+          <Logo classes={{ root: classes.logo }} />
         </div>
 
         <NestList data={data} />

+ 1 - 3
client/src/hooks/Form.ts

@@ -1,5 +1,5 @@
 import { useState } from 'react';
-import { IValidation } from '../components/textField/Types';
+import { IValidation } from '../components/customInput/Types';
 import { checkIsEmpty, getCheckResult } from '../utils/Validation';
 
 export interface IForm {
@@ -48,8 +48,6 @@ export const useFormValidation = (form: IForm[]): IValidationInfo => {
       {}
     );
 
-  console.log('init validation', initValidation);
-
   // validation detail about form item
   const [validation, setValidation] = useState(initValidation);
   // overall validation result to control following actions

+ 6 - 1
client/src/hooks/__test__/Form.spec.tsx

@@ -1,6 +1,11 @@
 import { render } from '@testing-library/react';
 import { IForm, useFormValidation } from '../Form';
 
+// jest.mock('react', () => ({
+//   ...jest.requireActual('react'),
+//   useState: jest.fn().mockReturnValue([[], jest.fn]),
+// }));
+
 const mockForm: IForm[] = [
   {
     key: 'username',
@@ -30,7 +35,7 @@ test('test useFormValidation hook', () => {
   const { checkFormValid, checkIsValid, validation } = setupUseFormValidation();
 
   expect(checkFormValid(mockForm)).toBeFalsy();
-  expect(validation).toEqual([]);
+  expect(Object.keys(validation)).toEqual(['username']);
   expect(
     checkIsValid({
       value: '',

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

@@ -1,4 +1,4 @@
-export default {
+const btnTrans = {
   cancel: '取消',
   save: '保存',
   reset: '重置',
@@ -8,18 +8,6 @@ export default {
   connect: '连接',
   import: '导入',
   delete: '删除',
-  export: '导出',
-  verify: '验证邮件',
-  login: '登录',
-  resendEmail: '重发邮件',
-  createAccount: '创建账号',
-  signIn: '登录',
-  new: '新增',
-  signOut: '退出',
-  filter: '筛选',
-  print: '打印',
-  downloadCSV: '下载 csv 文件',
-  drop: 'Drop',
-  terminate: '停止',
-  send: '发送',
 };
+
+export default btnTrans;

+ 7 - 63
client/src/i18n/cn/common.ts

@@ -1,4 +1,8 @@
-export default {
+const commonTrans = {
+  milvus: {
+    admin: 'Milvus Admin',
+    address: 'Milvus Address',
+  },
   status: {
     creating: 'creating',
     running: 'running',
@@ -12,66 +16,6 @@ export default {
     nextLabel: 'next page',
     prevLabel: 'prev page',
   },
-  nav: {
-    database: 'Database',
-    index: 'Index',
-    query: 'Query Service',
-    task: 'Task',
-
-    menu: {
-      database: 'Database',
-      collection: 'Collection',
-      partition: 'Partition',
-      index: 'Index',
-      query: 'Query',
-    },
-  },
-  copy: {
-    copy: 'Copy',
-    copied: 'Copied',
-  },
-  header: {
-    navigation: {
-      allDatabases: 'All Databases',
-      allTasks: 'Task',
-      allQueries: 'All Queries',
-    },
-  },
-  oauth: {
-    divider: 'OR',
-    signIn: {
-      google: 'Sign in with Google',
-      success: 'Sign in successfully',
-    },
-    signUp: {
-      google: 'Sign up with Google',
-    },
-  },
-  stepper: {
-    back: 'Back',
-    next: 'Next',
-  },
-  autoComplete: {
-    all: 'View All',
-    searchTitle: 'Search by category',
-    database: 'Database',
-    collection: 'Collection',
-    partition: 'Partition',
-    index: 'Index',
-    query: 'Query',
-  },
-  tabs: {
-    collection: 'Collections',
-    partitions: 'Partitions',
-    schema: 'Schema',
-    index: 'Index',
-    data: 'Data',
-  },
-  schema: {
-    name: 'Field Name',
-    type: 'Type',
-    dimension: 'Dimension',
-    desc: 'Descriptions',
-  },
-  uploadSuccess: 'Upload successfully',
 };
+
+export default commonTrans;

+ 3 - 17
client/src/i18n/cn/warning.ts

@@ -1,24 +1,10 @@
-export default {
-  nameLength: '名称最长 128 字',
-  nameStrength: '名称可包含字母、数字、空格或 + - = . _ : / @ 等特殊字符',
-
-  email: '请输入正确的邮箱地址',
-
-  passwordLength: '请输入 8 字以上的密码',
-  passwordStrength: '密码请使用大写字母、小写字母、数字和特殊符号的组合',
-  passwordConfirm: '两次输入的密码不一致,请重试',
-
+const warningTrans = {
   closeDialog: '已填写的表单会丢失,确认离开?',
 
-  collectionName:
-    'Name should be combined with number, letter or underscore and not begin with number',
-  dimension: 'dimension should be 8 multiple',
-
   required: '{{name}} is required',
   positive: '{{name}} should be positive',
   integer: '{{name}} should be integers',
   range: 'range is {{min}} ~ {{max}}',
-
-  deletePartition: '_default partition cannot be deleted',
-  createIndex: 'You can only create one index',
 };
+
+export default warningTrans;

+ 3 - 16
client/src/i18n/en/button.ts

@@ -1,4 +1,4 @@
-export default {
+const btnTrans = {
   cancel: 'Cancel',
   save: 'Save',
   create: 'Create',
@@ -9,19 +9,6 @@ export default {
   connect: 'Connect',
   import: 'Import',
   delete: 'Delete',
-  export: 'Export',
-  verify: 'Verify email',
-  login: 'Back to sign in',
-  signIn: 'Sign in',
-  resendEmail: 'Resend email',
-  createAccount: 'Create account',
-  forgetPassword: 'Forget password?',
-  new: 'New',
-  signOut: 'Sign out',
-  filter: 'Filter',
-  print: 'Print',
-  downloadCSV: 'Download CSV File',
-  drop: 'Drop',
-  terminate: 'Terminate',
-  send: 'Send',
 };
+
+export default btnTrans;

+ 7 - 64
client/src/i18n/en/common.ts

@@ -1,4 +1,8 @@
-export default {
+const commonTrans = {
+  milvus: {
+    admin: 'Milvus Admin',
+    address: 'Milvus Address',
+  },
   status: {
     creating: 'creating',
     running: 'running',
@@ -12,67 +16,6 @@ export default {
     nextLabel: 'next page',
     prevLabel: 'prev page',
   },
-  nav: {
-    database: 'Database',
-    index: 'Index',
-    query: 'Query Service',
-    task: 'Task',
-
-    menu: {
-      database: 'Database',
-      collection: 'Collection',
-      partition: 'Partition',
-      index: 'Index',
-      query: 'Query',
-    },
-  },
-  copy: {
-    copy: 'Copy',
-    copied: 'Copied',
-  },
-  header: {
-    navigation: {
-      allDatabases: 'All Databases',
-      allTasks: 'Task',
-      allQueries: 'All Queries',
-    },
-  },
-  oauth: {
-    divider: 'OR',
-    signIn: {
-      google: 'Sign in with Google',
-      success: 'Sign in successfully',
-    },
-    signUp: {
-      google: 'Sign up with Google',
-    },
-  },
-  stepper: {
-    back: 'Back',
-    next: 'Next',
-  },
-  autoComplete: {
-    all: 'View All',
-    searchTitle: 'Search by category',
-    database: 'Database',
-    collection: 'Collection',
-    partition: 'Partition',
-    index: 'Index',
-    query: 'Query',
-  },
-  tabs: {
-    collection: 'Collections',
-    partitions: 'Partitions',
-    schema: 'Schema',
-    index: 'Index',
-    data: 'Data',
-  },
-  schema: {
-    name: 'Field Name',
-    type: 'Type',
-    dimension: 'Dimension',
-    desc: 'Descriptions',
-  },
-
-  uploadSuccess: 'Upload successfully',
 };
+
+export default commonTrans;

+ 3 - 18
client/src/i18n/en/warning.ts

@@ -1,25 +1,10 @@
-export default {
-  nameLength: 'Contain at most 128 characters',
-  nameStrength: 'Only letters, numbers, spaces and + - = . _ : / @ are allowed',
-
-  email: 'Email address is invalid.',
-
-  passwordLength: 'Contain at least 8 characters',
-  passwordStrength:
-    'Please choose a stronger password. Try a mix of uppercase and lowercase letters, numbers, and symbols.',
-  passwordConfirm: `Those passwords didn't match. Try again.`,
-
+const warningTrans = {
   closeDialog: 'Will lose your data, are you sure to leave?',
 
-  collectionName:
-    'Name should be combined with number, letter or underscore and not begin with number',
-  dimension: 'dimension should be 8 multiple',
-
   required: '{{name}} is required',
   positive: '{{name}} should be positive',
   integer: '{{name}} should be integers',
   range: 'range is {{min}} ~ {{max}}',
-
-  deletePartition: '_default partition cannot be deleted',
-  createIndex: 'You can only create one index',
 };
+
+export default warningTrans;

+ 1 - 0
client/src/index.tsx

@@ -1,6 +1,7 @@
 import { StrictMode } from 'react';
 import ReactDOM from 'react-dom';
 import './index.css';
+import './i18n';
 import App from './App';
 import reportWebVitals from './reportWebVitals';
 

+ 107 - 1
client/src/pages/connect/Connect.tsx

@@ -1,5 +1,111 @@
+import { Theme, makeStyles } from '@material-ui/core';
+import { useTranslation } from 'react-i18next';
+import { ITextfieldConfig } from '../../components/customInput/Types';
+import icons from '../../components/icons/Icons';
+import ConnectContainer from './ConnectContainer';
+import CustomInput from '../../components/customInput/CustomInput';
+import { useMemo, useState } from 'react';
+import { formatForm } from '../../utils/Form';
+import { useFormValidation } from '../../hooks/Form';
+import CustomButton from '../../components/customButton/CustomButton';
+import { useHistory } from 'react-router-dom';
+
+const useStyles = makeStyles((theme: Theme) => ({
+  wrapper: {
+    display: 'flex',
+    flexDirection: 'column',
+    alignItems: 'flex-end',
+
+    padding: theme.spacing(0, 3),
+  },
+  titleWrapper: {
+    display: 'flex',
+    padding: theme.spacing(3),
+    margin: '0 auto',
+
+    '& h2': {
+      margin: 0,
+      color: '#323232',
+    },
+  },
+  logo: {
+    width: '42px',
+    height: 'auto',
+    marginRight: theme.spacing(2),
+  },
+  input: {
+    margin: theme.spacing(3, 0, 0.5),
+  },
+}));
+
 const Connect = () => {
-  return <section>connect</section>;
+  const history = useHistory();
+
+  const classes = useStyles();
+  const { t } = useTranslation();
+  const { t: warningTrans } = useTranslation('warning');
+  const milvusTrans: { [key in string]: string } = t('milvus');
+  const { t: btnTrans } = useTranslation('btn');
+
+  const [form, setForm] = useState({
+    address: '',
+  });
+  const checkedForm = useMemo(() => {
+    const { address } = form;
+    return formatForm({ address });
+  }, [form]);
+  const { validation, checkIsValid, disabled } = useFormValidation(checkedForm);
+
+  const Logo = icons.milvus;
+
+  const handleInputChange = (value: string) => {
+    setForm({ address: value });
+  };
+
+  const handleConnect = () => {
+    console.log('connect address', form.address);
+    history.push('/');
+  };
+
+  const addressInputConfig: ITextfieldConfig = {
+    label: milvusTrans.address,
+    key: 'address',
+    onChange: handleInputChange,
+    variant: 'filled',
+    className: classes.input,
+    placeholder: milvusTrans.addaress,
+    fullWidth: true,
+    validations: [
+      {
+        rule: 'require',
+        errorText: warningTrans('required', { name: milvusTrans.address }),
+      },
+    ],
+  };
+
+  return (
+    <ConnectContainer>
+      <section className={classes.wrapper}>
+        <div className={classes.titleWrapper}>
+          <Logo classes={{ root: classes.logo }} />
+          <h2>{milvusTrans.admin}</h2>
+        </div>
+        <CustomInput
+          type="text"
+          textConfig={addressInputConfig}
+          checkValid={checkIsValid}
+          validInfo={validation}
+        />
+        <CustomButton
+          variant="contained"
+          disabled={disabled}
+          onClick={handleConnect}
+        >
+          {btnTrans('connect')}
+        </CustomButton>
+      </section>
+    </ConnectContainer>
+  );
 };
 
 export default Connect;

+ 7 - 7
client/src/components/layout/UserContainer.tsx → client/src/pages/connect/ConnectContainer.tsx

@@ -1,6 +1,6 @@
 import { makeStyles, Theme } from '@material-ui/core';
 import { ReactElement } from 'react';
-import backgroundPath from '../../assets/imgs/background.png';
+import backgroundPath from '../../assets/imgs/connectContainer/background.png';
 
 const getContainerStyles = makeStyles((theme: Theme) => ({
   wrapper: {
@@ -23,15 +23,15 @@ const getContainerStyles = makeStyles((theme: Theme) => ({
   },
 }));
 
-// used for user login process
-const UserContainer = ({ children }: { children: ReactElement }) => {
+// used for user connect process
+const ConnectContainer = ({ children }: { children: ReactElement }) => {
   const classes = getContainerStyles();
 
   return (
-    <section className={classes.wrapper}>
-      <div className={classes.card}>{children}</div>
-    </section>
+    <main className={classes.wrapper}>
+      <section className={classes.card}>{children}</section>
+    </main>
   );
 };
 
-export default UserContainer;
+export default ConnectContainer;

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

@@ -1,3 +1,4 @@
+import Connect from '../pages/connect/Connect';
 import Overview from '../pages/overview/Overview';
 
 const RouterConfig = [
@@ -6,6 +7,11 @@ const RouterConfig = [
     component: Overview,
     auth: true,
   },
+  {
+    path: '/connect',
+    component: Connect,
+    auth: false,
+  },
 ];
 
 export default RouterConfig;

+ 1 - 1
client/src/router/Router.tsx

@@ -20,7 +20,7 @@ const RouterWrapper = () => {
               return (
                 <Redirect
                   to={{
-                    pathname: '/',
+                    pathname: '/connect',
                   }}
                 />
               );

+ 17 - 0
client/src/utils/Form.ts

@@ -0,0 +1,17 @@
+import { IForm } from "../hooks/Form";
+
+interface IInfo {
+  [key: string]: any
+}
+
+export const formatForm = (info: IInfo): IForm[] => {
+  const form: IForm[] = Object.entries(info).map(item => {
+    const [key, value] = item;
+    return {
+      key,
+      value,
+      needCheck: true,
+    };
+  });
+  return form;
+}

+ 17 - 0
client/src/utils/__test__/Form.spec.ts

@@ -0,0 +1,17 @@
+import { formatForm } from '../Form';
+
+describe('Test form utils', () => {
+  test('test formatForm function', () => {
+    const mockUserInfo = {
+      name: 'user1',
+      age: 20,
+      isMale: true,
+    };
+
+    const form = formatForm(mockUserInfo);
+    const nameInfo = form.find(item => item.key === 'name');
+    expect(form.length).toBe(3);
+    expect(nameInfo?.value).toBe('user1');
+    expect(nameInfo?.needCheck).toBeTruthy();
+  });
+});