Forráskód Böngészése

Merge pull request #38 from Tumao727/feature/structuce-table

add structure tab
nameczz 4 éve
szülő
commit
0637a7a489

+ 3 - 0
client/src/assets/icons/key.svg

@@ -0,0 +1,3 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M13.8808 1.50995C14.1182 1.74522 14.1182 2.12668 13.8808 2.36195L13.0948 3.14086L14.4888 4.52223C14.7262 4.7575 14.7262 5.13896 14.4888 5.37423L12.361 7.48283C12.1236 7.7181 11.7386 7.7181 11.5012 7.48283L10.1073 6.10146L8.43759 7.75607C8.6245 8.00658 8.78102 8.27889 8.90336 8.56747C9.10521 9.04361 9.21003 9.55463 9.21178 10.0711C9.21352 10.5875 9.11216 11.0992 8.91353 11.5767C8.7149 12.0542 8.42291 12.488 8.0544 12.8532C7.68588 13.2184 7.24811 13.5077 6.76628 13.7046C6.28446 13.9014 5.7681 14.0019 5.24694 14.0001C4.72578 13.9984 4.21011 13.8945 3.72963 13.6945C3.24915 13.4945 2.81334 13.2022 2.4473 12.8346L2.44274 12.83C1.72292 12.0915 1.32464 11.1022 1.33365 10.0755C1.34265 9.04874 1.75824 8.06657 2.4909 7.34052C3.22356 6.61447 4.21468 6.20263 5.25078 6.19371C6.08538 6.18652 6.89538 6.44123 7.56841 6.9134L13.0211 1.50995C13.2585 1.27468 13.6434 1.27468 13.8808 1.50995ZM12.2351 3.99287L10.967 5.24946L11.9311 6.20483L13.1991 4.94823L12.2351 3.99287ZM3.31509 11.9906C2.81817 11.4796 2.54326 10.7957 2.54948 10.086C2.55572 9.37514 2.84343 8.69517 3.35066 8.19252C3.85789 7.68987 4.54405 7.40475 5.26135 7.39857C5.97865 7.3924 6.6697 7.66565 7.18567 8.15949C7.19118 8.16477 7.19677 8.16992 7.20243 8.17495C7.4495 8.42177 7.64642 8.71346 7.78238 9.03415C7.92213 9.36379 7.99469 9.71757 7.9959 10.0751C7.99711 10.4327 7.92694 10.7869 7.78942 11.1175C7.65191 11.448 7.44976 11.7484 7.19464 12.0012C6.93951 12.254 6.63644 12.4543 6.30286 12.5906C5.96929 12.7269 5.61181 12.7964 5.25101 12.7952C4.89021 12.794 4.53321 12.7221 4.20057 12.5836C3.86893 12.4456 3.56803 12.244 3.31509 11.9906ZM3.31509 11.9906L3.31733 11.9929L2.88005 12.4115L3.3128 11.9883L3.31509 11.9906Z" fill="#010E29"/>
+</svg>

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

