2
0
Эх сурвалжийг харах

feat: support setup database properties (#632)

* init edit db properties page

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* finish

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

---------

Signed-off-by: ryjiang <jiangruiyi@gmail.com>
ryjiang 10 сар өмнө
parent
commit
63c59027cf

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

@@ -138,7 +138,7 @@ const Header: FC = () => {
 
 
                   // if url contains databases, go to the database page
                   // if url contains databases, go to the database page
                   if (window.location.hash.includes('databases')) {
                   if (window.location.hash.includes('databases')) {
-                    navigate(`/databases/${database}`);
+                    navigate(`/databases/${database}/collections`);
                   }
                   }
                 }}
                 }}
                 options={dbOptions}
                 options={dbOptions}

+ 60 - 0
client/src/consts/Milvus.ts

@@ -392,3 +392,63 @@ export const NONE_INDEXABLE_DATA_TYPES = [
   DataTypeStringEnum.JSON,
   DataTypeStringEnum.JSON,
   DataTypeStringEnum.Array,
   DataTypeStringEnum.Array,
 ];
 ];
+
+export type Property = { key: string; value: any; desc: string; type: string };
+
+export const collectionDefaults: Property[] = [
+  { key: 'collection.ttl.seconds', value: '', desc: '', type: 'number' },
+  {
+    key: 'collection.autocompaction.enabled',
+    value: '',
+    desc: '',
+    type: 'boolean',
+  },
+  { key: 'collection.insertRate.max.mb', value: '', desc: '', type: 'number' },
+  { key: 'collection.insertRate.min.mb', value: '', desc: '', type: 'number' },
+  { key: 'collection.upsertRate.max.mb', value: '', desc: '', type: 'number' },
+  { key: 'collection.upsertRate.min.mb', value: '', desc: '', type: 'number' },
+  { key: 'collection.deleteRate.max.mb', value: '', desc: '', type: 'number' },
+  { key: 'collection.deleteRate.min.mb', value: '', desc: '', type: 'number' },
+  {
+    key: 'collection.bulkLoadRate.max.mb',
+    value: '',
+    desc: '',
+    type: 'number',
+  },
+  {
+    key: 'collection.bulkLoadRate.min.mb',
+    value: '',
+    desc: '',
+    type: 'number',
+  },
+  { key: 'collection.queryRate.max.qps', value: '', desc: '', type: 'number' },
+  { key: 'collection.queryRate.min.qps', value: '', desc: '', type: 'number' },
+  { key: 'collection.searchRate.max.vps', value: '', desc: '', type: 'number' },
+  { key: 'collection.searchRate.min.vps', value: '', desc: '', type: 'number' },
+  {
+    key: 'collection.diskProtection.diskQuota.mb',
+    value: '',
+    desc: '',
+    type: 'number',
+  },
+  { key: 'collection.replica.number', value: '', desc: '', type: 'number' },
+  { key: 'collection.resource_groups', value: '', desc: '', type: 'string' },
+  {
+    key: 'partition.diskProtection.diskQuota.mb',
+    value: '',
+    desc: '',
+    type: 'number',
+  },
+  { key: 'mmap.enabled', value: '', desc: '', type: 'boolean' },
+  { key: 'lazyload.enabled', value: '', desc: '', type: 'boolean' },
+  { key: 'partitionkey.isolation', value: '', desc: '', type: 'boolean' },
+  { key: 'indexoffsetcache.enabled', value: '', desc: '', type: 'boolean' },
+];
+
+export const databaseDefaults: Property[] = [
+  { key: 'database.replica.number', value: '', desc: '', type: 'number' },
+  { key: 'database.resource_groups', value: '', desc: '', type: 'string' },
+  { key: 'database.diskQuota.mb', value: '', desc: '', type: 'number' },
+  { key: 'database.max.collections', value: '', desc: '', type: 'number' },
+  { key: 'database.force.deny.writing', value: '', desc: '', type: 'boolean' },
+];

