Browse Source

Merge pull request #224 from zilliztech/database

support manage databases
ryjiang 2 years ago
parent
commit
0309e335cf

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

@@ -120,6 +120,69 @@ const icons: { [x in IconsType]: (props?: any) => React.ReactElement } = {
   vectorSearch: (props = {}) => (
     <SvgIcon viewBox="0 0 48 48" component={SearchEmptyIcon} {...props} />
   ),
+  database: (props = {}) => (
+    <SvgIcon viewBox="-6 0 64 60" {...props} strokeWidth="3">
+      <path
+        d="M52.354,8.51C51.196,4.22,42.577,0,27.5,0C12.423,0,3.803,4.22,2.646,8.51C2.562,8.657,2.5,8.818,2.5,9v0.5V21v0.5V22v11
+	v0.5V34v12c0,0.162,0.043,0.315,0.117,0.451C3.798,51.346,14.364,55,27.5,55c13.106,0,23.655-3.639,24.875-8.516
+	C52.455,46.341,52.5,46.176,52.5,46V34v-0.5V33V22v-0.5V21V9.5V9C52.5,8.818,52.438,8.657,52.354,8.51z M50.421,33.985
+	c-0.028,0.121-0.067,0.241-0.116,0.363c-0.04,0.099-0.089,0.198-0.143,0.297c-0.067,0.123-0.142,0.246-0.231,0.369
+	c-0.066,0.093-0.141,0.185-0.219,0.277c-0.111,0.131-0.229,0.262-0.363,0.392c-0.081,0.079-0.17,0.157-0.26,0.236
+	c-0.164,0.143-0.335,0.285-0.526,0.426c-0.082,0.061-0.17,0.12-0.257,0.18c-0.226,0.156-0.462,0.311-0.721,0.463
+	c-0.068,0.041-0.141,0.08-0.212,0.12c-0.298,0.168-0.609,0.335-0.945,0.497c-0.043,0.021-0.088,0.041-0.132,0.061
+	c-0.375,0.177-0.767,0.351-1.186,0.519c-0.012,0.005-0.024,0.009-0.036,0.014c-2.271,0.907-5.176,1.67-8.561,2.17
+	c-0.017,0.002-0.034,0.004-0.051,0.007c-0.658,0.097-1.333,0.183-2.026,0.259c-0.113,0.012-0.232,0.02-0.346,0.032
+	c-0.605,0.063-1.217,0.121-1.847,0.167c-0.288,0.021-0.59,0.031-0.883,0.049c-0.474,0.028-0.943,0.059-1.429,0.076
+	C29.137,40.984,28.327,41,27.5,41s-1.637-0.016-2.432-0.044c-0.486-0.017-0.955-0.049-1.429-0.076
+	c-0.293-0.017-0.595-0.028-0.883-0.049c-0.63-0.046-1.242-0.104-1.847-0.167c-0.114-0.012-0.233-0.02-0.346-0.032
+	c-0.693-0.076-1.368-0.163-2.026-0.259c-0.017-0.002-0.034-0.004-0.051-0.007c-3.385-0.5-6.29-1.263-8.561-2.17
+	c-0.012-0.004-0.024-0.009-0.036-0.014c-0.419-0.168-0.812-0.342-1.186-0.519c-0.043-0.021-0.089-0.041-0.132-0.061
+	c-0.336-0.162-0.647-0.328-0.945-0.497c-0.07-0.04-0.144-0.079-0.212-0.12c-0.259-0.152-0.495-0.307-0.721-0.463
+	c-0.086-0.06-0.175-0.119-0.257-0.18c-0.191-0.141-0.362-0.283-0.526-0.426c-0.089-0.078-0.179-0.156-0.26-0.236
+	c-0.134-0.13-0.252-0.26-0.363-0.392c-0.078-0.092-0.153-0.184-0.219-0.277c-0.088-0.123-0.163-0.246-0.231-0.369
+	c-0.054-0.099-0.102-0.198-0.143-0.297c-0.049-0.121-0.088-0.242-0.116-0.363C4.541,33.823,4.5,33.661,4.5,33.5
+	c0-0.113,0.013-0.226,0.031-0.338c0.025-0.151,0.011-0.302-0.031-0.445v-7.424c0.028,0.026,0.063,0.051,0.092,0.077
+	c0.218,0.192,0.44,0.383,0.69,0.567C9.049,28.786,16.582,31,27.5,31c10.872,0,18.386-2.196,22.169-5.028
+	c0.302-0.22,0.574-0.447,0.83-0.678l0.001-0.001v7.424c-0.042,0.143-0.056,0.294-0.031,0.445c0.019,0.112,0.031,0.225,0.031,0.338
+	C50.5,33.661,50.459,33.823,50.421,33.985z M50.5,13.293v7.424c-0.042,0.143-0.056,0.294-0.031,0.445
+	c0.019,0.112,0.031,0.225,0.031,0.338c0,0.161-0.041,0.323-0.079,0.485c-0.028,0.121-0.067,0.241-0.116,0.363
+	c-0.04,0.099-0.089,0.198-0.143,0.297c-0.067,0.123-0.142,0.246-0.231,0.369c-0.066,0.093-0.141,0.185-0.219,0.277
+	c-0.111,0.131-0.229,0.262-0.363,0.392c-0.081,0.079-0.17,0.157-0.26,0.236c-0.164,0.143-0.335,0.285-0.526,0.426
+	c-0.082,0.061-0.17,0.12-0.257,0.18c-0.226,0.156-0.462,0.311-0.721,0.463c-0.068,0.041-0.141,0.08-0.212,0.12
+	c-0.298,0.168-0.609,0.335-0.945,0.497c-0.043,0.021-0.088,0.041-0.132,0.061c-0.375,0.177-0.767,0.351-1.186,0.519
+	c-0.012,0.005-0.024,0.009-0.036,0.014c-2.271,0.907-5.176,1.67-8.561,2.17c-0.017,0.002-0.034,0.004-0.051,0.007
+	c-0.658,0.097-1.333,0.183-2.026,0.259c-0.113,0.012-0.232,0.02-0.346,0.032c-0.605,0.063-1.217,0.121-1.847,0.167
+	c-0.288,0.021-0.59,0.031-0.883,0.049c-0.474,0.028-0.943,0.059-1.429,0.076C29.137,28.984,28.327,29,27.5,29
+	s-1.637-0.016-2.432-0.044c-0.486-0.017-0.955-0.049-1.429-0.076c-0.293-0.017-0.595-0.028-0.883-0.049
+	c-0.63-0.046-1.242-0.104-1.847-0.167c-0.114-0.012-0.233-0.02-0.346-0.032c-0.693-0.076-1.368-0.163-2.026-0.259
+	c-0.017-0.002-0.034-0.004-0.051-0.007c-3.385-0.5-6.29-1.263-8.561-2.17c-0.012-0.004-0.024-0.009-0.036-0.014
+	c-0.419-0.168-0.812-0.342-1.186-0.519c-0.043-0.021-0.089-0.041-0.132-0.061c-0.336-0.162-0.647-0.328-0.945-0.497
+	c-0.07-0.04-0.144-0.079-0.212-0.12c-0.259-0.152-0.495-0.307-0.721-0.463c-0.086-0.06-0.175-0.119-0.257-0.18
+	c-0.191-0.141-0.362-0.283-0.526-0.426c-0.089-0.078-0.179-0.156-0.26-0.236c-0.134-0.13-0.252-0.26-0.363-0.392
+	c-0.078-0.092-0.153-0.184-0.219-0.277c-0.088-0.123-0.163-0.246-0.231-0.369c-0.054-0.099-0.102-0.198-0.143-0.297
+	c-0.049-0.121-0.088-0.242-0.116-0.363C4.541,21.823,4.5,21.661,4.5,21.5c0-0.113,0.013-0.226,0.031-0.338
+	c0.025-0.151,0.011-0.302-0.031-0.445v-7.424c0.12,0.109,0.257,0.216,0.387,0.324c0.072,0.06,0.139,0.12,0.215,0.18
+	c0.3,0.236,0.624,0.469,0.975,0.696c0.073,0.047,0.155,0.093,0.231,0.14c0.294,0.183,0.605,0.362,0.932,0.538
+	c0.121,0.065,0.242,0.129,0.367,0.193c0.365,0.186,0.748,0.367,1.151,0.542c0.066,0.029,0.126,0.059,0.193,0.087
+	c0.469,0.199,0.967,0.389,1.485,0.573c0.143,0.051,0.293,0.099,0.44,0.149c0.412,0.139,0.838,0.272,1.279,0.401
+	c0.159,0.046,0.315,0.094,0.478,0.138c0.585,0.162,1.189,0.316,1.823,0.458c0.087,0.02,0.181,0.036,0.269,0.055
+	c0.559,0.122,1.139,0.235,1.735,0.341c0.202,0.036,0.407,0.07,0.613,0.104c0.567,0.093,1.151,0.178,1.75,0.256
+	c0.154,0.02,0.301,0.043,0.457,0.062c0.744,0.09,1.514,0.167,2.305,0.233c0.195,0.016,0.398,0.028,0.596,0.042
+	c0.633,0.046,1.28,0.084,1.942,0.114c0.241,0.011,0.481,0.022,0.727,0.031C25.712,18.979,26.59,19,27.5,19s1.788-0.021,2.65-0.05
+	c0.245-0.009,0.485-0.02,0.727-0.031c0.662-0.03,1.309-0.068,1.942-0.114c0.198-0.015,0.4-0.026,0.596-0.042
+	c0.791-0.065,1.561-0.143,2.305-0.233c0.156-0.019,0.303-0.042,0.457-0.062c0.599-0.078,1.182-0.163,1.75-0.256
+	c0.206-0.034,0.411-0.068,0.613-0.104c0.596-0.106,1.176-0.219,1.735-0.341c0.088-0.019,0.182-0.036,0.269-0.055
+	c0.634-0.142,1.238-0.297,1.823-0.458c0.163-0.045,0.319-0.092,0.478-0.138c0.441-0.129,0.867-0.262,1.279-0.401
+	c0.147-0.05,0.297-0.098,0.44-0.149c0.518-0.184,1.017-0.374,1.485-0.573c0.067-0.028,0.127-0.058,0.193-0.087
+	c0.403-0.176,0.786-0.356,1.151-0.542c0.125-0.064,0.247-0.128,0.367-0.193c0.327-0.175,0.638-0.354,0.932-0.538
+	c0.076-0.047,0.158-0.093,0.231-0.14c0.351-0.227,0.675-0.459,0.975-0.696c0.075-0.06,0.142-0.12,0.215-0.18
+	C50.243,13.509,50.38,13.402,50.5,13.293z M27.5,2c13.555,0,23,3.952,23,7.5s-9.445,7.5-23,7.5s-23-3.952-23-7.5S13.945,2,27.5,2z
+	 M50.5,45.703c-0.014,0.044-0.024,0.089-0.032,0.135C49.901,49.297,40.536,53,27.5,53S5.099,49.297,4.532,45.838
+	c-0.008-0.045-0.019-0.089-0.032-0.131v-8.414c0.028,0.026,0.063,0.051,0.092,0.077c0.218,0.192,0.44,0.383,0.69,0.567
+	C9.049,40.786,16.582,43,27.5,43c10.872,0,18.386-2.196,22.169-5.028c0.302-0.22,0.574-0.447,0.83-0.678l0.001-0.001V45.703z"
+      />
+    </SvgIcon>
+  ),
   copyExpression: (props = {}) => (
     <SvgIcon viewBox="0 0 16 16" component={CopyIcon} {...props} />
   ),

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

@@ -40,4 +40,5 @@ export type IconsType =
   | 'download'
   | 'source'
   | 'edit'
+  | 'database'
   | 'uploadFile';

+ 8 - 0
client/src/hooks/Navigation.ts

@@ -26,6 +26,14 @@ export const useNavigationHook = (
         setNavInfo(navInfo);
         break;
       }
+      case ALL_ROUTER_TYPES.DATABASES: {
+        const navInfo: NavInfo = {
+          navTitle: navTrans('database'),
+          backPath: '',
+        };
+        setNavInfo(navInfo);
+        break;
+      }
       case ALL_ROUTER_TYPES.COLLECTIONS: {
         const navInfo: NavInfo = {
           navTitle: navTrans('collection'),

+ 32 - 0
client/src/http/Database.ts

@@ -0,0 +1,32 @@
+import {
+  CreateDatabaseParams,
+  DropDatabaseParams,
+} from '../pages/database/Types';
+import BaseModel from './BaseModel';
+
+export class DatabaseHttp extends BaseModel {
+  private names!: string[];
+
+  constructor(props: {}) {
+    super(props);
+    Object.assign(this, props);
+  }
+
+  static DATABASE_URL = `/databases`;
+
+  static getDatabases() {
+    return super.search({ path: this.DATABASE_URL, params: {} });
+  }
+
+  static createDatabase(data: CreateDatabaseParams) {
+    return super.create({ path: this.DATABASE_URL, data });
+  }
+
+  static dropDatabase(data: DropDatabaseParams) {
+    return super.delete({ path: `${this.DATABASE_URL}/${data.db_name}` });
+  }
+
+  get _names() {
+    return this.names;
+  }
+}

+ 12 - 0
client/src/i18n/cn/database.ts

@@ -0,0 +1,12 @@
+const databaseTrans = {
+  createTitle: 'Create database',
+  databases: 'Databases',
+  deleteWarning:
+    'You are trying to drop a database. This action cannot be undone.',
+  databaseName: 'Database Name',
+  confirmDatabase: 'Confirm Password',
+  deleteTip:
+    'Please select at least one item to drop and default_db can not be dropped.',
+};
+
+export default databaseTrans;

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

@@ -4,7 +4,7 @@ const collectionTrans = {
 
   rowCount: 'Approx Entity Count',
 
-  create: 'Create Collection',
+  create: 'Collection',
   delete: 'delete',
   deleteTooltip: 'Please select at least one item to delete.',
   rename: 'rename',

+ 13 - 0
client/src/i18n/en/database.ts

@@ -0,0 +1,13 @@
+const databaseTrans = {
+  createTitle: 'Create Database',
+  databases: 'Databases',
+  database: 'Database',
+  deleteWarning:
+    'You are trying to drop a database. This action cannot be undone.',
+  databaseName: 'Database Name',
+  confirmDatabase: 'Confirm Password',
+  deleteTip:
+    'Please select at least one item to drop and default_db can not be dropped.',
+};
+
+export default databaseTrans;

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

@@ -2,7 +2,7 @@ const indexTrans = {
   type: 'Index Type',
   param: 'Index Parameters',
 
-  create: 'Create Index',
+  create: 'Index',
   index: 'Index',
   desc: 'Description',
 

+ 3 - 2
client/src/i18n/en/nav.ts

@@ -1,10 +1,11 @@
 const navTrans = {
   overview: 'Overview',
-  collection: 'Collection',
+  collection: 'Collections',
   console: 'Search Console',
   search: 'Vector Search',
   system: 'System View',
-  user: 'Milvus Users',
+  user: 'Users',
+  database: 'Databases',
 };
 
 export default navTrans;

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

@@ -1,5 +1,5 @@
 const partitionTrans = {
-  create: 'Create Partition',
+  create: 'Partition',
   delete: 'Delete partition',
 
   partition: 'Partition',

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

@@ -1,7 +1,7 @@
 const userTrans = {
-  createTitle: 'Create Milvus User',
+  createTitle: 'Create User',
   updateTitle: 'Update Milvus User',
-  user: 'Milvus User',
+  user: 'User',
   deleteWarning: 'You are trying to drop user. This action cannot be undone.',
   oldPassword: 'Current Password',
   newPassword: 'New Password',

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

@@ -29,6 +29,8 @@ import systemViewTransEn from './en/systemView';
 import systemViewTransCn from './cn/systemView';
 import userTransEn from './en/user';
 import userTransCn from './cn/user';
+import databaseTransEn from './en/database';
+import databaseTransCn from './cn/database';
 import prometheusTransEn from './en/prometheus';
 import prometheusTransCn from './cn/prometheus';
 
@@ -48,6 +50,7 @@ export const resources = {
     search: searchCn,
     systemView: systemViewTransCn,
     user: userTransCn,
+    database: databaseTransCn,
     prometheus: prometheusTransCn,
   },
   en: {
@@ -65,6 +68,7 @@ export const resources = {
     search: searchEn,
     systemView: systemViewTransEn,
     user: userTransEn,
+    database: databaseTransEn,
     prometheus: prometheusTransEn,
   },
 };

+ 84 - 0
client/src/pages/database/Create.tsx

@@ -0,0 +1,84 @@
+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 { CreateDatabaseProps, CreateDatabaseParams } from './Types';
+
+const useStyles = makeStyles((theme: Theme) => ({
+  input: {
+    margin: theme.spacing(3, 0, 0.5),
+  },
+}));
+
+const CreateUser: FC<CreateDatabaseProps> = ({ handleCreate, handleClose }) => {
+  const { t: databaseTrans } = useTranslation('database');
+  const { t: btnTrans } = useTranslation('btn');
+  const { t: warningTrans } = useTranslation('warning');
+
+  const [form, setForm] = useState<CreateDatabaseParams>({
+    db_name: '',
+  });
+  const checkedForm = useMemo(() => {
+    return formatForm(form);
+  }, [form]);
+  const { validation, checkIsValid, disabled } = useFormValidation(checkedForm);
+
+  const classes = useStyles();
+
+  const handleInputChange = (key: 'db_name', value: string) => {
+    setForm(v => ({ ...v, [key]: value }));
+  };
+
+  const createConfigs: ITextfieldConfig[] = [
+    {
+      label: databaseTrans('database'),
+      key: 'db_name',
+      onChange: (value: string) => handleInputChange('db_name', value),
+      variant: 'filled',
+      className: classes.input,
+      placeholder: databaseTrans('database'),
+      fullWidth: true,
+      validations: [
+        {
+          rule: 'require',
+          errorText: warningTrans('required', {
+            name: databaseTrans('databaseName'),
+          }),
+        },
+      ],
+      defaultValue: form.db_name,
+    },
+  ];
+
+  const handleCreateDatabase = () => {
+    handleCreate(form);
+  };
+
+  return (
+    <DialogTemplate
+      title={databaseTrans('createTitle')}
+      handleClose={handleClose}
+      confirmLabel={btnTrans('create')}
+      handleConfirm={handleCreateDatabase}
+      confirmDisabled={disabled}
+    >
+      <>
+        {createConfigs.map(v => (
+          <CustomInput
+            type="text"
+            textConfig={v}
+            checkValid={checkIsValid}
+            validInfo={validation}
+            key={v.label}
+          />
+        ))}
+      </>
+    </DialogTemplate>
+  );
+};
+
+export default CreateUser;

+ 145 - 0
client/src/pages/database/Database.tsx

@@ -0,0 +1,145 @@
+import React, { useContext, useEffect, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { DatabaseHttp } from '../../http/Database';
+import AttuGrid from '../../components/grid/Grid';
+import { ColDefinitionsType, ToolBarConfig } from '../../components/grid/Types';
+import {
+  CreateDatabaseParams,
+  DropDatabaseParams,
+  DatabaseData,
+} from './Types';
+import DeleteTemplate from '../../components/customDialog/DeleteDialogTemplate';
+import { rootContext } from '../../context/Root';
+import { useNavigationHook } from '../../hooks/Navigation';
+import { ALL_ROUTER_TYPES } from '../../router/Types';
+import CreateUser from './Create';
+
+const Database = () => {
+  useNavigationHook(ALL_ROUTER_TYPES.DATABASES);
+
+  const [databases, setDatabases] = useState<DatabaseData[]>([]);
+  const [selectedDatabase, setSelectedDatabase] = useState<DatabaseData[]>([]);
+  const { setDialog, handleCloseDialog, openSnackBar } =
+    useContext(rootContext);
+  const { t: successTrans } = useTranslation('success');
+  const { t: dbTrans } = useTranslation('database');
+  const { t: btnTrans } = useTranslation('btn');
+  const { t: dialogTrans } = useTranslation('dialog');
+
+  const fetchDatabases = async () => {
+    const res = await DatabaseHttp.getDatabases();
+    setDatabases(res.db_names.map((v: string) => ({ name: v })));
+  };
+
+  const handleCreate = async (data: CreateDatabaseParams) => {
+    await DatabaseHttp.createDatabase(data);
+    fetchDatabases();
+    openSnackBar(successTrans('create', { name: dbTrans('database') }));
+    handleCloseDialog();
+  };
+
+  const handleDelete = async () => {
+    for (const db of selectedDatabase) {
+      const param: DropDatabaseParams = {
+        db_name: db.name,
+      };
+      await DatabaseHttp.dropDatabase(param);
+    }
+
+    openSnackBar(successTrans('delete', { name: dbTrans('database') }));
+    fetchDatabases();
+    handleCloseDialog();
+  };
+
+  const toolbarConfigs: ToolBarConfig[] = [
+    {
+      label: dbTrans('database'),
+      onClick: () => {
+        setDialog({
+          open: true,
+          type: 'custom',
+          params: {
+            component: (
+              <CreateUser
+                handleCreate={handleCreate}
+                handleClose={handleCloseDialog}
+              />
+            ),
+          },
+        });
+      },
+      icon: 'add',
+    },
+
+    {
+      type: 'iconBtn',
+      onClick: () => {
+        setDialog({
+          open: true,
+          type: 'custom',
+          params: {
+            component: (
+              <DeleteTemplate
+                label={btnTrans('drop')}
+                title={dialogTrans('deleteTitle', {
+                  type: dbTrans('database'),
+                })}
+                text={dbTrans('deleteWarning')}
+                handleDelete={handleDelete}
+              />
+            ),
+          },
+        });
+      },
+      label: '',
+      disabled: () =>
+        selectedDatabase.length === 0 ||
+        selectedDatabase.findIndex(v => v.name === 'root') > -1,
+      disabledTooltip: dbTrans('deleteTip'),
+
+      icon: 'delete',
+    },
+  ];
+
+  const colDefinitions: ColDefinitionsType[] = [
+    {
+      id: 'name',
+      align: 'left',
+      disablePadding: false,
+      label: 'Name',
+    },
+  ];
+
+  const handleSelectChange = (value: DatabaseData[]) => {
+    setSelectedDatabase(value);
+  };
+
+  useEffect(() => {
+    fetchDatabases();
+  }, []);
+
+  return (
+    <div className="page-wrapper">
+      <AttuGrid
+        toolbarConfigs={toolbarConfigs}
+        colDefinitions={colDefinitions}
+        rows={databases}
+        rowCount={databases.length}
+        primaryKey="name"
+        showPagination={false}
+        selected={selectedDatabase}
+        setSelected={handleSelectChange}
+        // page={currentPage}
+        // onChangePage={handlePageChange}
+        // rowsPerPage={pageSize}
+        // setRowsPerPage={handlePageSize}
+        // isLoading={loading}
+        // order={order}
+        // orderBy={orderBy}
+        // handleSort={handleGridSort}
+      />
+    </div>
+  );
+};
+
+export default Database;

+ 15 - 0
client/src/pages/database/Types.ts

@@ -0,0 +1,15 @@
+export interface DatabaseData {
+  name: string;
+}
+export interface CreateDatabaseParams {
+  db_name: string;
+}
+
+export interface CreateDatabaseProps {
+  handleCreate: (data: CreateDatabaseParams) => void;
+  handleClose: () => void;
+}
+
+export interface DropDatabaseParams {
+  db_name: string;
+}

+ 140 - 0
client/src/pages/database/Update.tsx

@@ -0,0 +1,140 @@
+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 { UpdateUserParams, UpdateUserProps } from './Types';
+
+const useStyles = makeStyles((theme: Theme) => ({
+  input: {
+    margin: theme.spacing(3, 0, 0.5),
+  },
+}));
+
+const UpdateUser: FC<UpdateUserProps> = ({
+  handleClose,
+  handleUpdate,
+  username,
+}) => {
+  const { t: userTrans } = useTranslation('user');
+  const { t: btnTrans } = useTranslation('btn');
+  const { t: warningTrans } = useTranslation('warning');
+
+  const [form, setForm] = useState<
+    Omit<UpdateUserParams, 'username'> & { confirmPassword: string }
+  >({
+    oldPassword: '',
+    newPassword: '',
+    confirmPassword: '',
+  });
+  const checkedForm = useMemo(() => {
+    const { oldPassword, newPassword } = form;
+    return formatForm({ oldPassword, newPassword });
+  }, [form]);
+  const { validation, checkIsValid, disabled } = useFormValidation(checkedForm);
+
+  const classes = useStyles();
+
+  const handleInputChange = (
+    key: 'oldPassword' | 'newPassword' | 'confirmPassword',
+    value: string
+  ) => {
+    setForm(v => ({ ...v, [key]: value }));
+  };
+
+  const createConfigs: ITextfieldConfig[] = [
+    {
+      label: userTrans('oldPassword'),
+      key: 'oldPassword',
+      onChange: (value: string) => handleInputChange('oldPassword', value),
+      variant: 'filled',
+      className: classes.input,
+      placeholder: userTrans('oldPassword'),
+      fullWidth: true,
+      validations: [
+        {
+          rule: 'require',
+          errorText: warningTrans('required', {
+            name: userTrans('oldPassword'),
+          }),
+        },
+      ],
+      type: 'password',
+      defaultValue: form.oldPassword,
+    },
+    {
+      label: userTrans('newPassword'),
+      key: 'newPassword',
+      onChange: (value: string) => handleInputChange('newPassword', value),
+      variant: 'filled',
+      className: classes.input,
+      placeholder: userTrans('newPassword'),
+      fullWidth: true,
+      validations: [
+        {
+          rule: 'require',
+          errorText: warningTrans('required', {
+            name: userTrans('newPassword'),
+          }),
+        },
+      ],
+      type: 'password',
+      defaultValue: form.newPassword,
+    },
+    {
+      label: userTrans('confirmPassword'),
+      key: 'confirmPassword',
+      onChange: (value: string) => handleInputChange('confirmPassword', value),
+      variant: 'filled',
+      className: classes.input,
+      placeholder: userTrans('confirmPassword'),
+      fullWidth: true,
+      validations: [
+        {
+          rule: 'confirm',
+          extraParam: {
+            compareValue: form.newPassword,
+          },
+          errorText: userTrans('isNotSame'),
+        },
+      ],
+      type: 'password',
+      defaultValue: form.confirmPassword,
+    },
+  ];
+
+  const handleUpdateUser = () => {
+    handleUpdate({
+      username,
+      newPassword: form.newPassword,
+      oldPassword: form.oldPassword,
+    });
+  };
+
+  return (
+    <DialogTemplate
+      title={userTrans('updateTitle')}
+      handleClose={handleClose}
+      confirmLabel={btnTrans('create')}
+      handleConfirm={handleUpdateUser}
+      confirmDisabled={disabled}
+    >
+      <>
+        {createConfigs.map(v => (
+          <CustomInput
+            type="text"
+            textConfig={v}
+            checkValid={checkIsValid}
+            validInfo={validation}
+            key={v.label}
+          />
+        ))}
+      </>
+    </DialogTemplate>
+  );
+};
+
+export default UpdateUser;

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

@@ -69,6 +69,10 @@ function Index() {
       return navTrans('user');
     }
 
+    if (location.pathname.includes('databases')) {
+      return navTrans('database');
+    }
+
     return navTrans('overview');
   }, [location, navTrans]);
 
@@ -90,6 +94,11 @@ function Index() {
       iconActiveClass: 'normal',
       iconNormalClass: 'active',
     },
+    {
+      icon: icons.database,
+      label: navTrans('database'),
+      onClick: () => navigate('/databases'),
+    },
   ];
 
   if (!isManaged) {

+ 1 - 1
client/src/pages/user/User.tsx

@@ -63,7 +63,7 @@ const Users = () => {
 
   const toolbarConfigs: ToolBarConfig[] = [
     {
-      label: userTrans('createTitle'),
+      label: userTrans('user'),
       onClick: () => {
         setDialog({
           open: true,

+ 5 - 2
client/src/router/Router.tsx

@@ -5,6 +5,7 @@ import Collection from '../pages/collections/Collection';
 import Collections from '../pages/collections/Collections';
 import Connect from '../pages/connect/Connect';
 import Users from '../pages/user/User';
+import Database from '../pages/database/Database';
 import Index from '../pages/index';
 import Search from '../pages/search/VectorSearch';
 import System from '../pages/system/SystemView';
@@ -15,6 +16,10 @@ const routeObj = [
     path: '/',
     element: <Index />,
     children: [
+      {
+        path: '/databases',
+        element: <Database />,
+      },
       {
         path: '/collections',
         element: <Collections />,
@@ -23,12 +28,10 @@ const routeObj = [
         path: '/collections/:collectionName',
         element: <Collection />,
       },
-
       {
         path: '/search',
         element: <Search />,
       },
-
       {
         path: '/system_healthy',
         element: <SystemHealthy />,

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

@@ -12,6 +12,7 @@ export enum ALL_ROUTER_TYPES {
   // plugins
   PLUGIN = 'plugin',
   USER = 'user',
+  DATABASES = 'databases',
 }
 
 export type NavInfo = {

+ 2 - 0
server/src/app.ts

@@ -8,6 +8,7 @@ import * as path from 'path';
 import chalk from 'chalk';
 import { router as connectRouter } from './milvus';
 import { router as collectionsRouter } from './collections';
+import { router as databasesRouter } from './database';
 import { router as partitionsRouter } from './partitions';
 import { router as schemaRouter } from './schema';
 import { router as cronsRouter } from './crons';
@@ -35,6 +36,7 @@ const insightCache = new LruCache({
 const router = express.Router();
 // define routers
 router.use('/milvus', connectRouter);
+router.use('/databases', databasesRouter);
 router.use('/collections', collectionsRouter);
 router.use('/partitions', partitionsRouter);
 router.use('/schema', schemaRouter);

+ 64 - 0
server/src/database/databases.controller.ts

@@ -0,0 +1,64 @@
+import { NextFunction, Request, Response, Router } from 'express';
+import { dtoValidationMiddleware } from '../middlewares/validation';
+import { milvusService } from '../milvus';
+import { DatabasesService } from './databases.service';
+import { CreateDatabaseDto } from './dto';
+
+export class DatabasesController {
+  private databasesService: DatabasesService;
+  private router: Router;
+
+  constructor() {
+    this.databasesService = new DatabasesService(milvusService);
+
+    this.router = Router();
+  }
+
+  get databasesServiceGetter() {
+    return this.databasesService;
+  }
+
+  generateRoutes() {
+    this.router.get('/', this.listDatabases.bind(this));
+    this.router.post(
+      '/',
+      dtoValidationMiddleware(CreateDatabaseDto),
+      this.createDatabase.bind(this)
+    );
+
+    this.router.delete('/:name', this.dropDatabase.bind(this));
+
+    return this.router;
+  }
+
+  async createDatabase(req: Request, res: Response, next: NextFunction) {
+    const createDatabaseData = req.body;
+    try {
+      const result = await this.databasesService.createDatabase(
+        createDatabaseData
+      );
+      res.send(result);
+    } catch (error) {
+      next(error);
+    }
+  }
+
+  async listDatabases(req: Request, res: Response, next: NextFunction) {
+    try {
+      const result = await this.databasesService.listDatabase();
+      res.send(result);
+    } catch (error) {
+      next(error);
+    }
+  }
+
+  async dropDatabase(req: Request, res: Response, next: NextFunction) {
+    const db_name = req.params?.name;
+    try {
+      const result = await this.databasesService.dropDatabase({ db_name });
+      res.send(result);
+    } catch (error) {
+      next(error);
+    }
+  }
+}

+ 33 - 0
server/src/database/databases.service.ts

@@ -0,0 +1,33 @@
+import { MilvusService } from '../milvus/milvus.service';
+import {
+  CreateDatabaseRequest,
+  ListDatabasesRequest,
+  DropDatabasesRequest,
+} from '@zilliz/milvus2-sdk-node';
+import { throwErrorFromSDK } from '../utils/Error';
+
+export class DatabasesService {
+  constructor(private milvusService: MilvusService) {}
+
+  async createDatabase(data: CreateDatabaseRequest) {
+    const res = await this.milvusService.client.createDatabase(data);
+    throwErrorFromSDK(res);
+    return res;
+  }
+
+  async listDatabase(data?: ListDatabasesRequest) {
+    const res = await this.milvusService.client.listDatabases(data);
+    throwErrorFromSDK(res.status);
+    return res;
+  }
+
+  async dropDatabase(data: DropDatabasesRequest) {
+    const res = await this.milvusService.client.dropDatabase(data);
+    throwErrorFromSDK(res);
+    return res;
+  }
+
+  async use(db_name: string) {
+    return await await MilvusService.activeMilvusClient.use({ db_name });
+  }
+}

+ 6 - 0
server/src/database/dto.ts

@@ -0,0 +1,6 @@
+import { IsString } from 'class-validator';
+
+export class CreateDatabaseDto {
+  @IsString()
+  readonly db_name: string;
+}

+ 8 - 0
server/src/database/index.ts

@@ -0,0 +1,8 @@
+import { DatabasesController } from './databases.controller';
+
+const databasesManager = new DatabasesController();
+
+const router = databasesManager.generateRoutes();
+const DatabasesService = databasesManager.databasesServiceGetter;
+
+export { router, DatabasesService };