@@ -98,7 +98,7 @@ const MilvusGrid: FC<MilvusGridType> = props => {
     searchForm,
     searchForm,
     openCheckBox = true,
     openCheckBox = true,
     disableSelect = false,
     disableSelect = false,
-    noData = t('grid.noData'),
+    noData = gridTrans.noData,
     showHoverStyle = true,
     showHoverStyle = true,
     selected = [],
     selected = [],
     setSelected = () => {},
     setSelected = () => {},

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

@@ -26,6 +26,7 @@ import { ReactComponent as ConsoleIcon } from '../../assets/icons/console.svg';
 import { ReactComponent as InfoIcon } from '../../assets/icons/info.svg';
 import { ReactComponent as InfoIcon } from '../../assets/icons/info.svg';
 import { ReactComponent as ReleaseIcon } from '../../assets/icons/release.svg';
 import { ReactComponent as ReleaseIcon } from '../../assets/icons/release.svg';
 import { ReactComponent as LoadIcon } from '../../assets/icons/load.svg';
 import { ReactComponent as LoadIcon } from '../../assets/icons/load.svg';
+import { ReactComponent as KeyIcon } from '../../assets/icons/key.svg';
 
 
 const icons: { [x in IconsType]: (props?: any) => React.ReactElement } = {
 const icons: { [x in IconsType]: (props?: any) => React.ReactElement } = {
   search: (props = {}) => <SearchIcon {...props} />,
   search: (props = {}) => <SearchIcon {...props} />,
@@ -68,6 +69,9 @@ const icons: { [x in IconsType]: (props?: any) => React.ReactElement } = {
   load: (props = {}) => (
   load: (props = {}) => (
     <SvgIcon viewBox="0 0 24 24" component={LoadIcon} {...props} />
     <SvgIcon viewBox="0 0 24 24" component={LoadIcon} {...props} />
   ),
   ),
+  key: (props = {}) => (
+    <SvgIcon viewBox="0 0 16 16" component={KeyIcon} {...props} />
+  ),
 };
 };
 
 
 export default icons;
 export default icons;

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

@@ -23,4 +23,5 @@ export type IconsType =
   | 'info'
   | 'info'
   | 'release'
   | 'release'
   | 'load'
   | 'load'
-  | 'remove';
+  | 'remove'
+  | 'key';

+ 0 - 2
client/src/http/BaseModel.ts

@@ -55,9 +55,7 @@ export default class BaseModel {
    */
    */
   static async create(options: updateParamsType) {
   static async create(options: updateParamsType) {
     const { path, data } = options;
     const { path, data } = options;
-
     const res = await http.post(path, data);
     const res = await http.post(path, data);
-
     return new this(res.data.data || {});
     return new this(res.data.data || {});
   }
   }
 
 

+ 47 - 0
client/src/http/Field.ts

@@ -0,0 +1,47 @@
+import { DataType } from '../pages/collections/Types';
+import { FieldData } from '../pages/structure/Types';
+import BaseModel from './BaseModel';
+
+export class FieldHttp extends BaseModel implements FieldData {
+  data_type!: DataType;
+  fieldID!: string;
+  type_params!: { key: string; value: string }[];
+  is_primary_key!: true;
+  name!: string;
+
+  constructor(props: {}) {
+    super(props);
+    Object.assign(this, props);
+  }
+
+  static async getFields(collectionName: string): Promise<FieldHttp[]> {
+    const path = `/collections/${collectionName}`;
+
+    const res = await super.findAll({
+      path,
+      params: {},
+    });
+
+    return res.schema.fields.map((f: any) => new this(f));
+  }
+
+  get _fieldId() {
+    return this.fieldID;
+  }
+
+  get _isPrimaryKey() {
+    return this.is_primary_key;
+  }
+
+  get _fieldName() {
+    return this.name;
+  }
+
+  get _fieldType() {
+    return this.data_type;
+  }
+
+  get _dimension() {
+    return this.type_params.find(item => item.key === 'dim')?.value || '--';
+  }
+}

+ 48 - 0
client/src/http/Index.ts

@@ -0,0 +1,48 @@
+import { IndexView } from '../pages/structure/Types';
+import { IndexState } from '../types/Milvus';
+import BaseModel from './BaseModel';
+
+export class IndexHttp extends BaseModel implements IndexView {
+  params!: { key: string; value: string }[];
+  field_name!: string;
+
+  constructor(props: {}) {
+    super(props);
+    Object.assign(this, props);
+  }
+
+  static BASE_URL = `/schema/index`;
+
+  static async getIndexStatus(
+    collectionName: string,
+    fieldName: string
+  ): Promise<IndexState> {
+    const path = `${this.BASE_URL}/state`;
+    return super.findAll({
+      path,
+      params: { collection_name: collectionName, field_name: fieldName },
+    });
+  }
+
+  static async getIndexInfo(collectionName: string): Promise<IndexHttp[]> {
+    const path = this.BASE_URL;
+
+    const res = await super.findAll({
+      path,
+      params: { collection_name: collectionName },
+    });
+    return res.index_descriptions.map((index: any) => new this(index));
+  }
+
+  get _indexType() {
+    return this.params.find(p => p.key === 'index_type')?.value || '';
+  }
+
+  get _indexParameterPairs() {
+    return this.params.filter(p => p.key !== 'index_type');
+  }
+
+  get _fieldName() {
+    return this.field_name;
+  }
+}

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

@@ -26,6 +26,7 @@ const collectionTrans = {
   fieldName: 'Field Name',
   fieldName: 'Field Name',
   autoId: 'Auto ID',
   autoId: 'Auto ID',
   dimension: 'Dimension',
   dimension: 'Dimension',
+  dimensionTooltip: 'Only vector type has dimension',
   newBtn: 'add new field',
   newBtn: 'add new field',
 
 
   // load dialog
   // load dialog

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

@@ -0,0 +1,7 @@
+const indexTrans = {
+  type: 'Index Type',
+  param: 'Index Parameters',
+  create: 'Create Index',
+};
+
+export default indexTrans;

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

@@ -15,6 +15,7 @@ const partitionTrans = {
 
 
   deleteWarning:
   deleteWarning:
     'You are trying to delete partition. This action cannot be undone.',
     'You are trying to delete partition. This action cannot be undone.',
+  deletePartitionError: 'default partition cannot be deleted',
 };
 };
 
 
 export default partitionTrans;
 export default partitionTrans;

+ 0 - 4
client/src/i18n/cn/warning.ts

@@ -1,12 +1,8 @@
 const warningTrans = {
 const warningTrans = {
-  closeDialog: '已填写的表单会丢失,确认离开?',
-
   required: '{{name}} is required',
   required: '{{name}} is required',
   positive: '{{name}} should be positive',
   positive: '{{name}} should be positive',
   integer: '{{name}} should be integers',
   integer: '{{name}} should be integers',
   range: 'range is {{min}} ~ {{max}}',
   range: 'range is {{min}} ~ {{max}}',
-
-  deletePartition: 'default partition cannot be deleted',
 };
 };
 
 
 export default warningTrans;
 export default warningTrans;

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

@@ -26,6 +26,7 @@ const collectionTrans = {
   fieldName: 'Field Name',
   fieldName: 'Field Name',
   autoId: 'Auto ID',
   autoId: 'Auto ID',
   dimension: 'Dimension',
   dimension: 'Dimension',
+  dimensionTooltip: 'Only vector type has dimension',
   newBtn: 'add new field',
   newBtn: 'add new field',
 
 
   // load dialog
   // load dialog

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

@@ -0,0 +1,8 @@
+const indexTrans = {
+  type: 'Index Type',
+  param: 'Index Parameters',
+
+  create: 'Create Index',
+};
+
+export default indexTrans;

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

@@ -15,6 +15,7 @@ const partitionTrans = {
 
 
   deleteWarning:
   deleteWarning:
     'You are trying to delete partition. This action cannot be undone.',
     'You are trying to delete partition. This action cannot be undone.',
+  deletePartitionError: 'default partition cannot be deleted',
 };
 };
 
 
 export default partitionTrans;
 export default partitionTrans;

+ 0 - 4
client/src/i18n/en/warning.ts

@@ -1,12 +1,8 @@
 const warningTrans = {
 const warningTrans = {
-  closeDialog: 'Will lose your data, are you sure to leave?',
-
   required: '{{name}} is required',
   required: '{{name}} is required',
   positive: '{{name}} should be positive',
   positive: '{{name}} should be positive',
   integer: '{{name}} should be integers',
   integer: '{{name}} should be integers',
   range: 'range is {{min}} ~ {{max}}',
   range: 'range is {{min}} ~ {{max}}',
-
-  deletePartition: 'default partition cannot be deleted',
 };
 };
 
 
 export default warningTrans;
 export default warningTrans;

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

@@ -19,6 +19,8 @@ import partitionCn from './cn/partition';
 import partitionEn from './en/partition';
 import partitionEn from './en/partition';
 import successEn from './en/success';
 import successEn from './en/success';
 import successCn from './cn/success';
 import successCn from './cn/success';
+import indexEn from './en/index';
+import indexCn from './cn/index';
 
 
 export const resources = {
 export const resources = {
   cn: {
   cn: {
@@ -31,6 +33,7 @@ export const resources = {
     dialog: dialogCn,
     dialog: dialogCn,
     partition: partitionCn,
     partition: partitionCn,
     success: successCn,
     success: successCn,
+    index: indexCn,
   },
   },
   en: {
   en: {
     translation: commonEn,
     translation: commonEn,
@@ -42,6 +45,7 @@ export const resources = {
     dialog: dialogEn,
     dialog: dialogEn,
     partition: partitionEn,
     partition: partitionEn,
     success: successEn,
     success: successEn,
+    index: indexEn,
   },
   },
 };
 };
 
 

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

@@ -7,6 +7,7 @@ import Partitions from '../partitions/partitions';
 import { useHistory, useLocation, useParams } from 'react-router-dom';
 import { useHistory, useLocation, useParams } from 'react-router-dom';
 import { useMemo } from 'react';
 import { useMemo } from 'react';
 import { parseLocationSearch } from '../../utils/Format';
 import { parseLocationSearch } from '../../utils/Format';
+import Structure from '../structure/Structure';
 
 
 enum TAB_EMUM {
 enum TAB_EMUM {
   'partition',
   'partition',
@@ -45,7 +46,7 @@ const Collection = () => {
     },
     },
     {
     {
       label: t('structureTab'),
       label: t('structureTab'),
-      component: <section>structure section</section>,
+      component: <Structure collectionName={collectionName} />,
     },
     },
   ];
   ];
 
 

+ 15 - 14
client/src/pages/collections/CreateFields.tsx

@@ -79,18 +79,20 @@ const CreateFields: FC<CreateFieldsProps> = ({
     label: string,
     label: string,
     value: number,
     value: number,
     onChange: (value: DataTypeEnum) => void
     onChange: (value: DataTypeEnum) => void
-  ) => (
-    <CustomSelector
-      options={type === 'all' ? ALL_OPTIONS : VECTOR_FIELDS_OPTIONS}
-      onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
-        onChange(e.target.value as DataTypeEnum);
-      }}
-      value={value}
-      variant="filled"
-      label={label}
-      classes={{ root: classes.select }}
-    />
-  );
+  ) => {
+    return (
+      <CustomSelector
+        options={type === 'all' ? ALL_OPTIONS : VECTOR_FIELDS_OPTIONS}
+        onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
+          onChange(e.target.value as DataTypeEnum);
+        }}
+        value={value}
+        variant="filled"
+        label={label}
+        classes={{ root: classes.select }}
+      />
+    );
+  };
 
 
   const getInput = (
   const getInput = (
     label: string,
     label: string,
@@ -131,7 +133,6 @@ const CreateFields: FC<CreateFieldsProps> = ({
         [key]: value,
         [key]: value,
       };
       };
     });
     });
