Browse Source

Add collection overview page (#430)

* update tree part1

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

* add tooltip

Signed-off-by: ruiyi.jiang <ruiyi.jiang@zilliz.com>

* update style

Signed-off-by: ruiyi.jiang <ruiyi.jiang@zilliz.com>

* adjust style

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

* add overview page

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

* add overview page

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

* rename

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

---------

Signed-off-by: ryjiang <jiangruiyi@gmail.com>
Signed-off-by: ruiyi.jiang <ruiyi.jiang@zilliz.com>
ryjiang 1 year ago
parent
commit
d76e5e964a

+ 2 - 15
client/src/components/grid/Table.tsx

@@ -19,22 +19,9 @@ const useStyles = makeStyles(theme => ({
   root: {
     width: '100%',
     flexGrow: 1,
-    /* set flex basis to make child item height 100% work on Safari */
-    flexBasis: 0,
+    // /* set flex basis to make child item height 100% work on Safari */
+    // flexBasis: 0,
     background: '#fff',
-
-    // change scrollbar style
-    '&::-webkit-scrollbar': {
-      width: '8px',
-    },
-
-    '&::-webkit-scrollbar-track': {
-      backgroundColor: '#f9f9f9',
-    },
-
-    '&::-webkit-scrollbar-thumb': {
-      backgroundColor: '#eee',
-    },
   },
   box: {
     backgroundColor: '#fff',

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

@@ -710,6 +710,23 @@ const icons: { [x in IconsType]: (props?: any) => React.ReactElement } = {
       ></path>
     </SvgIcon>
   ),
+  check: (props = {}) => (
+    <SvgIcon
+      width="15"
+      height="15"
+      viewBox="0 0 15 15"
+      fill="none"
+      xmlns="http://www.w3.org/2000/svg"
+      {...props}
+    >
+      <path
+        d="M11.4669 3.72684C11.7558 3.91574 11.8369 4.30308 11.648 4.59198L7.39799 11.092C7.29783 11.2452 7.13556 11.3467 6.95402 11.3699C6.77247 11.3931 6.58989 11.3355 6.45446 11.2124L3.70446 8.71241C3.44905 8.48022 3.43023 8.08494 3.66242 7.82953C3.89461 7.57412 4.28989 7.55529 4.5453 7.78749L6.75292 9.79441L10.6018 3.90792C10.7907 3.61902 11.178 3.53795 11.4669 3.72684Z"
+        fill="currentColor"
+        fillRule="evenodd"
+        clipRule="evenodd"
+      ></path>
+    </SvgIcon>
+  ),
 };
 
 export default icons;

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

@@ -46,4 +46,5 @@ export type IconsType =
   | 'prev'
   | 'expand'
   | 'github'
-  | 'question';
+  | 'question'
+  | 'check';

+ 3 - 3
client/src/components/status/StatusIcon.tsx

@@ -10,13 +10,13 @@ const useStyles = makeStyles((theme: Theme) => ({
     paddingLeft: theme.spacing(1),
   },
   svg: {
-    color: theme.palette.primary.main,
+    color: theme.palette.primary.light,
   },
 }));
 
 const StatusIcon: FC<StatusIconType> = props => {
   const classes = useStyles();
-  const { type, className = '', size = 20 } = props;
+  const { type, className = '', size = 16 } = props;
 
   const getElement = (type: ChildrenStatusType): ReactElement => {
     switch (type) {
@@ -24,7 +24,7 @@ const StatusIcon: FC<StatusIconType> = props => {
         return (
           <CircularProgress
             size={size}
-            thickness={8}
+            thickness={4}
             classes={{ svg: classes.svg }}
           />
         );

+ 2 - 2
client/src/components/status/Types.ts

@@ -8,9 +8,9 @@ export type StatusType = {
 
 export type StatusActionType = {
   status: LOADING_STATE;
-  percentage?: string;
+  percentage?: string | number;
   action?: Function;
-  field: SchemaObject;
+  schema: SchemaObject;
   collectionName: string;
   onIndexCreate?: Function;
 };

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

@@ -82,7 +82,7 @@ const collectionTrans = {
 
   // collection tabs
   partitionTab: '分区',
-  schemaTab: 'Schema',
+  schemaTab: '概览',
   dataTab: '数据',
   previewTab: '数据预览',
   segmentsTab: '数据段(Segments)',
@@ -127,6 +127,7 @@ const collectionTrans = {
   consistencyEventuallyTooltip:
     '没有保证读写的顺序,副本最终会在没有进一步写操作的情况下收敛到相同的状态。',
   releaseCollectionFirst: '请先释放collection.',
+  noVectorIndexTooltip: '没有向量索引,请先创建一个。',
 
   clickToLoad: '点击加载collection。',
   clickToRelease: '点击释放collection。',

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

@@ -17,6 +17,7 @@ const commonTrans = {
     error: '错误',
     running: '运行中',
     loading: '正在加载',
+    noVectorIndex: '向量索引不存在',
   },
   grid: {
     action: '操作',

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

@@ -84,7 +84,7 @@ const collectionTrans = {
 
   // collection tabs
   partitionTab: 'Partitions',
-  schemaTab: 'Schema',
+  schemaTab: 'Overview',
   dataTab: 'Data',
   previewTab: 'Data Preview',
   segmentsTab: 'Segments',
@@ -125,6 +125,7 @@ const collectionTrans = {
   consistencySessionTooltip: `It ensures that all data writes can be immediately perceived in reads during the same session.`,
   consistencyEventuallyTooltip: `There is no guaranteed order of reads and writes, and replicas eventually converge to the same state given that no further write operations are done.`,
   releaseCollectionFirst: `Please release your collection first.`,
+  noVectorIndexTooltip: `No vector index, create one first.`,
 
   clickToLoad: 'Click to load the collection.',
   clickToRelease: 'Click to release the collection.',

+ 1 - 2
client/src/i18n/en/common.ts

@@ -1,5 +1,3 @@
-import { partition } from "d3";
-
 const commonTrans = {
   attu: {
     admin: 'Attu',
@@ -19,6 +17,7 @@ const commonTrans = {
     error: 'error',
     running: 'running',
     loading: 'is loaded',
+    noVectorIndex: 'No Vector Index',
   },
   grid: {
     action: 'action',

+ 3 - 60
client/src/pages/collections/Collections.tsx

@@ -1,6 +1,6 @@
 import { useCallback, useContext, useMemo, useState } from 'react';
 import { Link, useSearchParams } from 'react-router-dom';
-import { makeStyles, Theme, Chip, Tooltip } from '@material-ui/core';
+import { makeStyles, Theme } from '@material-ui/core';
 import { useTranslation } from 'react-i18next';
 import Highlighter from 'react-highlight-words';
 import { rootContext, authContext, dataContext } from '@/context';
@@ -93,13 +93,6 @@ const Collections = () => {
   const QuestionIcon = icons.question;
   const SourceIcon = icons.source;
 
-  const consistencyTooltipsMap: Record<string, string> = {
-    Strong: collectionTrans('consistencyStrongTooltip'),
-    Bounded: collectionTrans('consistencyBoundedTooltip'),
-    Session: collectionTrans('consistencySessionTooltip'),
-    Eventually: collectionTrans('consistencyEventuallyTooltip'),
-  };
-
   const clearIndexCache = useCallback(async () => {
     await CollectionService.flush();
   }, []);
@@ -348,7 +341,7 @@ const Collections = () => {
       formatter({ collection_name }) {
         return (
           <Link
-            to={`/databases/${database}/${collection_name}/data`}
+            to={`/databases/${database}/${collection_name}/info`}
             className={classes.link}
             title={collection_name}
           >
@@ -373,7 +366,7 @@ const Collections = () => {
           <StatusAction
             status={v.status}
             percentage={v.loadedPercentage}
-            field={v.schema}
+            schema={v.schema}
             collectionName={v.collection_name}
             action={() => {
               setDialog({
@@ -397,56 +390,6 @@ const Collections = () => {
         );
       },
     },
-    {
-      id: 'collection_name',
-      align: 'left',
-      disablePadding: true,
-      notSort: true,
-      label: collectionTrans('features'),
-      formatter(v) {
-        return (
-          <>
-            {v.autoID ? (
-              <Tooltip
-                title={collectionTrans('autoIDTooltip')}
-                placement="top"
-                arrow
-              >
-                <Chip
-                  className={classes.chip}
-                  label={collectionTrans('autoID')}
-                  size="small"
-                />
-              </Tooltip>
-            ) : null}
-            {v.schema && v.schema.enable_dynamic_field ? (
-              <Tooltip
-                title={collectionTrans('dynamicSchemaTooltip')}
-                placement="top"
-                arrow
-              >
-                <Chip
-                  className={classes.chip}
-                  label={collectionTrans('dynamicSchema')}
-                  size="small"
-                />
-              </Tooltip>
-            ) : null}
-            <Tooltip
-              title={consistencyTooltipsMap[v.consistency_level] || ''}
-              placement="top"
-              arrow
-            >
-              <Chip
-                className={classes.chip}
-                label={v.consistency_level}
-                size="small"
-              />
-            </Tooltip>
-          </>
-        );
-      },
-    },
     {
       id: 'rowCount',
       align: 'left',

+ 43 - 39
client/src/pages/collections/StatusAction.tsx

@@ -10,13 +10,12 @@ import {
   createStyles,
   Typography,
   useTheme,
-  Tooltip,
   Chip,
 } from '@material-ui/core';
 import { LOADING_STATE } from '@/consts';
 import StatusIcon from '@/components/status/StatusIcon';
 import icons from '@/components/icons/Icons';
-import IndexTypeElement from '@/pages/schema/IndexTypeElement';
+import CustomToolTip from '@/components/customToolTip/CustomToolTip';
 
 const useStyles = makeStyles((theme: Theme) =>
   createStyles({
@@ -31,17 +30,22 @@ const useStyles = makeStyles((theme: Theme) =>
       paddingLeft: theme.spacing(0.5),
     },
     circle: {
-      backgroundColor: (props: any) => props.color,
+      width: '8px',
+      height: '8px',
       borderRadius: '50%',
-      width: '10px',
-      height: '10px',
+      backgroundColor: theme.palette.primary.main,
     },
-
-    circleUnload: {
-      backgroundColor: theme.palette.attuGrey.main,
-      borderRadius: '50%',
-      width: '10px',
-      height: '10px',
+    loaded: {
+      border: `1px solid ${theme.palette.primary.main}`,
+      backgroundColor: theme.palette.primary.main,
+    },
+    unloaded: {
+      border: `1px solid ${theme.palette.primary.main}`,
+      background: '#fff !important',
+    },
+    noIndex: {
+      border: `1px solid ${theme.palette.attuGrey.light}`,
+      backgroundColor: '#fff',
     },
 
     loading: {
@@ -78,7 +82,7 @@ const StatusAction: FC<StatusActionType> = props => {
     status,
     percentage = 0,
     collectionName,
-    field,
+    schema,
     action = () => {},
   } = props;
   const { t: commonTrans } = useTranslation();
@@ -95,7 +99,7 @@ const StatusAction: FC<StatusActionType> = props => {
       case LOADING_STATE.UNLOADED:
         return {
           label: statusTrans.unloaded,
-          icon: <div className={`${classes.circleUnload}`}></div>,
+          icon: <div className={`${classes.circle} ${classes.unloaded}`}></div>,
           tooltip: collectionTrans('clickToLoad'),
           deleteIcon: <LoadIcon />,
         };
@@ -103,7 +107,7 @@ const StatusAction: FC<StatusActionType> = props => {
       case LOADING_STATE.LOADED:
         return {
           label: statusTrans.loaded,
-          icon: <div className={classes.circle}></div>,
+          icon: <div className={`${classes.circle} ${classes.loaded}`}></div>,
           tooltip: collectionTrans('clickToRelease'),
           deleteIcon: <ReleaseIcon />,
         };
@@ -129,33 +133,33 @@ const StatusAction: FC<StatusActionType> = props => {
     }
   }, [status, statusTrans, percentage]);
 
-  // UI state
-  const collectionLoaded = status === LOADING_STATE.LOADED;
+  const noIndex = !schema.hasVectorIndex;
+  const noVectorIndexTooltip = collectionTrans('noVectorIndexTooltip');
+  const noIndexIcon = (
+    <div className={`${classes.circle} ${classes.noIndex}`}></div>
+  );
+
   return (
     <div className={classes.root}>
-      {field.hasVectorIndex && (
-        <Tooltip arrow interactive title={tooltip} placement={'top'}>
-          <Chip
-            className={classes.chip}
-            variant="outlined"
-            label={<Typography>{label}</Typography>}
-            onDelete={() => action()}
-            onClick={(e: MouseEvent<HTMLDivElement>) => {
-              e.stopPropagation();
-              action();
-            }}
-            deleteIcon={deleteIcon}
-            size="small"
-            icon={icon}
-          />
-        </Tooltip>
-      )}
-      <IndexTypeElement
-        field={field.vectorFields[0]!}
-        collectionName={collectionName}
-        disabled={collectionLoaded}
-        disabledTooltip={collectionTrans('releaseCollectionFirst')}
-      />
+      <CustomToolTip
+        title={noIndex ? noVectorIndexTooltip : tooltip}
+        placement={'top'}
+      >
+        <Chip
+          className={classes.chip}
+          variant="outlined"
+          label={<Typography>{label}</Typography>}
+          onDelete={() => action()}
+          onClick={(e: MouseEvent<HTMLDivElement>) => {
+            e.stopPropagation();
+            action();
+          }}
+          disabled={noIndex}
+          deleteIcon={deleteIcon}
+          size="small"
+          icon={noIndex ? noIndexIcon : icon}
+        />
+      </CustomToolTip>
     </div>
   );
 };

+ 8 - 6
client/src/pages/databases/Databases.tsx

@@ -17,7 +17,6 @@ import Collections from '../collections/Collections';
 const useStyles = makeStyles((theme: Theme) => ({
   wrapper: {
     flexDirection: 'row',
-    gap: theme.spacing(2),
   },
   tree: {
     boxShadow: 'none',
@@ -25,13 +24,16 @@ const useStyles = makeStyles((theme: Theme) => ({
     width: theme.spacing(28),
     flexGrow: 0,
     flexShrink: 0,
+    height: 'calc(100vh - 96px)',
     overflow: 'auto',
     boxSizing: 'border-box',
+    padding: theme.spacing(0, 2, 0, 0),
   },
   tab: {
     flexGrow: 1,
     flexShrink: 1,
     overflowX: 'auto',
+    padding: theme.spacing(0, 2),
   },
 }));
 
@@ -65,16 +67,16 @@ const Databases = () => {
 
   // collection tabs
   const collectionTabs: ITab[] = [
+    {
+      label: collectionTrans('schemaTab'),
+      component: <Schema />,
+      path: `info`,
+    },
     {
       label: collectionTrans('dataTab'),
       component: <Query />,
       path: `data`,
     },
-    {
-      label: collectionTrans('schemaTab'),
-      component: <Schema />,
-      path: `schema`,
-    },
     {
       label: collectionTrans('partitionTab'),
       component: <Partitions />,

+ 89 - 3
client/src/pages/databases/tree/index.tsx

@@ -1,9 +1,11 @@
+import { useTranslation } from 'react-i18next';
 import TreeView from '@material-ui/lab/TreeView';
 import TreeItem from '@material-ui/lab/TreeItem';
 import icons from '@/components/icons/Icons';
-import { makeStyles, Theme } from '@material-ui/core';
+import { makeStyles, Theme, Tooltip } from '@material-ui/core';
 import { useNavigate, Params } from 'react-router-dom';
 import { CollectionObject } from '@server/types';
+import clcx from 'clsx';
 
 export type TreeNodeType = 'db' | 'collection' | 'partition' | 'segment';
 
@@ -13,6 +15,7 @@ export interface DatabaseTreeItem {
   name: string;
   type: TreeNodeType;
   expanded?: boolean;
+  data?: CollectionObject;
 }
 
 interface DatabaseToolProps {
@@ -37,6 +40,7 @@ const getExpanded = (nodes: DatabaseTreeItem[]) => {
 
 const useStyles = makeStyles((theme: Theme) => ({
   root: {
+    fontSize: '15px',
     '& .MuiTreeItem-iconContainer': {
       width: 'auto',
     },
@@ -50,6 +54,8 @@ const useStyles = makeStyles((theme: Theme) => ({
       backgroundColor: 'none',
     },
     '& .MuiTreeItem-content': {
+      width: 'auto',
+
       '&:hover': {
         backgroundColor: 'rgba(10, 206, 130, 0.08)',
       },
@@ -79,8 +85,87 @@ const useStyles = makeStyles((theme: Theme) => ({
       color: '#888',
     },
   },
+  collectionNode: {
+    display: 'flex',
+    justifyContent: 'space-between',
+    minHeight: '24px',
+    lineHeight: '24px',
+  },
+  right: {
+    display: 'flex',
+    alignItems: 'center',
+    width: 20,
+  },
+  count: {
+    fontSize: '13px',
+    fontWeight: 500,
+    marginLeft: theme.spacing(0.5),
+    color: theme.palette.attuGrey.main,
+  },
+  dot: {
+    width: '8px',
+    height: '8px',
+    borderRadius: '50%',
+    position: 'relative',
+    top: '0',
+  },
+  loaded: {
+    border: `1px solid ${theme.palette.primary.main}`,
+    backgroundColor: theme.palette.primary.main,
+  },
+  unloaded: {
+    border: `1px solid ${theme.palette.primary.main}`,
+    background: '#fff !important',
+  },
+  loading: {
+    border: `1px solid ${theme.palette.primary.light}`,
+    backgroundColor: `${theme.palette.primary.light} !important`,
+  },
+  noIndex: {
+    border: `1px solid ${theme.palette.attuGrey.light}`,
+    backgroundColor: theme.palette.attuGrey.light,
+  },
 }));
 
+const CollectionNode: React.FC<{ data: CollectionObject }> = ({ data }) => {
+  // i18n collectionTrans
+  const { t: commonTrans } = useTranslation();
+  const statusTrans = commonTrans('status');
+
+  // styles
+  const classes = useStyles();
+
+  // class
+  const loadClass = clcx(classes.dot, {
+    [classes.loaded]: data.loaded,
+    [classes.unloaded]: !data.loaded,
+    [classes.loading]: data.status === 'loading',
+    [classes.noIndex]: !data.schema || !data.schema.hasVectorIndex,
+  });
+
+  //  status tooltip
+  const hasIndex = data.schema && data.schema.hasVectorIndex;
+  const loadStatus = hasIndex
+    ? data.loaded
+      ? statusTrans.loaded
+      : statusTrans.unloaded
+    : statusTrans.noVectorIndex;
+
+  return (
+    <div className={classes.collectionNode}>
+      <div>
+        {data.collection_name}
+        <span className={classes.count}>({data.rowCount})</span>
+      </div>
+      <div className={classes.right}>
+        <Tooltip title={loadStatus}>
+          <div className={loadClass}></div>
+        </Tooltip>
+      </div>
+    </div>
+  );
+};
+
 const DatabaseTree: React.FC<DatabaseToolProps> = props => {
   // props
   const { database, collections, params } = props;
@@ -91,6 +176,7 @@ const DatabaseTree: React.FC<DatabaseToolProps> = props => {
       id: `c_${c.collection_name}`,
       name: c.collection_name,
       type: 'collection' as TreeNodeType,
+      data: c,
     };
   });
 
@@ -117,7 +203,7 @@ const DatabaseTree: React.FC<DatabaseToolProps> = props => {
       node.type === 'db'
         ? `/databases/${database}/${params.databasePage || 'collections'}`
         : `/databases/${database}/${node.name}/${
-            params.collectionPage || 'data'
+            params.collectionPage || 'info'
           }`
     );
   };
@@ -150,7 +236,7 @@ const DatabaseTree: React.FC<DatabaseToolProps> = props => {
           key={node.id}
           nodeId={node.id}
           icon={<CollectionIcon />}
-          label={node.name}
+          label={<CollectionNode data={node.data!} />}
           className={classes.treeItem}
           onClick={event => {
             event.stopPropagation();

+ 1 - 1
client/src/pages/dialogs/LoadCollectionDialog.tsx

@@ -181,7 +181,7 @@ const LoadCollectionDialog = (props: any) => {
           <Typography variant="body1" component="p" className={classes.desc}>
             {collectionTrans('loadContent')}
           </Typography>
-          {!enableRelica ? (
+          {enableRelica ? (
             <>
               <FormControlLabel
                 control={

+ 166 - 43
client/src/pages/schema/Schema.tsx

@@ -1,25 +1,55 @@
-import { makeStyles, Theme, Typography, Chip } from '@material-ui/core';
+import {
+  makeStyles,
+  Theme,
+  Typography,
+  Chip,
+  Tooltip,
+} from '@material-ui/core';
 import { useContext } from 'react';
 import { useParams } from 'react-router-dom';
 import AttuGrid from '@/components/grid/Grid';
 import { ColDefinitionsType } from '@/components/grid/Types';
 import { useTranslation } from 'react-i18next';
-import { usePaginationHook } from '@/hooks';
 import icons from '@/components/icons/Icons';
 import { formatFieldType } from '@/utils';
-import { dataContext } from '@/context';
+import { rootContext, dataContext } from '@/context';
 import IndexTypeElement from './IndexTypeElement';
 import { getLabelDisplayedRows } from '../search/Utils';
+import { LOADING_STATE } from '@/consts';
+import LoadCollectionDialog from '@/pages/dialogs/LoadCollectionDialog';
+import ReleaseCollectionDialog from '@/pages/dialogs/ReleaseCollectionDialog';
+import StatusAction from '@/pages/collections/StatusAction';
 
 const useStyles = makeStyles((theme: Theme) => ({
   wrapper: {
-    height: `calc(100vh - 160px)`,
+    display: 'flex',
+    flexDirection: 'column',
+    flexGrow: 1,
+    height: `100%`,
+    overflow: 'auto',
+    '& h5': {
+      color: theme.palette.attuGrey.dark,
+      marginBottom: theme.spacing(0.5),
+      fontSize: '14px',
+      fontWeight: 400,
+    },
+  },
+  infoWrapper: {
+    marginBottom: theme.spacing(2),
+    paddingTop: theme.spacing(0.5),
+  },
+  block: {
+    '& *': {
+      fontSize: '14px',
+      lineHeight: 1.5,
+    },
+    paddingBottom: theme.spacing(2),
   },
   icon: {
     fontSize: '20px',
     marginLeft: theme.spacing(0.5),
   },
-  iconTitle: {
+  primaryKeyChip: {
     fontSize: '8px',
     position: 'relative',
     top: '3px',
@@ -28,6 +58,13 @@ const useStyles = makeStyles((theme: Theme) => ({
   chip: {
     marginLeft: theme.spacing(0.5),
     marginRight: theme.spacing(0.5),
+    fontSize: '12px',
+    background: 'rgba(0, 0, 0, 0.04)',
+    border: 'none',
+  },
+  featureChip: {
+    marginLeft: 0,
+    border: 'none',
   },
   nameWrapper: {
     display: 'flex',
@@ -58,9 +95,14 @@ const useStyles = makeStyles((theme: Theme) => ({
       },
     },
   },
+
+  gridWrapper: {
+    paddingBottom: theme.spacing(2),
+  },
 }));
 
 const Schema = () => {
+  const { setDialog } = useContext(rootContext);
   const { fetchCollection, collections, loading } = useContext(dataContext);
   const { collectionName = '' } = useParams<{ collectionName: string }>();
   const classes = useStyles();
@@ -69,27 +111,23 @@ const Schema = () => {
   const { t: commonTrans } = useTranslation();
   const gridTrans = commonTrans('grid');
 
+  const consistencyTooltipsMap: Record<string, string> = {
+    Strong: collectionTrans('consistencyStrongTooltip'),
+    Bounded: collectionTrans('consistencyBoundedTooltip'),
+    Session: collectionTrans('consistencySessionTooltip'),
+    Eventually: collectionTrans('consistencyEventuallyTooltip'),
+  };
+
   // get collection
   const collection = collections.find(
     c => c.collection_name === collectionName
   );
 
   // get fields
-  const fileds = collection?.schema?.fields || [];
+  const fields = collection?.schema?.fields || [];
 
   const KeyIcon = icons.key;
-
-  const {
-    pageSize,
-    handlePageSize,
-    currentPage,
-    handleCurrentPage,
-    total,
-    data: schemaList,
-    order,
-    orderBy,
-    handleGridSort,
-  } = usePaginationHook(fileds);
+  const EnabledIcon = icons.check;
 
   const colDefinitions: ColDefinitionsType[] = [
     {
@@ -102,7 +140,7 @@ const Schema = () => {
             {f.name}
             {f.is_primary_key ? (
               <div
-                className={classes.iconTitle}
+                className={classes.primaryKeyChip}
                 title={collectionTrans('idFieldName')}
               >
                 <KeyIcon classes={{ root: 'key' }} />
@@ -209,32 +247,117 @@ const Schema = () => {
     },
   ];
 
-  const handlePageChange = (e: any, page: number) => {
-    handleCurrentPage(page);
-  };
-
+  // get loading state label
   return (
     <section className={classes.wrapper}>
-      <AttuGrid
-        toolbarConfigs={[]}
-        colDefinitions={colDefinitions}
-        rows={schemaList}
-        rowCount={total}
-        primaryKey="fieldID"
-        showHoverStyle={false}
-        page={currentPage}
-        onPageChange={handlePageChange}
-        rowsPerPage={pageSize}
-        setRowsPerPage={handlePageSize}
-        isLoading={loading}
-        openCheckBox={false}
-        order={order}
-        orderBy={orderBy}
-        handleSort={handleGridSort}
-        labelDisplayedRows={getLabelDisplayedRows(
-          gridTrans[schemaList.length > 1 ? 'fields' : 'field']
-        )}
-      />
+      {collection && (
+        <section className={classes.infoWrapper}>
+          <div className={classes.block}>
+            <Typography variant="h5">{collectionTrans('status')}</Typography>
+            <StatusAction
+              status={collection.status}
+              percentage={collection.loadedPercentage}
+              schema={collection.schema!}
+              collectionName={collection.collection_name}
+              action={() => {
+                setDialog({
+                  open: true,
+                  type: 'custom',
+                  params: {
+                    component:
+                      collection.status === LOADING_STATE.UNLOADED ? (
+                        <LoadCollectionDialog
+                          collectionName={collection.collection_name}
+                        />
+                      ) : (
+                        <ReleaseCollectionDialog
+                          collectionName={collection.collection_name}
+                        />
+                      ),
+                  },
+                });
+              }}
+            />
+          </div>
+
+          <div className={classes.block}>
+            <Typography variant="h5">
+              {collectionTrans('description')}
+            </Typography>
+            <Typography variant="h6">
+              {collection?.description || '--'}
+            </Typography>
+          </div>
+
+          <div className={classes.block}>
+            <Typography variant="h5">
+              {collectionTrans('consistency')}
+            </Typography>
+            <Typography variant="h6">
+              <Tooltip
+                title={
+                  consistencyTooltipsMap[collection.consistency_level!] || ''
+                }
+                placement="top"
+                arrow
+              >
+                <Chip
+                  className={`${classes.chip} ${classes.featureChip}`}
+                  label={collection.consistency_level}
+                  variant="outlined"
+                  size="small"
+                />
+              </Tooltip>
+            </Typography>
+          </div>
+
+          <div className={classes.block}>
+            <Typography variant="h5">
+              {collectionTrans('createdTime')}
+            </Typography>
+            <Typography variant="h6">
+              {new Date(collection.createdTime).toLocaleString()}
+            </Typography>
+          </div>
+        </section>
+      )}
+
+      <section className={classes.gridWrapper}>
+        <Typography variant="h5">
+          {collectionTrans('schema')}
+          {collection &&
+          collection.schema &&
+          collection.schema.enable_dynamic_field ? (
+            <Tooltip
+              title={collectionTrans('dynamicSchemaTooltip')}
+              placement="top"
+              arrow
+            >
+              <Chip
+                className={`${classes.chip}`}
+                label={collectionTrans('dynamicSchema')}
+                size="small"
+                icon={<EnabledIcon />}
+              />
+            </Tooltip>
+          ) : null}
+        </Typography>
+
+        <AttuGrid
+          toolbarConfigs={[]}
+          colDefinitions={colDefinitions}
+          rows={fields}
+          rowCount={fields.length}
+          primaryKey="fieldID"
+          showHoverStyle={false}
+          isLoading={loading}
+          openCheckBox={false}
+          showPagination={false}
+          labelDisplayedRows={getLabelDisplayedRows(
+            gridTrans[fields.length > 1 ? 'fields' : 'field']
+          )}
+        />
+      </section>
     </section>
   );
 };

+ 1 - 1
client/src/styles/theme.ts

@@ -42,7 +42,7 @@ const commonThemes = {
   palette: {
     primary: {
       main: '#0ACE82',
-      light: '#bfdeff',
+      light: '#65BA74',
       dark: '#08a568',
     },
     secondary: {

+ 1 - 1
server/src/collections/collections.service.ts

@@ -438,7 +438,7 @@ export class CollectionsService {
         v => v.name === collection.name
       );
 
-      const notLazy = !!loadedCollection || i < 5; // lazy is true, only load full details for the first 10 collections
+      const notLazy = i <= 5; // lazy is true, only load full details for the first 10 collections
 
       data.push(
         await this.getCollection(