+ 3 - 3
client/src/context/Data.tsx

@@ -71,7 +71,7 @@ export const dataContext = createContext<DataContextType>({
   dropAlias: async () => {
   dropAlias: async () => {
     return {} as CollectionFullObject;
     return {} as CollectionFullObject;
   },
   },
-  setProperty: async () => {
+  setCollectionProperty: async () => {
     return {} as CollectionFullObject;
     return {} as CollectionFullObject;
   },
   },
   ui: {
   ui: {
@@ -348,7 +348,7 @@ export const DataProvider = (props: { children: React.ReactNode }) => {
   };
   };
 
 
   // API: set property
   // API: set property
-  const setProperty = async (
+  const setCollectionProperty = async (
     collectionName: string,
     collectionName: string,
     key: string,
     key: string,
     value: any
     value: any
@@ -482,7 +482,7 @@ export const DataProvider = (props: { children: React.ReactNode }) => {
         dropIndex,
         dropIndex,
         createAlias,
         createAlias,
         dropAlias,
         dropAlias,
-        setProperty,
+        setCollectionProperty,
         ui,
         ui,
         setUIPref,
         setUIPref,
       }}
       }}

+ 1 - 1
client/src/context/Types.ts

@@ -136,7 +136,7 @@ export type DataContextType = {
     collectionName: string,
     collectionName: string,
     alias: string
     alias: string
   ) => Promise<CollectionFullObject>;
   ) => Promise<CollectionFullObject>;
-  setProperty: (
+  setCollectionProperty: (
     collectionName: string,
     collectionName: string,
     key: string,
     key: string,
     value: any
     value: any

+ 19 - 0
client/src/http/Database.service.ts

@@ -10,6 +10,11 @@ export interface DropDatabaseParams {
   db_name: string;
   db_name: string;
 }
 }
 
 
+export interface AlterDatabaseRequest {
+  db_name: string;
+  properties: Record<string, unknown>;
+}
+
 export class DatabaseService extends BaseModel {
 export class DatabaseService extends BaseModel {
   static listDatabases() {
   static listDatabases() {
     return super.search<DatabaseObject[]>({
     return super.search<DatabaseObject[]>({
@@ -25,4 +30,18 @@ export class DatabaseService extends BaseModel {
   static dropDatabase(data: DropDatabaseParams) {
   static dropDatabase(data: DropDatabaseParams) {
     return super.delete({ path: `/databases/${data.db_name}` });
     return super.delete({ path: `/databases/${data.db_name}` });
   }
   }
+
+  static describeDatabase(db_name: string) {
+    return super.search<DatabaseObject>({
+      path: `/databases/${db_name}`,
+      params: {},
+    });
+  }
+
+  static setProperty(data: AlterDatabaseRequest) {
+    return super.update({
+      path: `/databases/${data.db_name}/properties`,
+      data: data.properties,
+    });
+  }
 }
 }

+ 17 - 4
client/src/pages/databases/Databases.tsx

@@ -240,6 +240,7 @@ const Databases = () => {
     databaseName = '',
     databaseName = '',
     collectionName = '',
     collectionName = '',
     collectionPage = '',
     collectionPage = '',
+    databasePage = '',
   } = params;
   } = params;
 
 
   // get style
   // get style
@@ -304,7 +305,11 @@ const Databases = () => {
         ></div>
         ></div>
       </section>
       </section>
       {!collectionName && (
       {!collectionName && (
-        <DatabasesTab databaseName={databaseName} tabClass={classes.tab} />
+        <DatabasesTab
+          databasePage={databasePage}
+          databaseName={databaseName}
+          tabClass={classes.tab}
+        />
       )}
       )}
       {collectionName && (
       {collectionName && (
         <CollectionTabs
         <CollectionTabs
@@ -326,10 +331,11 @@ const Databases = () => {
 
 
 // Database tab pages
 // Database tab pages
 const DatabasesTab = (props: {
 const DatabasesTab = (props: {
+  databasePage: string; // current database page
   databaseName: string;
   databaseName: string;
   tabClass: string; // tab class
   tabClass: string; // tab class
 }) => {
 }) => {
-  const { databaseName, tabClass } = props;
+  const { databaseName, tabClass, databasePage } = props;
   const { t: collectionTrans } = useTranslation('collection');
   const { t: collectionTrans } = useTranslation('collection');
 
 
   const dbTab: ITab[] = [
   const dbTab: ITab[] = [
@@ -338,8 +344,15 @@ const DatabasesTab = (props: {
       component: <Collections />,
       component: <Collections />,
       path: `collections`,
       path: `collections`,
     },
     },
+    {
+      label: collectionTrans('properties'),
+      component: <Properties type="database" target={databaseName} />,
+      path: `properties`,
+    },
   ];
   ];
-  const actionDbTab = dbTab.findIndex(t => t.path === databaseName);
+
+  const actionDbTab = dbTab.findIndex(t => t.path === databasePage);
+
   return (
   return (
     <RouteTabList
     <RouteTabList
       tabs={dbTab}
       tabs={dbTab}
@@ -419,7 +432,7 @@ const CollectionTabs = (props: {
       },
       },
       {
       {
         label: collectionTrans('propertiesTab'),
         label: collectionTrans('propertiesTab'),
-        component: <Properties collection={collection} />,
+        component: <Properties type="collection" target={collection} />,
         path: `properties`,
         path: `properties`,
       }
       }
     );
     );

+ 50 - 64
client/src/pages/databases/collections/properties/Properties.tsx

@@ -1,5 +1,5 @@
 import { Theme } from '@mui/material';
 import { Theme } from '@mui/material';
-import { useContext, useState } from 'react';
+import { useContext, useState, useEffect } from 'react';
 import AttuGrid from '@/components/grid/Grid';
 import AttuGrid from '@/components/grid/Grid';
 import { ColDefinitionsType, ToolBarConfig } from '@/components/grid/Types';
 import { ColDefinitionsType, ToolBarConfig } from '@/components/grid/Types';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
@@ -9,9 +9,11 @@ import EditPropertyDialog from '@/pages/dialogs/EditPropertyDialog';
 import ResetPropertyDialog from '@/pages/dialogs/ResetPropertyDialog';
 import ResetPropertyDialog from '@/pages/dialogs/ResetPropertyDialog';
 import { rootContext } from '@/context';
 import { rootContext } from '@/context';
 import { getLabelDisplayedRows } from '@/pages/search/Utils';
 import { getLabelDisplayedRows } from '@/pages/search/Utils';
-import { CollectionFullObject } from '@server/types';
+import { CollectionFullObject, KeyValuePair } from '@server/types';
 import { formatNumber } from '@/utils';
 import { formatNumber } from '@/utils';
 import { makeStyles } from '@mui/styles';
 import { makeStyles } from '@mui/styles';
+import { DatabaseService } from '@/http';
+import { databaseDefaults, collectionDefaults, Property } from '@/consts';
 
 
 const useStyles = makeStyles((theme: Theme) => ({
 const useStyles = makeStyles((theme: Theme) => ({
   wrapper: {
   wrapper: {
@@ -27,60 +29,25 @@ const useStyles = makeStyles((theme: Theme) => ({
   },
   },
 }));
 }));
 
 
-export type Property = { key: string; value: any; desc: string; type: string };
-
-let defaults: Property[] = [
-  { key: 'collection.ttl.seconds', value: '', desc: '', type: 'number' },
-  {
-    key: 'collection.autocompaction.enabled',
-    value: '',
-    desc: '',
-    type: 'boolean',
-  },
-  { key: 'collection.insertRate.max.mb', value: '', desc: '', type: 'number' },
-  { key: 'collection.insertRate.min.mb', value: '', desc: '', type: 'number' },
-  { key: 'collection.upsertRate.max.mb', value: '', desc: '', type: 'number' },
-  { key: 'collection.upsertRate.min.mb', value: '', desc: '', type: 'number' },
-  { key: 'collection.deleteRate.max.mb', value: '', desc: '', type: 'number' },
-  { key: 'collection.deleteRate.min.mb', value: '', desc: '', type: 'number' },
-  {
-    key: 'collection.bulkLoadRate.max.mb',
-    value: '',
-    desc: '',
-    type: 'number',
-  },
-  {
-    key: 'collection.bulkLoadRate.min.mb',
-    value: '',
-    desc: '',
-    type: 'number',
-  },
-  { key: 'collection.queryRate.max.qps', value: '', desc: '', type: 'number' },
-  { key: 'collection.queryRate.min.qps', value: '', desc: '', type: 'number' },
-  { key: 'collection.searchRate.max.vps', value: '', desc: '', type: 'number' },
-  { key: 'collection.searchRate.min.vps', value: '', desc: '', type: 'number' },
-  {
-    key: 'collection.diskProtection.diskQuota.mb',
-    value: '',
-    desc: '',
-    type: 'number',
-  },
-  {
-    key: 'partition.diskProtection.diskQuota.mb',
-    value: '',
-    desc: '',
-    type: 'number',
-  },
-  { key: 'mmap.enabled', value: '', desc: '', type: 'boolean' },
-  { key: 'lazyload.enabled', value: '', desc: '', type: 'boolean' },
-];
+const mergeProperties = (
+  defaults: Property[],
+  custom: KeyValuePair[] | undefined
+) => {
+  return custom
+    ? defaults.map(i => {
+        let prop = custom.find(p => p.key === i.key);
+        return prop ? { ...i, ...prop } : i;
+      })
+    : defaults;
+};
 
 
 interface PropertiesProps {
 interface PropertiesProps {
-  collection: CollectionFullObject;
+  type: 'collection' | 'database';
+  target?: CollectionFullObject | string;
 }
 }
 
 
 const Properties = (props: PropertiesProps) => {
 const Properties = (props: PropertiesProps) => {
-  const { collection } = props;
+  const { target, type } = props;
 
 
   const classes = useStyles();
   const classes = useStyles();
   const { t } = useTranslation('properties');
   const { t } = useTranslation('properties');
@@ -89,20 +56,35 @@ const Properties = (props: PropertiesProps) => {
   const { t: commonTrans } = useTranslation();
   const { t: commonTrans } = useTranslation();
   const gridTrans = commonTrans('grid');
   const gridTrans = commonTrans('grid');
 
 
+  const [properties, setProperties] = useState<Property[]>([]);
   const [selected, setSelected] = useState<Property[]>([]);
   const [selected, setSelected] = useState<Property[]>([]);
   const { setDialog, openSnackBar } = useContext(rootContext);
   const { setDialog, openSnackBar } = useContext(rootContext);
 
 
-  // combine default properties with collection properties
-  let properties: Property[] = collection
-    ? defaults.map(i => {
-        let prop = collection.properties.find(p => p.key === i.key);
-        if (prop) {
-          return { ...i, ...prop };
-        } else {
-          return i;
+  // setup properties
+  const setupProperties = async () => {
+    let properties: Property[] = [];
+
+    switch (type) {
+      case 'collection':
+        const collection = target as CollectionFullObject;
+        if (!collection || !collection.schema) {
+          return;
         }
         }
-      })
-    : defaults;
+        properties = mergeProperties(collectionDefaults, collection.properties);
+        break;
+
+      case 'database':
+        const db = await DatabaseService.describeDatabase(target as string);
+        properties = mergeProperties(databaseDefaults, db.properties);
+        break;
+    }
+
+    setProperties(properties);
+  };
+
+  useEffect(() => {
+    setupProperties();
+  }, [type, target]);
 
 
   const {
   const {
     pageSize,
     pageSize,
@@ -129,12 +111,14 @@ const Properties = (props: PropertiesProps) => {
           params: {
           params: {
             component: (
             component: (
               <EditPropertyDialog
               <EditPropertyDialog
-                collection={collection}
+                target={target!}
+                type={type}
                 property={selected[0]}
                 property={selected[0]}
                 cb={() => {
                 cb={() => {
                   openSnackBar(
                   openSnackBar(
                     successTrans('update', { name: selected[0].key })
                     successTrans('update', { name: selected[0].key })
                   );
                   );
+                  setupProperties();
                 }}
                 }}
               />
               />
             ),
             ),
@@ -156,12 +140,14 @@ const Properties = (props: PropertiesProps) => {
           params: {
           params: {
             component: (
             component: (
               <ResetPropertyDialog
               <ResetPropertyDialog
-                collection={collection}
+                target={target!}
+                type={type}
                 property={selected[0]}
                 property={selected[0]}
                 cb={() => {
                 cb={() => {
                   openSnackBar(
                   openSnackBar(
                     successTrans('reset', { name: selected[0].key })
                     successTrans('reset', { name: selected[0].key })
                   );
                   );
+                  setupProperties();
                 }}
                 }}
               />
               />
             ),
             ),
@@ -221,7 +207,7 @@ const Properties = (props: PropertiesProps) => {
   };
   };
 
 
   // collection is not found or collection full object is not ready
   // collection is not found or collection full object is not ready
-  if (!collection || !collection.schema) {
+  if (!properties || properties.length === 0) {
     return <StatusIcon type={LoadingType.CREATING} />;
     return <StatusIcon type={LoadingType.CREATING} />;
   }
   }
 
 

+ 24 - 7
client/src/pages/dialogs/EditPropertyDialog.tsx

@@ -8,8 +8,9 @@ import { formatForm } from '@/utils';
 import { IForm, useFormValidation } from '@/hooks';
 import { IForm, useFormValidation } from '@/hooks';
 import { ITextfieldConfig } from '@/components/customInput/Types';
 import { ITextfieldConfig } from '@/components/customInput/Types';
 import { CollectionObject } from '@server/types';
 import { CollectionObject } from '@server/types';
-import { Property } from '../databases/collections/properties/Properties';
+import { Property } from '@/consts';
 import { makeStyles } from '@mui/styles';
 import { makeStyles } from '@mui/styles';
+import { DatabaseService } from '@/http';
 
 
 const useStyles = makeStyles((theme: Theme) => ({
 const useStyles = makeStyles((theme: Theme) => ({
   desc: {
   desc: {
@@ -18,16 +19,17 @@ const useStyles = makeStyles((theme: Theme) => ({
 }));
 }));
 
 
 export interface EditPropertyProps {
 export interface EditPropertyProps {
-  collection: CollectionObject;
+  target: CollectionObject | string;
+  type: 'collection' | 'database';
   property: Property;
   property: Property;
-  cb?: (collection: CollectionObject) => void;
+  cb?: (target: CollectionObject | string) => void;
 }
 }
 
 
 const EditPropertyDialog: FC<EditPropertyProps> = props => {
 const EditPropertyDialog: FC<EditPropertyProps> = props => {
-  const { setProperty } = useContext(dataContext);
+  const { setCollectionProperty } = useContext(dataContext);
   const { handleCloseDialog } = useContext(rootContext);
   const { handleCloseDialog } = useContext(rootContext);
 
 
-  const { cb, collection, property } = props;
+  const { cb, target, property } = props;
   const [form, setForm] = useState<IForm>({
   const [form, setForm] = useState<IForm>({
     key: 'property',
     key: 'property',
     value: property.value,
     value: property.value,
@@ -65,9 +67,24 @@ const EditPropertyDialog: FC<EditPropertyProps> = props => {
       }
       }
     }
     }
 
 
-    await setProperty(collection.collection_name, property.key, value);
+    switch (props.type) {
+      case 'collection':
+        await setCollectionProperty(
+          (target as CollectionObject).collection_name,
+          property.key,
+          value
+        );
+        break;
+      case 'database':
+        await DatabaseService.setProperty({
+          db_name: target as string,
+          properties: { [property.key]: value },
+        });
+        break;
+    }
+
     handleCloseDialog();
     handleCloseDialog();
-    cb && (await cb(collection));
+    cb && (await cb(target));
   };
   };
 
 
   const propertyInputConfig: ITextfieldConfig = {
   const propertyInputConfig: ITextfieldConfig = {

+ 29 - 7
client/src/pages/dialogs/ResetPropertyDialog.tsx

@@ -3,26 +3,48 @@ import { useTranslation } from 'react-i18next';
 import { rootContext, dataContext } from '@/context';
 import { rootContext, dataContext } from '@/context';
 import DeleteTemplate from '@/components/customDialog/DeleteDialogTemplate';
 import DeleteTemplate from '@/components/customDialog/DeleteDialogTemplate';
 import { CollectionObject } from '@server/types';
 import { CollectionObject } from '@server/types';
-import { Property } from '../databases/collections/properties/Properties';
+import { Property } from '@/consts';
+import { DatabaseService } from '@/http';
 
 
 export interface EditPropertyProps {
 export interface EditPropertyProps {
-  collection: CollectionObject;
+  target: CollectionObject | string;
+  type: 'collection' | 'database';
   property: Property;
   property: Property;
-  cb?: (collection: CollectionObject) => void;
+  cb?: (target: CollectionObject | string) => void;
 }
 }
 
 
 const ResetPropertyDialog: FC<EditPropertyProps> = props => {
 const ResetPropertyDialog: FC<EditPropertyProps> = props => {
   // context
   // context
-  const { setProperty } = useContext(dataContext);
+  const { setCollectionProperty } = useContext(dataContext);
   const { handleCloseDialog } = useContext(rootContext);
   const { handleCloseDialog } = useContext(rootContext);
   // props
   // props
-  const { cb, collection, property } = props;
+  const { cb, target, type, property } = props;
 
 
   // UI handlers
   // UI handlers
   const handleDelete = async () => {
   const handleDelete = async () => {
-    await setProperty(collection.collection_name, property.key, '');
+    switch (type) {
+      case 'collection':
+        const collection = target as CollectionObject;
+        if (!collection || !collection.schema) {
+          return;
+        }
+        await setCollectionProperty(
+          collection.collection_name,
+          property.key,
+          ''
+        );
+        break;
+
+      case 'database':
+        await DatabaseService.setProperty({
+          db_name: target as string,
+          properties: { [property.key]: '' },
+        });
+        break;
+    }
+
     handleCloseDialog();
     handleCloseDialog();
-    cb && (await cb(collection));
+    cb && (await cb(target));
   };
   };
 
 
   // i18n
   // i18n

+ 1 - 1
client/src/pages/home/DatabaseCard.tsx

@@ -128,7 +128,7 @@ const DatabaseCard: FC<DatabaseCardProps> = ({
     setDatabase(database.name);
     setDatabase(database.name);
 
 
     // navigate to database detail page
     // navigate to database detail page
-    const targetPath = `/databases/${database.name}`;
+    const targetPath = `/databases/${database.name}/colletions`;
 
 
     navigate(targetPath);
     navigate(targetPath);
   };
   };

+ 9 - 1
client/src/pages/home/Home.tsx

@@ -32,7 +32,15 @@ const useStyles = makeStyles((theme: Theme) => ({
   },
   },
 }));
 }));
 
 
-export const CREATE_DB = { name: '___new___', collections: [], createdTime: 0 };
+export const CREATE_DB = {
+  name: '___new___',
+  collections: [],
+  createdTime: 0,
+  db_name: '___new___',
+  properties: [],
+  created_timestamp: 0,
+  dbID: 0,
+};
 
 
 const Home = () => {
 const Home = () => {
   useNavigationHook(ALL_ROUTER_TYPES.HOME);
   useNavigationHook(ALL_ROUTER_TYPES.HOME);

+ 1 - 1
client/src/pages/index.tsx

@@ -87,7 +87,7 @@ function Index() {
     {
     {
       icon: icons.database,
       icon: icons.database,
       label: navTrans('database'),
       label: navTrans('database'),
-      onClick: () => navigate(`/databases/${database}`),
+      onClick: () => navigate(`/databases/${database}/collections`),
     },
     },
     // {
     // {
     //   icon: icons.navSearch,
     //   icon: icons.navSearch,

+ 1 - 1
server/package.json

@@ -13,7 +13,7 @@
   },
   },
   "dependencies": {
   "dependencies": {
     "@json2csv/plainjs": "^7.0.3",
     "@json2csv/plainjs": "^7.0.3",
-    "@zilliz/milvus2-sdk-node": "2.4.5",
+    "@zilliz/milvus2-sdk-node": "2.4.6",
     "axios": "^1.7.4",
     "axios": "^1.7.4",
     "chalk": "4.1.2",
     "chalk": "4.1.2",
     "class-sanitizer": "^1.0.1",
     "class-sanitizer": "^1.0.1",

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

@@ -26,7 +26,9 @@ export class DatabasesController {
       this.createDatabase.bind(this)
       this.createDatabase.bind(this)
     );
     );
 
 
+    this.router.get('/:name', this.describeDatabase.bind(this));
     this.router.delete('/:name', this.dropDatabase.bind(this));
     this.router.delete('/:name', this.dropDatabase.bind(this));
+    this.router.put('/:name/properties', this.alterDatabase.bind(this));
 
 
     return this.router;
     return this.router;
   }
   }
@@ -73,4 +75,33 @@ export class DatabasesController {
       next(error);
       next(error);
     }
     }
   }
   }
+
+  async describeDatabase(req: Request, res: Response, next: NextFunction) {
+    const db_name = req.params?.name;
+    try {
+      const result = await this.databasesService.describeDatabase(
+        req.clientId,
+        {
+          db_name,
+        }
+      );
+      res.send(result);
+    } catch (error) {
+      next(error);
+    }
+  }
+
+  async alterDatabase(req: Request, res: Response, next: NextFunction) {
+    const db_name = req.params?.name;
+    const properties = req.body;
+    try {
+      const result = await this.databasesService.alterDatabase(req.clientId, {
+        db_name,
+        properties,
+      });
+      res.send(result);
+    } catch (error) {
+      next(error);
+    }
+  }
 }
 }

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

@@ -1,7 +1,10 @@
 import {
 import {
   CreateDatabaseRequest,
   CreateDatabaseRequest,
   ListDatabasesRequest,
   ListDatabasesRequest,
+  DescribeDatabaseRequest,
+  DescribeDatabaseResponse,
   DropDatabasesRequest,
   DropDatabasesRequest,
+  AlterDatabaseRequest,
 } from '@zilliz/milvus2-sdk-node';
 } from '@zilliz/milvus2-sdk-node';
 import { throwErrorFromSDK } from '../utils/Error';
 import { throwErrorFromSDK } from '../utils/Error';
 import { clientCache } from '../app';
 import { clientCache } from '../app';
@@ -17,6 +20,14 @@ export class DatabasesService {
     return res;
     return res;
   }
   }
 
 
+  async describeDatabase(clientId: string, data: DescribeDatabaseRequest) {
+    const { milvusClient } = clientCache.get(clientId);
+
+    const res = await milvusClient.describeDatabase(data);
+    throwErrorFromSDK(res.status);
+    return res as DescribeDatabaseResponse;
+  }
+
   async listDatabase(
   async listDatabase(
     clientId: string,
     clientId: string,
     data?: ListDatabasesRequest
     data?: ListDatabasesRequest
@@ -34,10 +45,18 @@ export class DatabasesService {
         await milvusClient.use({ db_name: res.db_names[i] });
         await milvusClient.use({ db_name: res.db_names[i] });
         await milvusClient.listDatabases(data);
         await milvusClient.listDatabases(data);
         const collections = await milvusClient.showCollections();
         const collections = await milvusClient.showCollections();
+
+        const dbName = res.db_names[i];
+
+        const dbObject = await this.describeDatabase(clientId, {
+          db_name: dbName,
+        });
+
         availableDatabases.push({
         availableDatabases.push({
           name: res.db_names[i],
           name: res.db_names[i],
           collections: collections.data.map(c => c.name),
           collections: collections.data.map(c => c.name),
           createdTime: (res as any).created_timestamp[i] || -1,
           createdTime: (res as any).created_timestamp[i] || -1,
+          ...dbObject,
         });
         });
       } catch (e) {
       } catch (e) {
         // ignore
         // ignore
@@ -73,4 +92,12 @@ export class DatabasesService {
     const dbs = await this.listDatabase(clientId);
     const dbs = await this.listDatabase(clientId);
     return dbs.map(d => d.name).indexOf(data) !== -1;
     return dbs.map(d => d.name).indexOf(data) !== -1;
   }
   }
+
+  async alterDatabase(clientId: string, data: AlterDatabaseRequest) {
+    const { milvusClient } = clientCache.get(clientId);
+
+    const res = await milvusClient.alterDatabase(data);
+    throwErrorFromSDK(res);
+    return res;
+  }
 }
 }

+ 4 - 0
server/src/types/collections.type.ts

@@ -100,6 +100,10 @@ export type CronJobObject = {
 
 
 export type DatabaseObject = {
 export type DatabaseObject = {
   name: string;
   name: string;
+  db_name: string;
+  dbID: string | number;
   createdTime: number;
   createdTime: number;
+  created_timestamp: number;
+  properties: KeyValuePair[];
   collections: string[];
   collections: string[];
 };
 };

+ 4 - 4
server/yarn.lock

@@ -1397,10 +1397,10 @@
   resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.9.tgz#b6ef7457e826be8049667ae673eda7876eb049be"
   resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.9.tgz#b6ef7457e826be8049667ae673eda7876eb049be"
   integrity sha512-4VSbbcMoxc4KLjb1gs96SRmi7w4h1SF+fCoiK0XaQX62buCc1G5d0DC5bJ9xJBNPDSVCmIrcl8BiYxzjrqaaJA==
   integrity sha512-4VSbbcMoxc4KLjb1gs96SRmi7w4h1SF+fCoiK0XaQX62buCc1G5d0DC5bJ9xJBNPDSVCmIrcl8BiYxzjrqaaJA==
 
 
-"@zilliz/milvus2-sdk-node@2.4.5":
-  version "2.4.5"
-  resolved "https://registry.yarnpkg.com/@zilliz/milvus2-sdk-node/-/milvus2-sdk-node-2.4.5.tgz#8cd971e1d0dee6f6d33593a844861d75e3126a15"
-  integrity sha512-k6tphtDXbpIZhyKQYyM6h7h/Y2sFfkwPcAu5IZvx6HK8RurY4bgUpaQW1ax5MeD+BmK04ZNbh5MGVJUT1bKGPw==
+"@zilliz/milvus2-sdk-node@2.4.6":
+  version "2.4.6"
+  resolved "https://registry.yarnpkg.com/@zilliz/milvus2-sdk-node/-/milvus2-sdk-node-2.4.6.tgz#37916e6a4ecae8d2ac55c0e90be3a6188300e734"
+  integrity sha512-78SQitWpKFYDONPuXfKSQCR6aPdOsZlUeyucIEqaXrRE9icfwjO41SUt9/pmLi5Ls2b+X5fEepYQMMZYwS+I+w==
   dependencies:
   dependencies:
     "@grpc/grpc-js" "^1.8.22"
     "@grpc/grpc-js" "^1.8.22"
     "@grpc/proto-loader" "^0.7.10"
     "@grpc/proto-loader" "^0.7.10"