-
     setFields(newFields);
     setFields(newFields);
   };
   };
 
 
@@ -248,7 +249,7 @@ const CreateFields: FC<CreateFieldsProps> = ({
           'all',
           'all',
           t('fieldType'),
           t('fieldType'),
           field.data_type,
           field.data_type,
-          (value: DataTypeEnum) => changeFields(field.id, 'type', value)
+          (value: DataTypeEnum) => changeFields(field.id, 'data_type', value)
         )}
         )}
 
 
         {getInput(
         {getInput(

+ 10 - 0
client/src/pages/collections/Types.ts

@@ -38,6 +38,16 @@ export enum DataTypeEnum {
   FloatVector = 101,
   FloatVector = 101,
 }
 }
 
 
+export type DataType =
+  | 'Int8'
+  | 'Int16'
+  | 'Int32'
+  | 'Int64'
+  | 'Float'
+  | 'Double'
+  | 'BinaryVector'
+  | 'FloatVector';
+
 export interface Field {
 export interface Field {
   name: string;
   name: string;
   data_type: DataTypeEnum;
   data_type: DataTypeEnum;

+ 1 - 2
client/src/pages/partitions/partitions.tsx

@@ -32,7 +32,6 @@ const Partitions: FC<{
   const classes = useStyles();
   const classes = useStyles();
   const { t } = useTranslation('partition');
   const { t } = useTranslation('partition');
   const { t: successTrans } = useTranslation('success');
   const { t: successTrans } = useTranslation('success');
-  const { t: warningTrans } = useTranslation('warning');
   const { t: btnTrans } = useTranslation('btn');
   const { t: btnTrans } = useTranslation('btn');
   const { t: dialogTrans } = useTranslation('dialog');
   const { t: dialogTrans } = useTranslation('dialog');
   const InfoIcon = icons.info;
   const InfoIcon = icons.info;
@@ -154,7 +153,7 @@ const Partitions: FC<{
         selectedPartitions.length === 0 ||
         selectedPartitions.length === 0 ||
         selectedPartitions.some(p => p._name === '_default'),
         selectedPartitions.some(p => p._name === '_default'),
       tooltip: selectedPartitions.some(p => p._name === '_default')
       tooltip: selectedPartitions.some(p => p._name === '_default')
-        ? warningTrans('deletePartition')
+        ? t('deletePartitionError')
         : '',
         : '',
     },
     },
   ];
   ];

+ 94 - 0
client/src/pages/structure/IndexTypeElement.tsx

@@ -0,0 +1,94 @@
+import { FC, useCallback, useEffect, useState } from 'react';
+import Chip from '@material-ui/core/Chip';
+import CustomButton from '../../components/customButton/CustomButton';
+import { IndexHttp } from '../../http/Index';
+import { IndexState } from '../../types/Milvus';
+import { FieldView } from './Types';
+import StatusIcon from '../../components/status/StatusIcon';
+import { ChildrenStatusType } from '../../components/status/Types';
+import { useTranslation } from 'react-i18next';
+import { makeStyles, Theme } from '@material-ui/core';
+import icons from '../../components/icons/Icons';
+
+const useStyles = makeStyles((theme: Theme) => ({
+  item: {
+    paddingLeft: theme.spacing(1),
+  },
+  btn: {
+    '& span': {
+      textTransform: 'uppercase',
+    },
+  },
+  chip: {
+    backgroundColor: '#e9e9ed',
+  },
+}));
+
+const IndexTypeElement: FC<{ data: FieldView; collectionName: string }> = ({
+  data,
+  collectionName,
+}) => {
+  const classes = useStyles();
+
+  const [status, setStatus] = useState<string>('');
+  const { t } = useTranslation('index');
+
+  const AddIcon = icons.add;
+  const DeleteIcon = icons.delete;
+
+  const fetchStatus = useCallback(async () => {
+    const status = await IndexHttp.getIndexStatus(
+      collectionName,
+      data._fieldName
+    );
+    setStatus(status);
+  }, [collectionName, data._fieldName]);
+
+  useEffect(() => {
+    fetchStatus();
+  }, [fetchStatus]);
+
+  const handleCreate = () => {};
+
+  const handleDelete = () => {};
+
+  const generateElement = () => {
+    if (
+      data._fieldType !== 'BinaryVector' &&
+      data._fieldType !== 'FloatVector'
+    ) {
+      return <div className={classes.item}>--</div>;
+    }
+
+    switch (data._indexType) {
+      case '': {
+        return (
+          <CustomButton
+            disabled={data._createIndexDisabled}
+            className={classes.btn}
+            onClick={handleCreate}
+          >
+            <AddIcon />
+            {t('create')}
+          </CustomButton>
+        );
+      }
+      default: {
+        return status === IndexState.InProgress ? (
+          <StatusIcon type={ChildrenStatusType.CREATING} />
+        ) : (
+          <Chip
+            label={data._indexType}
+            classes={{ root: classes.chip }}
+            deleteIcon={<DeleteIcon />}
+            onDelete={handleDelete}
+          />
+        );
+      }
+    }
+  };
+
+  return <>{generateElement()}</>;
+};
+
+export default IndexTypeElement;

+ 210 - 0
client/src/pages/structure/Structure.tsx

@@ -0,0 +1,210 @@
+import { makeStyles, Theme, Typography } from '@material-ui/core';
+import { FC, useCallback, useEffect, useState } from 'react';
+import MilvusGrid from '../../components/grid';
+import { ColDefinitionsType } 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 { FieldHttp } from '../../http/Field';
+import { FieldView } from './Types';
+import IndexTypeElement from './IndexTypeElement';
+import { DataType } from '../collections/Types';
+import { IndexHttp } from '../../http/Index';
+
+const useStyles = makeStyles((theme: Theme) => ({
+  wrapper: {
+    height: '100%',
+  },
+  icon: {
+    fontSize: '20px',
+    marginLeft: theme.spacing(0.5),
+  },
+  nameWrapper: {
+    display: 'flex',
+    alignItems: 'center',
+
+    '& .key': {
+      width: '16px',
+      height: '16px',
+      marginLeft: theme.spacing(0.5),
+    },
+  },
+
+  param: {
+    padding: theme.spacing(0.5),
+
+    marginRight: theme.spacing(2),
+
+    '& .key': {
+      color: '#82838e',
+      display: 'inline-block',
+      marginRight: theme.spacing(0.5),
+    },
+
+    '& .value': {
+      color: '#010e29',
+    },
+  },
+}));
+
+const Structure: FC<{
+  collectionName: string;
+}> = ({ collectionName }) => {
+  const classes = useStyles();
+  const { t: collectionTrans } = useTranslation('collection');
+  const { t: indexTrans } = useTranslation('index');
+  const InfoIcon = icons.info;
+
+  const [fields, setFields] = useState<FieldView[]>([]);
+  const [loading, setLoading] = useState<boolean>(true);
+
+  const {
+    pageSize,
+    currentPage,
+    handleCurrentPage,
+    total,
+    data: structureList,
+  } = usePaginationHook(fields);
+
+  const fetchStructureListWithIndex = async (
+    collectionName: string
+  ): Promise<FieldView[]> => {
+    const vectorTypes: DataType[] = ['BinaryVector', 'FloatVector'];
+    const indexList = await IndexHttp.getIndexInfo(collectionName);
+    const structureList = await FieldHttp.getFields(collectionName);
+    let fields: FieldView[] = [];
+    for (const structure of structureList) {
+      let field: FieldView = Object.assign(structure, {
+        _indexParameterPairs: [],
+        _indexType: '',
+      });
+      if (vectorTypes.includes(structure.data_type)) {
+        const index = indexList.find(i => i._fieldName === structure.name);
+
+        field._indexParameterPairs = index?._indexParameterPairs || [];
+        field._indexType = index?._indexType || '';
+        field._createIndexDisabled = indexList.length > 0;
+      }
+
+      fields = [...fields, field];
+    }
+    return fields;
+  };
+
+  const fetchFields = useCallback(
+    async (collectionName: string) => {
+      const KeyIcon = icons.key;
+
+      try {
+        const list = await fetchStructureListWithIndex(collectionName);
+        const fields: FieldView[] = list.map(f =>
+          Object.assign(f, {
+            _fieldNameElement: (
+              <div className={classes.nameWrapper}>
+                {f._fieldName}
+                {f._isPrimaryKey && <KeyIcon classes={{ root: 'key' }} />}
+              </div>
+            ),
+            _indexParamElement: (
+              <>
+                {f._indexParameterPairs?.length > 0 ? (
+                  f._indexParameterPairs.map(p => (
+                    <span key={p.key} className={classes.param}>
+                      <Typography variant="caption" className="key">
+                        {`${p.key}:`}
+                      </Typography>
+                      <Typography variant="caption" className="value">
+                        {p.value}
+                      </Typography>
+                    </span>
+                  ))
+                ) : (
+                  <>--</>
+                )}
+              </>
+            ),
+            _indexTypeElement: (
+              <IndexTypeElement data={f} collectionName={collectionName} />
+            ),
+          })
+        );
+
+        setFields(fields);
+        setLoading(false);
+      } catch (err) {
+        setLoading(false);
+        throw err;
+      }
+    },
+    [classes.nameWrapper, classes.param]
+  );
+
+  useEffect(() => {
+    fetchFields(collectionName);
+  }, [collectionName, fetchFields]);
+
+  const colDefinitions: ColDefinitionsType[] = [
+    {
+      id: '_fieldNameElement',
+      align: 'left',
+      disablePadding: true,
+      label: collectionTrans('fieldName'),
+    },
+    {
+      id: '_fieldType',
+      align: 'left',
+      disablePadding: false,
+      label: collectionTrans('fieldType'),
+    },
+    {
+      id: '_dimension',
+      align: 'left',
+      disablePadding: false,
+      label: (
+        <span className="flex-center">
+          {collectionTrans('dimension')}
+          <CustomToolTip title={collectionTrans('dimensionTooltip')}>
+            <InfoIcon classes={{ root: classes.icon }} />
+          </CustomToolTip>
+        </span>
+      ),
+    },
+    {
+      id: '_indexTypeElement',
+      align: 'left',
+      disablePadding: true,
+      label: indexTrans('type'),
+    },
+    {
+      id: '_indexParamElement',
+      align: 'left',
+      disablePadding: false,
+      label: indexTrans('param'),
+    },
+  ];
+
+  const handlePageChange = (e: any, page: number) => {
+    handleCurrentPage(page);
+  };
+
+  return (
+    <section className={classes.wrapper}>
+      <MilvusGrid
+        toolbarConfigs={[]}
+        colDefinitions={colDefinitions}
+        rows={structureList}
+        rowCount={total}
+        primaryKey="_fieldId"
+        openCheckBox={false}
+        showHoverStyle={false}
+        page={currentPage}
+        onChangePage={handlePageChange}
+        rowsPerPage={pageSize}
+        isLoading={loading}
+      />
+    </section>
+  );
+};
+
+export default Structure;

+ 39 - 0
client/src/pages/structure/Types.ts

@@ -0,0 +1,39 @@
+import { ReactElement } from 'react';
+import { IndexState } from '../../types/Milvus';
+import { DataType } from '../collections/Types';
+
+export enum INDEX_TYPES_ENUM {
+  IVF_FLAT = 'IVF_FLAT',
+  IVF_PQ = 'IVF_PQ',
+  IVF_SQ8 = 'IVF_SQ8',
+  IVF_SQ8_HYBRID = 'IVF_SQ8_HYBRID',
+  FLAT = 'FLAT',
+  HNSW = 'HNSW',
+  ANNOY = 'ANNOY',
+  RNSG = 'RNSG',
+}
+
+export interface FieldData {
+  _fieldId: string;
+  _isPrimaryKey: boolean;
+  _fieldName: string;
+  _fieldNameElement?: ReactElement;
+  _fieldType: DataType;
+  _dimension: string;
+}
+
+export interface FieldView extends FieldData, IndexView {
+  _createIndexDisabled?: boolean;
+}
+
+export interface Index {
+  params: { key: string; value: string }[];
+}
+
+export interface IndexView {
+  _fieldName: string;
+  _indexType: string;
+  _indexTypeElement?: ReactElement;
+  _indexParameterPairs: { key: string; value: string }[];
+  _indexParamElement?: ReactElement;
+}