Browse Source

add create partition

tumao 4 years ago
parent
commit
d0cbb5c6db

+ 14 - 2
client/src/components/customDialog/DialogTemplate.tsx

@@ -1,10 +1,21 @@
 import { FC } from 'react';
 import { useTranslation } from 'react-i18next';
-import { DialogContent, DialogActions } from '@material-ui/core';
+import {
+  DialogContent,
+  DialogActions,
+  makeStyles,
+  Theme,
+} from '@material-ui/core';
 import { DialogContainerProps } from './Types';
 import CustomDialogTitle from './CustomDialogTitle';
 import CustomButton from '../customButton/CustomButton';
 
+const useStyles = makeStyles((theme: Theme) => ({
+  actions: {
+    paddingTop: theme.spacing(2),
+  },
+}));
+
 const DialogTemplate: FC<DialogContainerProps> = ({
   title,
   cancelLabel,
@@ -17,12 +28,13 @@ const DialogTemplate: FC<DialogContainerProps> = ({
   const { t } = useTranslation('btn');
   const cancel = cancelLabel || t('cancel');
   const confirm = confirmLabel || t('confirm');
+  const classes = useStyles();
 
   return (
     <>
       <CustomDialogTitle onClose={handleCancel}>{title}</CustomDialogTitle>
       <DialogContent>{children}</DialogContent>
-      <DialogActions>
+      <DialogActions className={classes.actions}>
         <CustomButton onClick={handleCancel} color="default">
           {cancel}
         </CustomButton>

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

@@ -55,13 +55,6 @@ const Layout = (props: any) => {
       label: t('collection'),
       onClick: () => history.push('/collections'),
     },
-    // {
-    //   icon: icons.navConsole,
-    //   label: t('console'),
-    //   onClick: () => history.push('/console'),
-    //   iconActiveClass: classes.activeConsole,
-    //   iconNormalClass: classes.normalConsole,
-    // },
   ];
 
   return (

+ 17 - 2
client/src/http/Partition.ts

@@ -1,5 +1,5 @@
 import { StatusEnum } from '../components/status/Types';
-import { PartitionView } from '../pages/partitions/Types';
+import { PartitionManageParam, PartitionView } from '../pages/partitions/Types';
 import { formatNumber } from '../utils/Common';
 import BaseModel from './BaseModel';
 
@@ -14,12 +14,27 @@ export class PartitionHttp extends BaseModel implements PartitionView {
     Object.assign(this, props);
   }
 
+  static URL_BASE = `/partitions`;
+
   static getPartitions(collectionName: string): Promise<PartitionHttp[]> {
-    const path = '/partitions';
+    const path = this.URL_BASE;
 
     return super.findAll({ path, params: { collection_name: collectionName } });
   }
 
+  static createPartition(createParam: PartitionManageParam) {
+    const { collectionName, partitionName, type } = createParam;
+    const path = this.URL_BASE;
+    return super.create({
+      path,
+      data: {
+        collection_name: collectionName,
+        partition_name: partitionName,
+        type,
+      },
+    });
+  }
+
   get _id() {
     return this.id;
   }

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

@@ -7,6 +7,9 @@ const partitionTrans = {
   status: 'Status',
   rowCount: 'Row Count',
   tooltip: 'data in one row',
+
+  createTitle: 'Create Partition',
+  nameWarning: '_default is reserved, cannot be used as name',
 };
 
 export default partitionTrans;

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

@@ -7,6 +7,9 @@ const partitionTrans = {
   status: 'Status',
   rowCount: 'Row Count',
   tooltip: 'data in one row',
+
+  createTitle: 'Create Partition',
+  nameWarning: '_default is reserved, cannot be used as name',
 };
 
 export default partitionTrans;

+ 2 - 26
client/src/pages/collections/Collection.tsx

@@ -5,11 +5,8 @@ import CustomTabList from '../../components/customTabList/CustomTabList';
 import { ITab } from '../../components/customTabList/Types';
 import Partitions from '../partitions/partitions';
 import { useHistory, useLocation, useParams } from 'react-router-dom';
-import { useEffect, useMemo, useState } from 'react';
-import { PartitionView } from '../partitions/Types';
+import { useMemo } from 'react';
 import { parseLocationSearch } from '../../utils/Format';
-import Status from '../../components/status/Status';
-import { PartitionHttp } from '../../http/Partition';
 
 enum TAB_EMUM {
   'partition',
@@ -23,8 +20,6 @@ const Collection = () => {
     }>();
 
   useNavigationHook(ALL_ROUTER_TYPES.COLLECTION_DETAIL, { collectionName });
-  const [partitions, setPartitions] = useState<PartitionView[]>([]);
-  const [loading, setLoading] = useState<boolean>(true);
 
   const history = useHistory();
   const location = useLocation();
@@ -41,31 +36,12 @@ const Collection = () => {
   const handleTabChange = (activeIndex: number) => {
     const path = location.pathname;
     history.push(`${path}?activeIndex=${activeIndex}`);
-
-    // fetch data
-    if (activeIndex === TAB_EMUM.partition) {
-      fetchPartitions(collectionName);
-    }
   };
 
-  const fetchPartitions = async (collectionName: string) => {
-    const res = await PartitionHttp.getPartitions(collectionName);
-
-    const partitons: PartitionView[] = res.map(p =>
-      Object.assign(p, { _statusElement: <Status status={p._status} /> })
-    );
-    setLoading(false);
-    setPartitions(partitons);
-  };
-
-  useEffect(() => {
-    fetchPartitions(collectionName);
-  }, [collectionName]);
-
   const tabs: ITab[] = [
     {
       label: t('partitionTab'),
-      component: <Partitions data={partitions} loading={loading} />,
+      component: <Partitions collectionName={collectionName} />,
     },
     {
       label: t('structureTab'),

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

@@ -83,7 +83,7 @@ const Connect = () => {
     onChange: handleInputChange,
     variant: 'filled',
     className: classes.input,
-    placeholder: milvusTrans.addaress,
+    placeholder: milvusTrans.address,
     fullWidth: true,
     validations: [
       {

+ 0 - 9
client/src/pages/console/Console.tsx

@@ -1,9 +0,0 @@
-import { useNavigationHook } from '../../hooks/Navigation';
-import { ALL_ROUTER_TYPES } from '../../router/Types';
-
-const Console = () => {
-  useNavigationHook(ALL_ROUTER_TYPES.CONSOLE);
-  return <section>console</section>;
-};
-
-export default Console;

+ 83 - 0
client/src/pages/partitions/Create.tsx

@@ -0,0 +1,83 @@
+import { makeStyles, Theme } from '@material-ui/core';
+import { FC, 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 { useFormValidation } from '../../hooks/Form';
+import { formatForm } from '../../utils/Form';
+import { PartitionCreateProps } from './Types';
+
+const useStyles = makeStyles((theme: Theme) => ({
+  input: {
+    margin: theme.spacing(3, 0, 0.5),
+  },
+}));
+
+const CreatePartition: FC<PartitionCreateProps> = ({
+  handleCreate,
+  handleClose,
+}) => {
+  const { t } = useTranslation('partition');
+  const { t: btnTrans } = useTranslation('btn');
+  const { t: warningTrans } = useTranslation('warning');
+
+  const [form, setForm] = useState<{ name: string }>({
+    name: '',
+  });
+  const checkedForm = useMemo(() => {
+    const { name } = form;
+    return formatForm({ name });
+  }, [form]);
+  const { validation, checkIsValid, disabled } = useFormValidation(checkedForm);
+
+  const classes = useStyles();
+
+  const handleInputChange = (value: string) => {
+    setForm({ name: value });
+  };
+
+  const nameInputConfig: ITextfieldConfig = {
+    label: t('name'),
+    variant: 'filled',
+    key: 'name',
+    fullWidth: true,
+    onChange: handleInputChange,
+    className: classes.input,
+    validations: [
+      {
+        rule: 'require',
+        errorText: warningTrans('required', { name: t('name') }),
+      },
+      {
+        rule: 'partitionName',
+        errorText: t('nameWarning'),
+      },
+    ],
+  };
+
+  const handleCreatePartition = () => {
+    handleCreate(form.name);
+  };
+
+  return (
+    <DialogTemplate
+      title={t('createTitle')}
+      handleCancel={handleClose}
+      confirmLabel={btnTrans('create')}
+      handleConfirm={handleCreatePartition}
+      confirmDisabled={disabled}
+    >
+      <form>
+        <CustomInput
+          type="text"
+          textConfig={nameInputConfig}
+          checkValid={checkIsValid}
+          validInfo={validation}
+        />
+      </form>
+    </DialogTemplate>
+  );
+};
+
+export default CreatePartition;

+ 12 - 0
client/src/pages/partitions/Types.ts

@@ -1,5 +1,6 @@
 import { ReactElement } from 'react';
 import { StatusEnum } from '../../components/status/Types';
+import { ManageRequestMethods } from '../../types/Common';
 
 export interface PartitionView {
   _id: string;
@@ -8,3 +9,14 @@ export interface PartitionView {
   _statusElement?: ReactElement;
   _rowCount: string;
 }
+
+export interface PartitionManageParam {
+  collectionName: string;
+  partitionName: string;
+  type: ManageRequestMethods;
+}
+
+export interface PartitionCreateProps {
+  handleCreate: (name: string) => void;
+  handleClose: () => void;
+}

+ 58 - 9
client/src/pages/partitions/partitions.tsx

@@ -1,25 +1,30 @@
 import { makeStyles, Theme } from '@material-ui/core';
-import { FC, useState } from 'react';
-import { PartitionView } from './Types';
+import { FC, useContext, useEffect, useState } from 'react';
+import { PartitionManageParam, PartitionView } from './Types';
 import MilvusGrid from '../../components/grid';
 import { ColDefinitionsType, ToolBarConfig } from '../../components/grid/Types';
 import { useTranslation } from 'react-i18next';
 import { usePaginationHook } from '../../hooks/Pagination';
 import icons from '../../components/icons/Icons';
 import CustomToolTip from '../../components/customToolTip/CustomToolTip';
+import { rootContext } from '../../context/Root';
+import CreatePartition from './Create';
+import { PartitionHttp } from '../../http/Partition';
+import Status from '../../components/status/Status';
 
 const useStyles = makeStyles((theme: Theme) => ({
-  wrapper: {},
+  wrapper: {
+    height: '100%',
+  },
   icon: {
     fontSize: '20px',
     marginLeft: theme.spacing(0.5),
   },
 }));
 
-const Partitions: FC<{ data: PartitionView[]; loading: boolean }> = ({
-  data,
-  loading,
-}) => {
+const Partitions: FC<{
+  collectionName: string;
+}> = ({ collectionName }) => {
   const classes = useStyles();
   const { t } = useTranslation('partition');
   const InfoIcon = icons.info;
@@ -36,11 +41,42 @@ const Partitions: FC<{ data: PartitionView[]; loading: boolean }> = ({
   const [selectedPartitions, setSelectedPartitions] = useState<PartitionView[]>(
     []
   );
+  const [partitions, setPartitions] = useState<PartitionView[]>([]);
+  const [loading, setLoading] = useState<boolean>(true);
+  const { setDialog, handleCloseDialog, openSnackBar } =
+    useContext(rootContext);
+
+  useEffect(() => {
+    fetchPartitions(collectionName);
+  }, [collectionName]);
+
+  const fetchPartitions = async (collectionName: string) => {
+    const res = await PartitionHttp.getPartitions(collectionName);
+
+    const partitons: PartitionView[] = res.map(p =>
+      Object.assign(p, { _statusElement: <Status status={p._status} /> })
+    );
+    setLoading(false);
+    setPartitions(partitons);
+  };
 
   const toolbarConfigs: ToolBarConfig[] = [
     {
       label: t('create'),
-      onClick: () => {},
+      onClick: () => {
+        setDialog({
+          open: true,
+          type: 'custom',
+          params: {
+            component: (
+              <CreatePartition
+                handleCreate={handleCreatePartition}
+                handleClose={handleCloseDialog}
+              />
+            ),
+          },
+        });
+      },
       icon: 'add',
     },
     {
@@ -94,12 +130,25 @@ const Partitions: FC<{ data: PartitionView[]; loading: boolean }> = ({
     setSelectedPartitions([]);
   };
 
+  const handleCreatePartition = async (name: string) => {
+    const param: PartitionManageParam = {
+      partitionName: name,
+      collectionName,
+      type: 'create',
+    };
+
+    await PartitionHttp.createPartition(param);
+
+    openSnackBar('create partition success');
+    handleCloseDialog();
+  };
+
   return (
     <section className={classes.wrapper}>
       <MilvusGrid
         toolbarConfigs={toolbarConfigs}
         colDefinitions={colDefinitions}
-        rows={data}
+        rows={partitions}
         rowCount={total}
         primaryKey="id"
         openCheckBox={true}

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

@@ -1,7 +1,6 @@
 import Collection from '../pages/collections/Collection';
 import Collections from '../pages/collections/Collections';
 import Connect from '../pages/connect/Connect';
-// import Console from '../pages/console/Console';
 import Overview from '../pages/overview/Overview';
 import { RouterConfigType } from './Types';
 
@@ -26,11 +25,6 @@ const RouterConfig: RouterConfigType[] = [
     component: Collection,
     auth: true,
   },
-  // {
-  //   path: '/console',
-  //   component: Console,
-  //   auth: true,
-  // },
 ];
 
 export default RouterConfig;

+ 2 - 0
client/src/types/Common.ts

@@ -20,3 +20,5 @@ export interface IPaginationRes {
   offset: number;
   total_count: number;
 }
+
+export type ManageRequestMethods = 'delete' | 'create';

+ 11 - 3
client/src/utils/Validation.ts

@@ -12,7 +12,8 @@ export type ValidType =
   | 'positiveNumber'
   | 'collectionName'
   | 'dimension'
-  | 'multiple';
+  | 'multiple'
+  | 'partitionName';
 export interface ICheckMapParam {
   value: string;
   extraParam?: IExtraParam;
@@ -84,12 +85,14 @@ export const checkClusterName = (value: string): boolean => {
 };
 
 export const checkIP = (value: string): boolean => {
-  const re = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
+  const re =
+    /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
   return re.test(value);
 };
 
 export const checkCIDR = (value: string): boolean => {
-  const re = /^(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\/([0-9]|[1-2]\d|3[0-2])$/;
+  const re =
+    /^(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\/([0-9]|[1-2]\d|3[0-2])$/;
   return re.test(value);
 };
 
@@ -116,6 +119,10 @@ export const checkCollectionName = (value: string): boolean => {
   return re.test(value);
 };
 
+export const checkPartitionName = (value: string): boolean => {
+  return value !== '_default';
+};
+
 export const checkMultiple = (param: {
   value: string;
   multipleNumber?: number;
@@ -168,6 +175,7 @@ export const getCheckResult = (param: ICheckMapParam): boolean => {
       value,
       multipleNumber: extraParam?.multipleNumber,
     }),
+    partitionName: checkPartitionName(value),
   };
 
   return checkMap[rule];