Browse Source

improve routers for collections and users page (#377)

Signed-off-by: ryjiang <jiangruiyi@gmail.com>
ryjiang 1 year ago
parent
commit
e952f2682f

+ 116 - 0
client/src/components/customTabList/RouteTabList.tsx

@@ -0,0 +1,116 @@
+import { Box, makeStyles, Tab, Tabs, Theme } from '@material-ui/core';
+import { FC, useState } from 'react';
+import { useNavigate, useLocation } from 'react-router-dom';
+import { ITabListProps, ITabPanel } from './Types';
+
+const useStyles = makeStyles((theme: Theme) => ({
+  wrapper: {
+    display: 'flex',
+    flexDirection: 'column',
+    flexBasis: 0,
+    flexGrow: 1,
+    '& .MuiTab-wrapper': {
+      textTransform: 'capitalize',
+      fontWeight: 'bold',
+      color: '#323232',
+    },
+  },
+  tab: {
+    height: theme.spacing(0.5),
+    backgroundColor: theme.palette.primary.main,
+  },
+  tabContainer: {
+    borderBottom: '1px solid #e9e9ed',
+  },
+  tabContent: {
+    minWidth: 0,
+    marginRight: theme.spacing(3),
+  },
+  tabPanel: {
+    flexBasis: 0,
+    flexGrow: 1,
+    marginTop: theme.spacing(2),
+    overflow: 'hidden',
+  },
+}));
+
+const TabPanel = (props: ITabPanel) => {
+  const { children, value, index, className = '', ...other } = props;
+
+  return (
+    <div
+      role="tabpanel"
+      hidden={value !== index}
+      className={className}
+      id={`tabpanel-${index}`}
+      aria-labelledby={`tabpanel-${index}`}
+      {...other}
+    >
+      {value === index && <Box height="100%">{children}</Box>}
+    </div>
+  );
+};
+
+const a11yProps = (index: number) => {
+  return {
+    id: `tab-${index}`,
+    'aria-controls': `tabpanel-${index}`,
+  };
+};
+
+const RouteTabList: FC<ITabListProps> = props => {
+  const { tabs, activeIndex = 0, wrapperClass = '' } = props;
+  const classes = useStyles();
+  const [value, setValue] = useState<number>(activeIndex);
+  const navigate = useNavigate();
+  const location = useLocation();
+
+  const handleChange = (event: any, newValue: any) => {
+    setValue(newValue);
+    const newPath =
+      location.pathname.split('/').slice(0, -1).join('/') +
+      '/' +
+      tabs[newValue].path;
+
+    navigate(`${newPath}`);
+  };
+
+  return (
+    <div className={`${classes.wrapper}  ${wrapperClass}`}>
+      <Tabs
+        classes={{
+          indicator: classes.tab,
+          flexContainer: classes.tabContainer,
+        }}
+        // if not provide this property, Material will add single span element by default
+        TabIndicatorProps={{ children: <div className="tab-indicator" /> }}
+        value={value}
+        onChange={handleChange}
+        aria-label="tabs"
+      >
+        {tabs.map((tab, index) => (
+          <Tab
+            classes={{ root: classes.tabContent }}
+            textColor="primary"
+            key={tab.label}
+            label={tab.label}
+            {...a11yProps(index)}
+          ></Tab>
+        ))}
+      </Tabs>
+
+      {tabs.map((tab, index) => (
+        <TabPanel
+          key={tab.label}
+          value={value}
+          index={index}
+          className={classes.tabPanel}
+        >
+          {tab.component}
+        </TabPanel>
+      ))}
+    </div>
+  );
+};
+
+export default RouteTabList;

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

@@ -3,12 +3,13 @@ import { ReactElement } from 'react';
 export interface ITab {
   label: string;
   component: ReactElement;
+  path?: string;
 }
 
 export interface ITabListProps {
   tabs: ITab[];
   activeIndex?: number;
-  handleTabChange?: (index: number) => void;
+  handleTabChange?:(index:number) => void;
   wrapperClass?: string;
 }
 

+ 19 - 29
client/src/pages/collections/Collection.tsx

@@ -1,19 +1,17 @@
-import { useMemo, useContext } from 'react';
-import { useNavigate, useLocation, useParams } from 'react-router-dom';
+import { useContext } from 'react';
+import { useParams } from 'react-router-dom';
 import { useTranslation } from 'react-i18next';
 import { makeStyles, Theme } from '@material-ui/core';
 import { authContext } from '@/context';
 import { useNavigationHook } from '@/hooks';
 import { ALL_ROUTER_TYPES } from '@/router/Types';
-import CustomTabList from '@/components/customTabList/CustomTabList';
+import RouteTabList from '@/components/customTabList/RouteTabList';
 import { ITab } from '@/components/customTabList/Types';
 import Partitions from '../partitions/Partitions';
-import { parseLocationSearch } from '@/utils';
 import Schema from '../schema/Schema';
 import Query from '../query/Query';
 import Preview from '../preview/Preview';
 import Segments from '../segments/Segments';
-import { TAB_ENUM } from './Types';
 
 const useStyles = makeStyles((theme: Theme) => ({
   wrapper: {
@@ -38,49 +36,40 @@ const Collection = () => {
   const classes = useStyles();
   const { isManaged } = useContext(authContext);
 
-  const { collectionName = '' } = useParams<{
+  const { collectionName = '', tab = '' } = useParams<{
     collectionName: string;
+    tab: string;
   }>();
 
   useNavigationHook(ALL_ROUTER_TYPES.COLLECTION_DETAIL, { collectionName });
 
-  const navigate = useNavigate();
-  const location = useLocation();
-
   const { t: collectionTrans } = useTranslation('collection');
 
-  const activeTabIndex = useMemo(() => {
-    const { activeIndex } = location.search
-      ? parseLocationSearch(location.search)
-      : { activeIndex: TAB_ENUM.schema };
-    return Number(activeIndex);
-  }, [location]);
-
-  const handleTabChange = (activeIndex: number) => {
-    const path = location.pathname;
-    navigate(`${path}?activeIndex=${activeIndex}`);
-  };
-
   const tabs: ITab[] = [
     {
       label: collectionTrans('schemaTab'),
-      component: <Schema collectionName={collectionName} />,
+      component: <Schema />,
+      path: `schema`,
     },
     {
       label: collectionTrans('partitionTab'),
-      component: <Partitions collectionName={collectionName} />,
+      component: <Partitions />,
+      path: `partitions`,
     },
     {
       label: collectionTrans('previewTab'),
-      component: <Preview collectionName={collectionName} />,
+      component: <Preview />,
+      path: `preview`,
     },
     {
       label: collectionTrans('queryTab'),
-      component: <Query collectionName={collectionName} />,
+      component: <Query />,
+      path: `query`,
     },
     {
       label: collectionTrans('segmentsTab'),
-      component: <Segments collectionName={collectionName} />,
+      component: <Segments />,
+      path: `segments`,
     },
   ];
 
@@ -89,13 +78,14 @@ const Collection = () => {
     tabs.splice(1, 1);
   }
 
+  const activeTab = tabs.findIndex(t => t.path === tab);
+
   return (
     <section className={`page-wrapper ${classes.wrapper}`}>
-      <CustomTabList
+      <RouteTabList
         tabs={tabs}
         wrapperClass={classes.tab}
-        activeIndex={activeTabIndex}
-        handleTabChange={handleTabChange}
+        activeIndex={activeTab !== -1 ? activeTab : 0}
       />
     </section>
   );

+ 1 - 1
client/src/pages/collections/Collections.tsx

@@ -151,7 +151,7 @@ const Collections = () => {
       Object.assign(v, {
         nameElement: (
           <Link
-            to={`/collections/${v.collectionName}`}
+            to={`/collections/${v.collectionName}/schema`}
             className={classes.link}
             title={v.collectionName}
           >

+ 1 - 1
client/src/pages/overview/collectionCard/CollectionCard.tsx

@@ -154,7 +154,7 @@ const CollectionCard: FC<CollectionCardProps> = ({
         <div>
           <Status status={status} percentage={loadedPercentage} />
         </div>
-        <Link className="link" to={`/collections/${collectionName}`}>
+        <Link className="link" to={`/collections/${collectionName}/schema`}>
           {collectionName}
           <RightArrowIcon classes={{ root: classes.icon }} />
         </Link>

+ 6 - 9
client/src/pages/partitions/Partitions.tsx

@@ -1,6 +1,6 @@
 import { makeStyles, Theme } from '@material-ui/core';
-import { FC, useContext, useEffect, useState } from 'react';
-import { useSearchParams } from 'react-router-dom';
+import { useContext, useEffect, useState } from 'react';
+import { useSearchParams, useParams } from 'react-router-dom';
 import Highlighter from 'react-highlight-words';
 import AttuGrid from '@/components/grid/Grid';
 import { ColDefinitionsType, ToolBarConfig } from '@/components/grid/Types';
@@ -31,11 +31,9 @@ const useStyles = makeStyles((theme: Theme) => ({
 }));
 
 let timer: NodeJS.Timeout | null = null;
-// get init search value from url
-// const { search = '' } = parseLocationSearch(window.location.search);
-const Partitions: FC<{
-  collectionName: string;
-}> = ({ collectionName }) => {
+
+const Partitions = () => {
+  const { collectionName = '' } = useParams<{ collectionName: string }>();
   const classes = useStyles();
   const { t } = useTranslation('partition');
   const { t: successTrans } = useTranslation('success');
@@ -67,8 +65,7 @@ const Partitions: FC<{
     handleGridSort,
   } = usePaginationHook(searchedPartitions);
   const [loading, setLoading] = useState<boolean>(true);
-  const { setDialog, handleCloseDialog, openSnackBar } =
-    useContext(rootContext);
+  const { setDialog, openSnackBar } = useContext(rootContext);
 
   const fetchPartitions = async (collectionName: string) => {
     try {

+ 5 - 5
client/src/pages/preview/Preview.tsx

@@ -1,6 +1,7 @@
-import { FC, useEffect, useState } from 'react';
+import { useEffect, useState } from 'react';
 import { useTranslation } from 'react-i18next';
 import AttuGrid from '@/components/grid/Grid';
+import { useParams } from 'react-router-dom';
 import { Collection, MilvusIndex } from '@/http';
 import { usePaginationHook, useSearchResult } from '@/hooks';
 import { generateVector } from '@/utils';
@@ -15,9 +16,8 @@ import { ToolBarConfig } from '@/components/grid/Types';
 import CustomToolBar from '@/components/grid/ToolBar';
 import { getQueryStyles } from '../query/Styles';
 
-const Preview: FC<{
-  collectionName: string;
-}> = ({ collectionName }) => {
+const Preview = () => {
+  const { collectionName } = useParams<{ collectionName: string }>();
   const [fields, setFields] = useState<any[]>([]);
   const [tableLoading, setTableLoading] = useState<any>();
   const [queryResult, setQueryResult] = useState<any>();
@@ -140,7 +140,7 @@ const Preview: FC<{
       type: 'button',
       btnVariant: 'text',
       onClick: () => {
-        loadData(collectionName);
+        loadData(collectionName!);
       },
       label: btnTrans('refresh'),
     },

+ 6 - 6
client/src/pages/query/Query.tsx

@@ -1,6 +1,7 @@
-import { FC, useEffect, useState, useRef, useContext } from 'react';
+import { useEffect, useState, useRef, useContext } from 'react';
 import { TextField } from '@material-ui/core';
 import { useTranslation } from 'react-i18next';
+import { useParams } from 'react-router-dom';
 import { rootContext } from '@/context';
 import { Collection, DataService } from '@/http';
 import { usePaginationHook, useSearchResult } from '@/hooks';
@@ -22,9 +23,8 @@ import {
 } from '@/consts';
 import CustomSelector from '@/components/customSelector/CustomSelector';
 
-const Query: FC<{
-  collectionName: string;
-}> = ({ collectionName }) => {
+const Query = () => {
+  const { collectionName } = useParams<{ collectionName: string }>();
   const [fields, setFields] = useState<any[]>([]);
   const [expression, setExpression] = useState('');
   const [tableLoading, setTableLoading] = useState<any>();
@@ -122,7 +122,7 @@ const Query: FC<{
       return;
     }
     try {
-      const res = await Collection.queryData(collectionName, {
+      const res = await Collection.queryData(collectionName!, {
         expr: expr,
         output_fields: fields.map(i => i.name),
         offset: 0,
@@ -145,7 +145,7 @@ const Query: FC<{
   };
 
   const handleDelete = async () => {
-    await DataService.deleteEntities(collectionName, {
+    await DataService.deleteEntities(collectionName!, {
       expr: `${primaryKey.value} in [${selectedData
         .map(v =>
           primaryKey.type === DataTypeStringEnum.VarChar

+ 5 - 5
client/src/pages/schema/Schema.tsx

@@ -1,5 +1,6 @@
 import { makeStyles, Theme, Typography, Chip } from '@material-ui/core';
-import { FC, useCallback, useEffect, useState } from 'react';
+import { useCallback, useEffect, useState } 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';
@@ -58,9 +59,8 @@ const useStyles = makeStyles((theme: Theme) => ({
   },
 }));
 
-const Schema: FC<{
-  collectionName: string;
-}> = ({ collectionName }) => {
+const Schema = () => {
+  const { collectionName = '' } = useParams<{ collectionName: string }>();
   const classes = useStyles();
   const { t: collectionTrans } = useTranslation('collection');
   const { t: indexTrans } = useTranslation('index');
@@ -187,7 +187,7 @@ const Schema: FC<{
       id: 'indexName',
       align: 'left',
       disablePadding: true,
-      label: indexTrans('indexName')
+      label: indexTrans('indexName'),
     },
     {
       id: '_indexTypeElement',

+ 5 - 5
client/src/pages/segments/Segments.tsx

@@ -1,5 +1,6 @@
-import { useEffect, useState, FC, useContext } from 'react';
+import { useEffect, useState, useContext } from 'react';
 import { useTranslation } from 'react-i18next';
+import { useParams } from 'react-router-dom';
 import { Segement } from '@/http';
 import { usePaginationHook } from '@/hooks';
 import { rootContext } from '@/context';
@@ -12,9 +13,8 @@ import FlushDialog from '@/pages/dialogs/FlushDialog';
 import { getQueryStyles } from '../query/Styles';
 import { Segment } from './Types';
 
-const Segments: FC<{
-  collectionName: string;
-}> = ({ collectionName }) => {
+const Segments = () => {
+  const { collectionName = '' } = useParams<{ collectionName: string }>();
   const classes = getQueryStyles();
   const { setDialog } = useContext(rootContext);
 
@@ -58,7 +58,7 @@ const Segments: FC<{
         fetchSegments();
       },
       label: btnTrans('refresh'),
-      icon: 'refresh'
+      icon: 'refresh',
     },
     {
       type: 'button',

+ 8 - 20
client/src/pages/user/Users.tsx

@@ -1,15 +1,12 @@
-import { useMemo } from 'react';
-import { useNavigate, useLocation } from 'react-router-dom';
+import { useLocation } from 'react-router-dom';
 import { useTranslation } from 'react-i18next';
 import { makeStyles, Theme } from '@material-ui/core';
 import { useNavigationHook } from '@/hooks';
 import { ALL_ROUTER_TYPES } from '@/router/Types';
-import CustomTabList from '@/components/customTabList/CustomTabList';
+import RouteTabList from '@/components/customTabList/RouteTabList';
 import { ITab } from '@/components/customTabList/Types';
-import { parseLocationSearch } from '@/utils';
 import User from './User';
 import Roles from './Roles';
-import { TAB_ENUM } from './Types';
 
 const useStyles = makeStyles((theme: Theme) => ({
   wrapper: {
@@ -34,41 +31,32 @@ const Users = () => {
   const classes = useStyles();
   useNavigationHook(ALL_ROUTER_TYPES.USER);
 
-  const navigate = useNavigate();
   const location = useLocation();
+  const currentPath = location.pathname.slice(1);
 
   const { t: userTrans } = useTranslation('user');
 
-  const activeTabIndex = useMemo(() => {
-    const { activeIndex } = location.search
-      ? parseLocationSearch(location.search)
-      : { activeIndex: TAB_ENUM.schema };
-    return Number(activeIndex);
-  }, [location]);
-
-  const handleTabChange = (activeIndex: number) => {
-    const path = location.pathname;
-    navigate(`${path}?activeIndex=${activeIndex}`);
-  };
-
   const tabs: ITab[] = [
     {
       label: userTrans('users'),
       component: <User />,
+      path: 'users',
     },
     {
       label: userTrans('roles'),
       component: <Roles />,
+      path: 'roles',
     },
   ];
 
+  const activeTabIndex = tabs.findIndex(t => t.path === currentPath);
+
   return (
     <section className={`page-wrapper ${classes.wrapper}`}>
-      <CustomTabList
+      <RouteTabList
         tabs={tabs}
         wrapperClass={classes.tab}
         activeIndex={activeTabIndex}
-        handleTabChange={handleTabChange}
       />
     </section>
   );

+ 6 - 0
client/src/router/Router.tsx

@@ -22,11 +22,17 @@ const RouterComponent = () => {
           <Route path="databases" element={<Database />} />
           <Route path="collections" element={<Collections />} />
           <Route path="collections/:collectionName" element={<Collection />} />
+          <Route
+            path="collections/:collectionName/:tab"
+            element={<Collection />}
+          />
+
           <Route path="search" element={<Search />} />
           <Route path="system_healthy" element={<SystemHealthy />} />
           {!isManaged && (
             <>
               <Route path="users" element={<Users />} />
+              <Route path="roles" element={<Users />} />
               <Route path="system" element={<System />} />
             </>
           )}