Browse Source

chore: remove makeStyle (#886)

* remove makeStyle for topology

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

* upgrade schema.tsx

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

* adjust schema

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

* finish update schema page

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

* update role edit dialog

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

* update privilegeGroup

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

---------

Signed-off-by: shanghaikid <jiangruiyi@gmail.com>
ryjiang 1 month ago
parent
commit
ab7bb22c68

+ 35 - 22
client/src/components/advancedSearch/CopyButton.tsx

@@ -1,16 +1,19 @@
 import React, { useState, useCallback } from 'react';
-import icons from '../icons/Icons';
-import CustomIconButton from '../customButton/CustomIconButton';
+import { IconButton, Tooltip } from '@mui/material';
+import ContentCopyIcon from '@mui/icons-material/ContentCopy';
 import { useTranslation } from 'react-i18next';
 import type { FC } from 'react';
-import type { CopyButtonProps } from './Types';
+import type { IconButtonProps } from '@mui/material';
 
-const CopyIcon = icons.copyExpression;
+interface CopyButtonProps extends Omit<IconButtonProps, 'value'> {
+  copyValue?: string | object;
+  tooltipPlacement?: 'top' | 'bottom' | 'left' | 'right';
+}
 
 const CopyButton: FC<CopyButtonProps> = props => {
-  const { label, icon, className, value = '', ...others } = props;
+  const { copyValue = '', tooltipPlacement = 'top', ...others } = props;
   const { t: commonTrans } = useTranslation();
-  const [tooltipTitle, setTooltipTitle] = useState('Copy');
+  const [tooltipTitle, setTooltipTitle] = useState(commonTrans('copy.copy'));
 
   const unsecuredCopyToClipboard = useCallback((v: string) => {
     const textArea = document.createElement('textarea');
@@ -30,34 +33,44 @@ const CopyButton: FC<CopyButtonProps> = props => {
   }, []);
 
   const handleClick = useCallback(
-    (event: React.MouseEvent<HTMLElement>, v: string) => {
+    (event: React.MouseEvent<HTMLElement>) => {
       event.stopPropagation();
-      if (typeof v === 'object') {
-        v = JSON.stringify(v);
-      }
+      let textToCopy =
+        typeof copyValue === 'object' ? JSON.stringify(copyValue) : copyValue;
       setTooltipTitle(commonTrans('copy.copied'));
+
       if (navigator.clipboard && navigator.clipboard.writeText) {
-        navigator.clipboard.writeText(v);
+        navigator.clipboard.writeText(textToCopy);
       } else {
-        unsecuredCopyToClipboard(v);
+        unsecuredCopyToClipboard(textToCopy);
       }
+
       setTimeout(() => {
         setTooltipTitle(commonTrans('copy.copy'));
       }, 1000);
     },
-    [commonTrans, unsecuredCopyToClipboard]
+    [commonTrans, unsecuredCopyToClipboard, copyValue]
   );
 
   return (
-    <CustomIconButton
-      tooltip={tooltipTitle}
-      aria-label={label}
-      className={className}
-      onClick={event => handleClick(event, value || '')}
-      {...others}
-    >
-      {icon || <CopyIcon />}
-    </CustomIconButton>
+    <Tooltip title={tooltipTitle} arrow placement={tooltipPlacement}>
+      <IconButton
+        size="small"
+        onClick={handleClick}
+        sx={{
+          '& svg': {
+            width: 12,
+            height: 12,
+          },
+          display: 'inline-flex',
+          alignItems: 'center',
+          ...others.sx,
+        }}
+        {...others}
+      >
+        <ContentCopyIcon />
+      </IconButton>
+    </Tooltip>
   );
 };
 

+ 39 - 25
client/src/components/customButton/RefreshButton.tsx

@@ -1,49 +1,63 @@
 import { useState } from 'react';
-import CustomIconButton from '@/components/customButton/CustomIconButton';
-import StatusIcon, { LoadingType } from '@/components/status/StatusIcon';
-import icons from '@/components/icons/Icons';
-import type { MouseEvent } from 'react';
+import { IconButton, Tooltip, CircularProgress } from '@mui/material';
+import RefreshIcon from '@mui/icons-material/Refresh';
+import type { MouseEvent, ReactNode } from 'react';
 import type { IconButtonProps } from '@mui/material';
 
-interface RefreshButtonProps extends IconButtonProps {
+interface RefreshButtonProps extends Omit<IconButtonProps, 'onClick'> {
   tooltip?: string;
-  icon?: React.ReactNode;
+  tooltipPlacement?: 'top' | 'bottom' | 'left' | 'right';
+  onClick?: (event: MouseEvent<HTMLButtonElement>) => Promise<void> | void;
+  icon?: ReactNode;
 }
 
 const RefreshButton = ({
   onClick,
+  tooltip,
+  tooltipPlacement = 'top',
   icon,
-  className,
   ...otherProps
 }: RefreshButtonProps) => {
   const [isLoading, setIsLoading] = useState(false);
-  const RefreshIcon = icons.refresh;
 
   const onBtnClicked = async (event: MouseEvent<HTMLButtonElement>) => {
     setIsLoading(true);
-    if (onClick) {
-      await onClick(event);
+    try {
+      if (onClick) {
+        await onClick(event);
+      }
+    } finally {
+      setIsLoading(false);
     }
-    setIsLoading(false);
   };
 
-  if (isLoading) {
-    return (
-      <span className={className} style={{ display: 'flex', width: 23 }}>
-        <StatusIcon type={LoadingType.CREATING} />
-      </span>
-    );
-  }
-
-  return (
-    <CustomIconButton
-      className={className}
-      {...otherProps}
+  const button = (
+    <IconButton
+      size="small"
       onClick={onBtnClicked}
       disabled={isLoading}
+      sx={{
+        '& svg': {
+          width: 16,
+          height: 16,
+        },
+        display: 'inline-flex',
+        alignItems: 'center',
+        justifyContent: 'center',
+        ...otherProps.sx,
+      }}
+      {...otherProps}
     >
-      {icon ?? <RefreshIcon />}
-    </CustomIconButton>
+      {isLoading ? <CircularProgress size={16} /> : icon || <RefreshIcon />}
+    </IconButton>
+  );
+
+  return tooltip ? (
+    <Tooltip title={tooltip} arrow placement={tooltipPlacement}>
+      <span>{button}</span>
+    </Tooltip>
+  ) : (
+    button
   );
 };
 

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

@@ -20,7 +20,7 @@ const collectionTrans = {
   // table
   id: 'ID',
   name: '名称',
-  features: 'Collection 特性',
+  features: '特性',
   nameTip: 'Collection 名称',
   status: '状态',
   desc: '描述',

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

@@ -20,7 +20,7 @@ const collectionTrans = {
   // table
   id: 'ID',
   name: 'Name',
-  features: 'Collection Features',
+  features: 'Features',
   nameTip: 'Collection Name',
   status: 'Status',
   desc: 'Description',

+ 308 - 275
client/src/pages/databases/collections/schema/Schema.tsx

@@ -1,4 +1,4 @@
-import { Typography, Chip, Tooltip } from '@mui/material';
+import { Typography, Tooltip, Box } from '@mui/material';
 import { useContext } from 'react';
 import { useParams, useNavigate } from 'react-router-dom';
 import AttuGrid from '@/components/grid/Grid';
@@ -11,8 +11,20 @@ import IndexTypeElement from './IndexTypeElement';
 import { getLabelDisplayedRows } from '@/pages/search/Utils';
 import StatusAction from '@/pages/databases/collections/StatusAction';
 import CustomToolTip from '@/components/customToolTip/CustomToolTip';
-import { useStyles } from './Styles';
-import CustomIconButton from '@/components/customButton/CustomIconButton';
+import {
+  Wrapper,
+  InfoWrapper,
+  Card,
+  InfoRow,
+  InfoLabel,
+  InfoValue,
+  ActionWrapper,
+  StyledChip,
+  DataTypeChip,
+  NameWrapper,
+  ParamWrapper,
+  GridWrapper,
+} from './Styles';
 import LoadCollectionDialog from '@/pages/dialogs/LoadCollectionDialog';
 import RenameCollectionDialog from '@/pages/dialogs/RenameCollectionDialog';
 import EditMmapDialog from '@/pages/dialogs/EditMmapDialog';
@@ -30,7 +42,6 @@ const Overview = () => {
 
   const { collectionName = '' } = useParams<{ collectionName: string }>();
   const navigate = useNavigate();
-  const classes = useStyles();
   const { t: collectionTrans } = useTranslation('collection');
   const { t: indexTrans } = useTranslation('index');
   const { t: btnTrans } = useTranslation('btn');
@@ -63,37 +74,21 @@ const Overview = () => {
       disablePadding: true,
       formatter(f: FieldObject) {
         return (
-          <div className={classes.nameWrapper}>
+          <NameWrapper>
             {f.name}
-            {f.is_primary_key ? (
-              <div
-                className={classes.primaryKeyChip}
-                title={collectionTrans('idFieldName')}
-              >
-                <Chip className={classes.chip} size="small" label="ID" />
-              </div>
-            ) : null}
-            {f.is_partition_key ? (
-              <Chip
-                className={classes.chip}
-                size="small"
-                label="Partition key"
-              />
-            ) : null}
-            {findKeyValue(f.type_params, 'enable_match') ? (
-              <Chip
-                className={classes.chip}
-                size="small"
-                label={collectionTrans('enableMatch')}
-              />
-            ) : null}
-            {findKeyValue(f.type_params, 'enable_analyzer') === 'true' ? (
+            {f.is_primary_key && <StyledChip size="small" label="ID" />}
+            {f.is_partition_key && (
+              <StyledChip size="small" label="Partition key" />
+            )}
+            {findKeyValue(f.type_params, 'enable_match') && (
+              <StyledChip size="small" label={collectionTrans('enableMatch')} />
+            )}
+            {findKeyValue(f.type_params, 'enable_analyzer') === 'true' && (
               <Tooltip
                 title={findKeyValue(f.type_params, 'analyzer_params') as string}
                 arrow
               >
-                <Chip
-                  className={classes.chip}
+                <StyledChip
                   size="small"
                   label={collectionTrans('analyzer')}
                   onClick={() => {
@@ -101,23 +96,15 @@ const Overview = () => {
                       f.type_params,
                       'analyzer_params'
                     );
-                    navigator.clipboard
-                      .writeText(textToCopy as string)
-                      .then(() => {
-                        alert('Copied to clipboard!');
-                      })
-                      .catch(err => {
-                        alert('Failed to copy: ' + err);
-                      });
+                    navigator.clipboard.writeText(textToCopy as string);
                   }}
                 />
               </Tooltip>
-            ) : null}
-            {findKeyValue(f.type_params, 'mmap.enabled') === 'true' ||
-            isCollectionMmapEnabled ? (
+            )}
+            {(findKeyValue(f.type_params, 'mmap.enabled') === 'true' ||
+              isCollectionMmapEnabled) && (
               <Tooltip title={collectionTrans('mmapTooltip')} arrow>
-                <Chip
-                  className={classes.chip}
+                <StyledChip
                   size="small"
                   label={collectionTrans('mmapEnabled')}
                   onClick={() => {
@@ -138,12 +125,10 @@ const Overview = () => {
                   }}
                 />
               </Tooltip>
-            ) : null}
-
-            {f.function ? (
+            )}
+            {f.function && (
               <Tooltip title={JSON.stringify(f.function)} arrow>
-                <Chip
-                  className={classes.chip}
+                <StyledChip
                   size="small"
                   label={`
                     ${
@@ -153,19 +138,12 @@ const Overview = () => {
                     }`}
                   onClick={() => {
                     const textToCopy = JSON.stringify(f.function);
-                    navigator.clipboard
-                      .writeText(textToCopy as string)
-                      .then(() => {
-                        alert('Copied to clipboard!');
-                      })
-                      .catch(err => {
-                        alert('Failed to copy: ' + err);
-                      });
+                    navigator.clipboard.writeText(textToCopy as string);
                   }}
                 />
               </Tooltip>
-            ) : null}
-          </div>
+            )}
+          </NameWrapper>
         );
       },
       label: collectionTrans('fieldName'),
@@ -176,13 +154,7 @@ const Overview = () => {
       align: 'left',
       disablePadding: false,
       formatter(f) {
-        return (
-          <Chip
-            className={`${classes.chip} ${classes.dataTypeChip}`}
-            size="small"
-            label={formatFieldType(f)}
-          />
-        );
+        return <DataTypeChip size="small" label={formatFieldType(f)} />;
       },
       label: collectionTrans('fieldType'),
     },
@@ -193,9 +165,9 @@ const Overview = () => {
       label: collectionTrans('nullable'),
       formatter(f) {
         return f.nullable ? (
-          <Icons.check className={classes.smallIcon} />
+          <Icons.check sx={{ fontSize: '11px', ml: 0.5 }} />
         ) : (
-          <Icons.cross2 className={classes.smallIcon} />
+          <Icons.cross2 sx={{ fontSize: '11px', ml: 0.5 }} />
         );
       },
     },
@@ -243,7 +215,7 @@ const Overview = () => {
       notSort: true,
       formatter(f) {
         return f.index ? (
-          <div className={classes.paramWrapper}>
+          <ParamWrapper>
             {f.index.indexParameterPairs.length > 0 ? (
               f.index.indexParameterPairs.map((p: any) =>
                 p.value ? (
@@ -264,7 +236,7 @@ const Overview = () => {
             ) : (
               <>--</>
             )}
-          </div>
+          </ParamWrapper>
         ) : (
           <>--</>
         );
@@ -315,133 +287,190 @@ const Overview = () => {
 
   // get loading state label
   return (
-    <section className={classes.wrapper}>
+    <Wrapper>
       {collection && (
-        <section className={classes.infoWrapper}>
-          <div className={classes.cardWrapper}>
-            <div className={classes.card}>
-              <Typography variant="h5">{collectionTrans('name')}</Typography>
-              <Typography variant="h6">
-                <p title={collection.collection_name}>
-                  {collection.collection_name}
-                </p>
-                <RefreshButton
-                  className={classes.extraBtn}
-                  onClick={async () => {
-                    setDialog({
-                      open: true,
-                      type: 'custom',
-                      params: {
-                        component: (
-                          <RenameCollectionDialog
-                            collection={collection}
-                            cb={async newName => {
-                              await fetchCollection(newName);
-
-                              // update collection name in the route url;
-                              navigate(
-                                `/databases/${database}/${newName}/schema`
-                              );
-                            }}
-                          />
-                        ),
+        <InfoWrapper>
+          <Card>
+            <InfoRow>
+              <InfoLabel>{collectionTrans('name')}</InfoLabel>
+              <InfoValue>
+                <Tooltip title={collection.collection_name} arrow>
+                  <Typography
+                    variant="body1"
+                    sx={{ fontWeight: 500 }}
+                    className="truncate"
+                  >
+                    {collection.collection_name}
+                  </Typography>
+                </Tooltip>
+                <ActionWrapper>
+                  <RefreshButton
+                    sx={{
+                      '& svg': {
+                        width: 16,
+                        height: 16,
                       },
-                    });
-                  }}
-                  tooltip={btnTrans('rename')}
-                  icon={<Icons.edit />}
-                />
-                <CopyButton
-                  className={classes.extraBtn}
-                  label={collection.collection_name}
-                  value={collection.collection_name}
-                />
-                <RefreshButton
-                  className={classes.extraBtn}
-                  onClick={async () => {
-                    const res =
-                      await CollectionService.describeCollectionUnformatted(
-                        collection.collection_name
-                      );
-
-                    // download json file
-                    const json = JSON.stringify(res, null, 2);
-                    const blob = new Blob([json], { type: 'application/json' });
-                    const url = URL.createObjectURL(blob);
-                    const a = document.createElement('a');
-                    a.href = url;
-                    a.download = `${collection.collection_name}.json`;
-                    a.click();
-                  }}
-                  tooltip={btnTrans('downloadSchema')}
-                  icon={<Icons.download />}
-                />
-                <CustomIconButton
-                  className={classes.extraBtn}
-                  tooltip={btnTrans('drop')}
-                  onClick={() => {
-                    setDialog({
-                      open: true,
-                      type: 'custom',
-                      params: {
-                        component: (
-                          <DropCollectionDialog
-                            collections={[collection]}
-                            onDelete={() => {
-                              navigate(`/databases/${database}`);
-                            }}
-                          />
-                        ),
+                    }}
+                    onClick={async () => {
+                      setDialog({
+                        open: true,
+                        type: 'custom',
+                        params: {
+                          component: (
+                            <RenameCollectionDialog
+                              collection={collection}
+                              cb={async newName => {
+                                await fetchCollection(newName);
+                                navigate(
+                                  `/databases/${database}/${newName}/schema`
+                                );
+                              }}
+                            />
+                          ),
+                        },
+                      });
+                    }}
+                    tooltip={btnTrans('rename')}
+                    icon={<Icons.edit />}
+                  />
+                  <CopyButton
+                    sx={{
+                      '& svg': {
+                        width: 16,
+                        height: 16,
                       },
-                    });
-                  }}
-                >
-                  <Icons.cross />
-                </CustomIconButton>
+                    }}
+                    copyValue={collection.collection_name}
+                  />
+                  <RefreshButton
+                    sx={{
+                      '& svg': {
+                        width: 16,
+                        height: 16,
+                      },
+                    }}
+                    onClick={async () => {
+                      const res =
+                        await CollectionService.describeCollectionUnformatted(
+                          collection.collection_name
+                        );
+                      const json = JSON.stringify(res, null, 2);
+                      const blob = new Blob([json], {
+                        type: 'application/json',
+                      });
+                      const url = URL.createObjectURL(blob);
+                      const a = document.createElement('a');
+                      a.href = url;
+                      a.download = `${collection.collection_name}.json`;
+                      a.click();
+                    }}
+                    tooltip={btnTrans('downloadSchema')}
+                    icon={<Icons.download />}
+                  />
+                  <RefreshButton
+                    sx={{
+                      '& svg': {
+                        width: 16,
+                        height: 16,
+                      },
+                    }}
+                    onClick={() => {
+                      setDialog({
+                        open: true,
+                        type: 'custom',
+                        params: {
+                          component: (
+                            <DropCollectionDialog
+                              collections={[collection]}
+                              onDelete={() => {
+                                navigate(`/databases/${database}`);
+                              }}
+                            />
+                          ),
+                        },
+                      });
+                    }}
+                    tooltip={btnTrans('drop')}
+                    icon={<Icons.cross />}
+                  />
+                  <RefreshButton
+                    sx={{
+                      '& svg': {
+                        width: 16,
+                        height: 16,
+                      },
+                    }}
+                    tooltip={btnTrans('refresh')}
+                    onClick={async () => {
+                      await fetchCollection(collectionName);
+                    }}
+                    icon={<Icons.refresh />}
+                  />
+                </ActionWrapper>
+              </InfoValue>
+            </InfoRow>
 
-                <RefreshButton
-                  className={classes.extraBtn}
-                  tooltip={btnTrans('refresh')}
-                  onClick={async () => {
-                    await fetchCollection(collectionName);
-                  }}
+            <InfoRow>
+              <InfoLabel>{collectionTrans('description')}</InfoLabel>
+              <InfoValue>
+                <Typography variant="body1">
+                  {collection?.description || '--'}
+                </Typography>
+              </InfoValue>
+            </InfoRow>
+
+            <InfoRow>
+              <InfoLabel>{collectionTrans('createdTime')}</InfoLabel>
+              <InfoValue>
+                <Typography variant="body1">
+                  {new Date(collection.createdTime).toLocaleString()}
+                </Typography>
+              </InfoValue>
+            </InfoRow>
+          </Card>
+
+          <Card>
+            <InfoRow>
+              <InfoLabel>{collectionTrans('status')}</InfoLabel>
+              <InfoValue>
+                <StatusAction
+                  status={collection.status}
+                  percentage={collection.loadedPercentage}
+                  collection={collection}
+                  showExtraAction={false}
+                  showLoadButton={true}
+                  createIndexElement={CreateIndexElement}
                 />
-              </Typography>
-              <Typography variant="h5">
-                {collectionTrans('description')}
-              </Typography>
-              <Typography variant="h6">
-                {collection?.description || '--'}
-              </Typography>
-              <Typography variant="h5">
-                {collectionTrans('createdTime')}
-              </Typography>
-              <Typography variant="h6">
-                {new Date(collection.createdTime).toLocaleString()}
-              </Typography>
-            </div>
+              </InfoValue>
+            </InfoRow>
 
-            <div className={classes.card}>
-              <Typography variant="h5">{collectionTrans('status')}</Typography>
-              <StatusAction
-                status={collection.status}
-                percentage={collection.loadedPercentage}
-                collection={collection}
-                showExtraAction={false}
-                showLoadButton={true}
-                createIndexElement={CreateIndexElement}
-              />
-              <Typography variant="h5">
+            <InfoRow>
+              <InfoLabel>
                 {collectionTrans('replica')}
                 <CustomToolTip title={collectionTrans('replicaTooltip')}>
-                  <Icons.question classes={{ root: classes.questionIcon }} />
+                  <Icons.question
+                    sx={{
+                      width: 12,
+                      height: 12,
+                      position: 'relative',
+                      top: '2px',
+                      right: '-4px',
+                    }}
+                  />
                 </CustomToolTip>
-              </Typography>
-              <Typography variant="h6">
-                {collection.loaded ? collection.replicas?.length : '...'}
+              </InfoLabel>
+              <InfoValue>
+                <Typography variant="body1">
+                  {collection.loaded ? collection.replicas?.length : '...'}
+                </Typography>
                 {collection.loaded && enableModifyReplica && (
-                  <CustomIconButton
-                    className={classes.extraBtn}
+                  <RefreshButton
+                    sx={{
+                      '& svg': {
+                        width: 12,
+                        height: 12,
+                      },
+                    }}
                     tooltip={collectionTrans('modifyReplicaTooltip')}
                     onClick={() => {
                       setDialog({
@@ -457,13 +486,14 @@ const Overview = () => {
                         },
                       });
                     }}
-                  >
-                    <Icons.settings />
-                  </CustomIconButton>
+                    icon={<Icons.settings />}
+                  />
                 )}
-              </Typography>
+              </InfoValue>
+            </InfoRow>
 
-              <Typography variant="h5">
+            <InfoRow>
+              <InfoLabel>
                 {collection.loaded ? (
                   collectionTrans('count')
                 ) : (
@@ -471,95 +501,98 @@ const Overview = () => {
                     {collectionTrans('rowCount')}
                     <CustomToolTip title={collectionTrans('entityCountInfo')}>
                       <Icons.question
-                        classes={{ root: classes.questionIcon }}
+                        sx={{
+                          width: 12,
+                          height: 12,
+                          position: 'relative',
+                          top: '2px',
+                          right: '-4px',
+                        }}
                       />
                     </CustomToolTip>
                   </>
                 )}
-              </Typography>
-              <Typography variant="h6">
-                {formatNumber(Number(collection?.rowCount || '0'))}
-              </Typography>
-            </div>
-            <div className={classes.card}>
-              <Typography variant="h5">
-                {collectionTrans('features')}
-              </Typography>
-              {isAutoIDEnabled ? (
-                <Chip
-                  className={`${classes.chip} ${classes.featureChip}`}
-                  label={collectionTrans('autoId')}
-                  size="small"
-                />
-              ) : null}
-              <Tooltip
-                title={
-                  consistencyTooltipsMap[collection.consistency_level!] || ''
-                }
-                placement="top"
-                arrow
-              >
-                <Chip
-                  className={`${classes.chip} ${classes.featureChip}`}
-                  label={`${collectionTrans('consistency')}: ${
-                    collection.consistency_level
-                  }`}
-                  size="small"
-                />
-              </Tooltip>
-
-              {collection &&
-              collection.schema &&
-              collection.schema.enable_dynamic_field ? (
-                <Tooltip
-                  title={collectionTrans('dynamicSchemaTooltip')}
-                  placement="top"
-                  arrow
-                >
-                  <Chip
-                    className={`${classes.chip}`}
-                    label={collectionTrans('dynamicSchema')}
-                    size="small"
-                  />
-                </Tooltip>
-              ) : null}
+              </InfoLabel>
+              <InfoValue>
+                <Typography variant="body1">
+                  {formatNumber(Number(collection?.rowCount || '0'))}
+                </Typography>
+              </InfoValue>
+            </InfoRow>
+          </Card>
 
-              <Tooltip
-                title={collectionTrans('mmapTooltip')}
-                placement="top"
-                arrow
-              >
-                <Chip
-                  className={classes.chip}
-                  label={collectionTrans('mmapSettings')}
-                  size="small"
-                  onDelete={async () => {
-                    setDialog({
-                      open: true,
-                      type: 'custom',
-                      params: {
-                        component: (
-                          <EditMmapDialog
-                            collection={collection}
-                            cb={async () => {
-                              fetchCollection(collectionName);
-                            }}
-                          />
-                        ),
-                      },
-                    });
-                  }}
-                  deleteIcon={<Icons.settings />}
-                />
-              </Tooltip>
-            </div>
-          </div>
-        </section>
+          <Card>
+            <InfoRow>
+              <InfoLabel>{collectionTrans('features')}</InfoLabel>
+              <InfoValue>
+                <Box className="features-wrapper">
+                  {isAutoIDEnabled && (
+                    <StyledChip
+                      sx={{ border: 'none' }}
+                      label={collectionTrans('autoId')}
+                      size="small"
+                    />
+                  )}
+                  <Tooltip
+                    title={
+                      collection.consistency_level
+                        ? consistencyTooltipsMap[
+                            collection.consistency_level
+                          ] || ''
+                        : ''
+                    }
+                    arrow
+                  >
+                    <StyledChip
+                      sx={{ border: 'none' }}
+                      label={`${collectionTrans('consistency')}: ${collection.consistency_level}`}
+                      size="small"
+                    />
+                  </Tooltip>
+                  {collection?.schema?.enable_dynamic_field && (
+                    <StyledChip
+                      label={collectionTrans('dynamicSchema')}
+                      size="small"
+                    />
+                  )}
+                  <Tooltip title={collectionTrans('mmapTooltip')} arrow>
+                    <StyledChip
+                      label={collectionTrans('mmapSettings')}
+                      size="small"
+                      onDelete={async () => {
+                        setDialog({
+                          open: true,
+                          type: 'custom',
+                          params: {
+                            component: (
+                              <EditMmapDialog
+                                collection={collection}
+                                cb={async () => {
+                                  fetchCollection(collectionName);
+                                }}
+                              />
+                            ),
+                          },
+                        });
+                      }}
+                      deleteIcon={
+                        <Icons.settings
+                          sx={{
+                            width: 12,
+                            height: 12,
+                          }}
+                        />
+                      }
+                    />
+                  </Tooltip>
+                </Box>
+              </InfoValue>
+            </InfoRow>
+          </Card>
+        </InfoWrapper>
       )}
 
-      <section className={classes.gridWrapper}>
-        {/* <Typography variant="h5">{collectionTrans('schema')}</Typography> */}
-
+      <GridWrapper>
         <AttuGrid
           toolbarConfigs={[]}
           colDefinitions={colDefinitions}
@@ -574,8 +607,8 @@ const Overview = () => {
             commonTrans(`grid.${fields.length > 1 ? 'fields' : 'field'}`)
           )}
         />
-      </section>
-    </section>
+      </GridWrapper>
+    </Wrapper>
   );
 };
 

+ 147 - 123
client/src/pages/databases/collections/schema/Styles.tsx

@@ -1,145 +1,169 @@
-import { makeStyles } from '@mui/styles';
-import { Theme } from '@mui/material';
+import { styled } from '@mui/material/styles';
+import { Chip, Box } from '@mui/material';
 
-export const useStyles = makeStyles((theme: Theme) => ({
-  wrapper: {
-    display: 'flex',
-    flexDirection: 'column',
-    flexGrow: 1,
-    height: `100%`,
-    overflow: 'auto',
-    '& h5': {
-      color: theme.palette.text.secondary,
-      marginBottom: theme.spacing(0.5),
-      fontSize: 13,
-      fontWeight: 400,
-    },
-    '& h6': {
-      fontSize: 14,
-      lineHeight: '20px',
-      display: 'flex',
-      color: theme.palette.text.primary,
-      marginBottom: theme.spacing(0.5),
+export const Wrapper = styled(Box)(({ theme }) => ({
+  display: 'flex',
+  flexDirection: 'column',
+  flexGrow: 1,
+  height: '100%',
+  overflow: 'auto',
+  gap: theme.spacing(2),
+  padding: theme.spacing(1, 0),
+  maxWidth: '100%',
+}));
 
-      '& p': {
-        margin: 0,
-        marginRight: 8,
-        textOverflow: 'ellipsis',
-        overflow: 'hidden',
-        maxWidth: 150,
-        fontWeight: 700,
-      },
-    },
+export const InfoWrapper = styled(Box)(({ theme }) => ({
+  display: 'grid',
+  gridTemplateColumns: '1.2fr 1fr 1fr',
+  gap: theme.spacing(2),
+  width: '100%',
+  [theme.breakpoints.down('md')]: {
+    gridTemplateColumns: '1fr',
   },
-  infoWrapper: {
-    marginBottom: theme.spacing(1.5),
-    paddingTop: theme.spacing(0.5),
+}));
+
+export const Card = styled(Box)(({ theme }) => ({
+  backgroundColor: theme.palette.background.default,
+  borderRadius: 8,
+  padding: theme.spacing(2),
+  boxSizing: 'border-box',
+  display: 'flex',
+  flexDirection: 'column',
+  gap: theme.spacing(2),
+  width: '100%',
+  minWidth: 0,
+  [theme.breakpoints.down('md')]: {
+    padding: theme.spacing(1.5),
+    gap: theme.spacing(1.5),
   },
-  cardWrapper: {
-    display: 'flex',
-    flexWrap: 'nowrap', // Changed from 'wrap' to 'nowrap'
-    overflowX: 'auto', // Add horizontal scrolling
-    '& > div:not(:last-child)': { marginRight: theme.spacing(1.5) },
-    height: 200,
-    // Optional: hide scrollbar for cleaner look (works in some browsers)
-    scrollbarWidth: 'none', // Firefox
-    '&::-webkit-scrollbar': {
-      // Chrome, Safari
-      display: 'none',
+}));
+
+export const InfoRow = styled(Box)(({ theme }) => ({
+  display: 'flex',
+  alignItems: 'flex-start',
+  gap: theme.spacing(2),
+  '&:not(:last-child)': {
+    paddingBottom: theme.spacing(1),
+  },
+  [theme.breakpoints.down('md')]: {
+    gap: theme.spacing(1),
+    '&:not(:last-child)': {
+      paddingBottom: theme.spacing(0.5),
     },
   },
-  card: {
-    backgroundColor: theme.palette.background.default,
-    borderRadius: 8,
-    padding: theme.spacing(1.5, 2),
-    boxSizing: 'border-box',
-    flexGrow: 0, // Changed from 1 to prevent stretching
-    flexShrink: 0, // Changed from 1 to prevent shrinking
-    flexBasis: 'auto', // Changed from calc
-    width: 'calc(33.333% - 8px)', // Use width instead of flex-basis
-    minWidth: 200, // Minimum width for each card
-    height: '100%',
-  },
-  icon: {
-    fontSize: '20px',
-    marginLeft: theme.spacing(0.5),
-  },
-  extraBtn: {
-    position: 'relative',
-    top: -6,
-    '& svg': {
-      width: 15,
-      color: theme.palette.text.primary,
-    },
+}));
+
+export const InfoLabel = styled(Box)(({ theme }) => ({
+  color: theme.palette.text.secondary,
+  fontSize: 13,
+  fontWeight: 400,
+  minWidth: 80,
+  flexShrink: 0,
+  paddingTop: '2px',
+  [theme.breakpoints.down('md')]: {
+    minWidth: 70,
+    fontSize: 12,
   },
-  mmapExtraBtn: {
-    position: 'relative',
-    top: -2,
-    '& svg': {
-      width: 15,
-      color: theme.palette.text.primary,
+}));
+
+export const InfoValue = styled(Box)(({ theme }) => ({
+  color: theme.palette.text.primary,
+  fontSize: 14,
+  fontWeight: 500,
+  display: 'flex',
+  alignItems: 'flex-start',
+  gap: theme.spacing(1),
+  flex: 1,
+  minWidth: 0,
+  flexWrap: 'nowrap',
+  [theme.breakpoints.down('md')]: {
+    fontSize: 13,
+    gap: theme.spacing(0.5),
+  },
+  '& .truncate': {
+    overflow: 'hidden',
+    textOverflow: 'ellipsis',
+    whiteSpace: 'nowrap',
+    maxWidth: '100%',
+  },
+  '& .features-wrapper': {
+    display: 'flex',
+    flexDirection: 'column',
+    gap: theme.spacing(1),
+    width: '100%',
+    alignItems: 'flex-start',
+    [theme.breakpoints.down('md')]: {
+      gap: theme.spacing(0.5),
     },
   },
-  smallIcon: {
-    fontSize: '13px',
-    marginLeft: theme.spacing(0.5),
-  },
+}));
 
-  questionIcon: {
-    width: 12,
-    position: 'relative',
-    top: '6px',
-    right: '-2px',
-  },
-  primaryKeyChip: {
-    fontSize: '8px',
+export const ActionWrapper = styled(Box)(({ theme }) => ({
+  display: 'flex',
+  alignItems: 'center',
+  gap: theme.spacing(0.5),
+  marginLeft: 'auto',
+  [theme.breakpoints.down('md')]: {
+    gap: theme.spacing(0.25),
   },
-  chip: {
-    fontSize: '12px',
-    color: theme.palette.text.primary,
-    cursor: 'normal',
-    marginRight: 4,
-    marginBottom: 4,
-    marginTop: 4,
+}));
+
+export const StyledChip = styled(Chip)(({ theme }) => ({
+  fontSize: '12px',
+  color: theme.palette.text.primary,
+  cursor: 'normal',
+  height: 24,
+  '& .MuiChip-label': {
+    padding: '0 8px',
+  },
+  [theme.breakpoints.down('md')]: {
+    height: 20,
+    fontSize: '11px',
+    '& .MuiChip-label': {
+      padding: '0 6px',
+    },
   },
-  dataTypeChip: {
-    backgroundColor: theme.palette.background.grey,
+}));
+
+export const DataTypeChip = styled(Chip)(({ theme }) => ({
+  backgroundColor: theme.palette.background.paper,
+  border: `1px solid ${theme.palette.divider}`,
+  color: theme.palette.text.secondary,
+  fontSize: '12px',
+  height: 24,
+  '& .MuiChip-label': {
+    padding: '0 8px',
   },
-  featureChip: {
-    border: 'none',
-    marginLeft: 0,
+}));
+
+export const NameWrapper = styled(Box)(({ theme }) => ({
+  display: 'flex',
+  alignItems: 'center',
+  gap: theme.spacing(0.5),
+  '& .key': {
+    width: '16px',
+    height: '16px',
   },
-  nameWrapper: {
+}));
+
+export const ParamWrapper = styled(Box)(({ theme }) => ({
+  display: 'flex',
+  flexWrap: 'wrap',
+  gap: theme.spacing(1),
+  '& .param': {
     display: 'flex',
     alignItems: 'center',
-
+    gap: theme.spacing(0.5),
     '& .key': {
-      width: '16px',
-      height: '16px',
-      marginLeft: theme.spacing(0.5),
+      color: theme.palette.text.secondary,
     },
-  },
-
-  paramWrapper: {
-    // set min width to prevent other table cell stretching
-    minWidth: 180,
-
-    '& .param': {
-      marginRight: theme.spacing(2),
-
-      '& .key': {
-        color: theme.palette.text.secondary,
-        display: 'inline-block',
-        marginRight: theme.spacing(0.5),
-      },
-
-      '& .value': {
-        color: theme.palette.text.primary,
-      },
+    '& .value': {
+      color: theme.palette.text.primary,
     },
   },
+}));
 
-  gridWrapper: {
-    paddingBottom: theme.spacing(2),
-  },
+export const GridWrapper = styled(Box)(({ theme }) => ({
+  flex: 1,
+  minHeight: 0,
 }));

+ 84 - 88
client/src/pages/system/Topology.tsx

@@ -1,104 +1,110 @@
 import { Box, Theme, useTheme } from '@mui/material';
+import { styled } from '@mui/material/styles';
 import { useEffect } from 'react';
-import { makeStyles } from '@mui/styles';
 
-const getStyles = makeStyles((theme: Theme) => ({
-  rootNode: {
+const RootNode = styled('g')(({ theme }) => ({
+  transition: 'all .25s',
+  cursor: 'pointer',
+  transformOrigin: '50% 50%',
+  transformBox: 'fill-box',
+
+  '& circle': {
     transition: 'all .25s',
-    cursor: 'pointer',
-    transformOrigin: '50% 50%',
-    transformBox: 'fill-box',
+  },
+
+  '& text': {
+    transition: 'all .25s',
+  },
 
+  '&:hover, &.selectedNode': {
+    transform: 'scale(1.1)',
+    filter: 'drop-shadow(3px 3px 5px rgba(0, 0, 0, .2))',
+    outline: 'none',
+  },
+
+  '&.selectedNode': {
     '& circle': {
-      transition: 'all .25s',
+      fill: theme.palette.primary.main,
+      stroke: theme.palette.primary.main,
     },
 
     '& text': {
-      transition: 'all .25s',
-    },
-
-    '&:hover, &.selectedNode': {
-      transform: 'scale(1.1)',
-      filter: 'drop-shadow(3px 3px 5px rgba(0, 0, 0, .2))',
-      outline: 'none',
+      fill: theme.palette.background.paper,
     },
+  },
+}));
 
-    '&.selectedNode': {
-      '& circle': {
-        fill: theme.palette.primary.main,
-        stroke: theme.palette.primary.main,
-      },
+const ChildNode = styled('g')(({ theme }) => ({
+  transition: 'all .25s',
+  cursor: 'pointer',
+  transformOrigin: '50% 50%',
+  transformBox: 'fill-box',
 
-      '& text': {
-        fill: theme.palette.background.paper,
-      },
-    },
+  '& circle': {
+    transition: 'all .25s',
   },
-  childNode: {
+
+  '& text': {
     transition: 'all .25s',
-    cursor: 'pointer',
-    transformOrigin: '50% 50%',
-    transformBox: 'fill-box',
+  },
 
-    '& circle': {
-      transition: 'all .25s',
-    },
+  '&:hover, &.selectedNode': {
+    transform: 'scale(1.1)',
+    filter: 'drop-shadow(3px 3px 5px rgba(0, 0, 0, .2))',
+    outline: 'none',
+  },
 
-    '& text': {
-      transition: 'all .25s',
+  '&.selectedNode': {
+    '& svg path': {
+      fill: theme.palette.background.paper,
     },
 
-    '&:hover, &.selectedNode': {
-      transform: 'scale(1.1)',
-      filter: 'drop-shadow(3px 3px 5px rgba(0, 0, 0, .2))',
-      outline: 'none',
+    '& circle': {
+      fill: theme.palette.primary.main,
+      stroke: theme.palette.primary.main,
     },
 
-    '&.selectedNode': {
-      '& svg path': {
-        fill: theme.palette.background.paper,
-      },
+    '& text': {
+      fill: theme.palette.background.paper,
+    },
+  },
+}));
 
-      '& circle': {
-        fill: theme.palette.primary.main,
-        stroke: theme.palette.primary.main,
-      },
+const SubChild = styled('svg')(({ theme }) => ({
+  transition: 'all .25s',
+  cursor: 'pointer',
+  outline: 'none',
 
-      '& text': {
-        fill: theme.palette.background.paper,
-      },
-    },
+  '& circle': {
+    transition: 'all .25s',
   },
-  subChild: {
+
+  '& rect': {
     transition: 'all .25s',
-    cursor: 'pointer',
-    outline: 'none',
+  },
 
-    '& circle': {
-      transition: 'all .25s',
-    },
+  '&:hover, &:focus': {
+    transform: 'scale(1.05)',
+    transformOrigin: 'center',
+    filter: 'drop-shadow(3px 3px 5px rgba(0, 0, 0, .2))',
 
     '& rect': {
-      transition: 'all .25s',
+      opacity: 0,
     },
 
-    '&:hover, &:focus': {
-      transform: 'scale(1.05)',
-      transformOrigin: 'center',
-      filter: 'drop-shadow(3px 3px 5px rgba(0, 0, 0, .2))',
-
-      '& rect': {
-        opacity: 0,
-      },
-
-      '& .selected': {
-        opacity: 1,
-        transform: 'translate(-40px, -77px) scale(3)',
-      },
+    '& .selected': {
+      opacity: 1,
+      transform: 'translate(-40px, -77px) scale(3)',
     },
   },
 }));
 
+const Container = styled(Box)(({ theme }) => ({
+  display: 'flex',
+  overflow: 'auto',
+  backgroundColor: theme.palette.background.paper,
+}));
+
 const capitalize = (s: string) => {
   return s.charAt(0).toUpperCase() + s.slice(1);
 };
@@ -118,7 +124,6 @@ const setSelected = (el: any) => {
 };
 
 const Topo = (props: any) => {
-  const classes = getStyles();
   const theme = useTheme();
   const { nodes, setNode, setCord, setShowChildView } = props;
 
@@ -153,13 +158,7 @@ const Topo = (props: any) => {
   let centerNode: any;
 
   return (
-    <Box
-      sx={{
-        display: 'flex',
-        overflow: 'auto',
-        backgroundColor: theme.palette.background.paper,
-      }}
-    >
+    <Container>
       <svg
         width={WIDTH}
         height={HEIGHT}
@@ -358,10 +357,9 @@ const Topo = (props: any) => {
                     stroke={theme.palette.primary.main}
                   />
                 )}
-                <g
-                  className={classes.childNode}
+                <ChildNode
                   tabIndex={0}
-                  onClick={e => {
+                  onClick={(e: React.MouseEvent) => {
                     setNode(node);
                     setSelected(e.target);
                   }}
@@ -395,18 +393,17 @@ const Topo = (props: any) => {
                   >
                     {capitalize(node?.infos?.name)}
                   </text>
-                </g>
+                </ChildNode>
 
                 {connectedLength && (
                   <>
-                    <svg
+                    <SubChild
                       tabIndex={0}
                       width="60"
                       height="60"
                       viewBox="0 0 60 60"
                       x={childNodeCenterX - 30}
                       y={childNodeCenterY - 30}
-                      className={classes.subChild}
                       onClick={() => {
                         setCord(node);
                         setShowChildView(true);
@@ -512,7 +509,7 @@ const Topo = (props: any) => {
                         height="6"
                         fill={theme.palette.primary.main}
                       />
-                    </svg>
+                    </SubChild>
                     <text
                       textAnchor="middle"
                       fill={theme.palette.text.primary}
@@ -527,11 +524,10 @@ const Topo = (props: any) => {
           }
           return null;
         })}
-        <g
+        <RootNode
           id="center"
-          className={classes.rootNode}
           tabIndex={0}
-          onClick={e => {
+          onClick={(e: React.MouseEvent) => {
             setNode(centerNode);
             setSelected(e.target);
           }}
@@ -554,9 +550,9 @@ const Topo = (props: any) => {
           >
             Milvus Proxy
           </text>
-        </g>
+        </RootNode>
       </svg>
-    </Box>
+    </Container>
   );
 };
 

+ 88 - 28
client/src/pages/user/dialogs/DBCollectionSelector.tsx

@@ -6,12 +6,13 @@ import {
   Tabs,
   Tab,
   Radio,
+  Box,
+  styled,
 } from '@mui/material';
 import { authContext } from '@/context';
 import Autocomplete from '@mui/material/Autocomplete';
 import { CollectionService } from '@/http';
 import { useTranslation } from 'react-i18next';
-import { useDBCollectionSelectorStyle } from './styles';
 import type {
   DBOption,
   CollectionOption,
@@ -19,6 +20,74 @@ import type {
 } from '../Types';
 import type { DBCollectionsPrivileges, RBACOptions } from '@server/types';
 
+const Root = styled(Box)(({ theme }) => ({
+  display: 'flex',
+  flexDirection: 'column',
+  gap: theme.spacing(1),
+  backgroundColor: theme.palette.background.paper,
+}));
+
+const DBCollections = styled(Box)(({ theme }) => ({
+  display: 'flex',
+  flexDirection: 'row',
+  gap: theme.spacing(1),
+}));
+
+const SelectorDB = styled(Autocomplete<DBOption>)(({ theme }) => ({
+  flex: 1,
+  marginBottom: theme.spacing(2),
+}));
+
+const SelectorCollection = styled(Autocomplete<CollectionOption>)({
+  flex: 1,
+});
+
+const CategoryHeader = styled(Box)(({ theme }) => ({
+  display: 'flex',
+  alignItems: 'center',
+  padding: theme.spacing(0.5),
+  backgroundColor: theme.palette.action.hover,
+  borderRadius: theme.shape.borderRadius,
+  marginBottom: 0,
+  '& .MuiTypography-root': {
+    fontSize: 14,
+    fontWeight: 600,
+    color: theme.palette.text.primary,
+  },
+}));
+
+const CategoryBody = styled(Box)(({ theme }) => ({
+  padding: theme.spacing(0.5, 1.5),
+  borderRadius: theme.shape.borderRadius,
+  backgroundColor: theme.palette.background.paper,
+}));
+
+const Privileges = styled(Box)(({ theme }) => ({
+  display: 'flex',
+  flex: 1,
+  flexDirection: 'column',
+  height: 'auto',
+  minHeight: 200,
+  width: '100%',
+  borderRadius: theme.shape.borderRadius,
+}));
+
+const PrivilegeBody = styled(FormControlLabel)({
+  minWidth: 200,
+});
+
+const ToggleContainer = styled(Box)(({ theme }) => ({
+  fontSize: 13,
+  '& .toggle-label': {
+    padding: 0,
+    marginRight: theme.spacing(1),
+    fontWeight: '400',
+  },
+  '& .MuiRadio-root': {
+    paddingRight: 8,
+  },
+}));
+
 export default function DBCollectionsSelector(
   props: DBCollectionsSelectorProps
 ) {
@@ -26,7 +95,6 @@ export default function DBCollectionsSelector(
   const { selected, setSelected, options } = props;
   const { rbacOptions, dbOptions } = options;
   // Styles
-  const classes = useDBCollectionSelectorStyle();
   // i18n
   const { t: searchTrans } = useTranslation('search');
   const { t: userTrans } = useTranslation('user');
@@ -234,7 +302,7 @@ export default function DBCollectionsSelector(
 
   const rbacEntries = Object.entries(rbacOptions) as [
     keyof RBACOptions,
-    Record<string, string>
+    Record<string, string>,
   ][];
   const databasePrivilegeOptions = rbacEntries.filter(([category]) => {
     return (
@@ -264,9 +332,9 @@ export default function DBCollectionsSelector(
   });
 
   return (
-    <div className={classes.root}>
+    <Root>
       {/* Privilege type toggle */}
-      <div className={classes.toggle}>
+      <ToggleContainer>
         <label className="toggle-label">
           <Radio
             checked={privilegeOptionType === 'group'}
@@ -291,7 +359,7 @@ export default function DBCollectionsSelector(
             {userTrans('privileges')}
           </label>
         )}
-      </div>
+      </ToggleContainer>
       {/* Tabs for cluster, Database, Collection */}
       <Tabs
         value={tabValue}
@@ -319,8 +387,7 @@ export default function DBCollectionsSelector(
 
       {tabValue === 1 && (
         <div>
-          <Autocomplete
-            className={classes.selectorDB}
+          <SelectorDB
             options={dbOptions}
             loading={loading}
             value={selectedDB || null}
@@ -359,9 +426,8 @@ export default function DBCollectionsSelector(
 
       {tabValue === 0 && (
         <div>
-          <div className={classes.dbCollections}>
-            <Autocomplete
-              className={classes.selectorDB}
+          <DBCollections>
+            <SelectorDB
               options={dbOptions}
               loading={loading}
               value={selectedDB || null}
@@ -384,8 +450,7 @@ export default function DBCollectionsSelector(
                 loading ? searchTrans('loading') : searchTrans('noOptions')
               }
             />
-            <Autocomplete
-              className={classes.selectorCollection}
+            <SelectorCollection
               options={collectionOptions}
               loading={loading}
               value={
@@ -423,7 +488,7 @@ export default function DBCollectionsSelector(
                 loading ? searchTrans('loading') : searchTrans('noOptions')
               }
             />
-          </div>
+          </DBCollections>
 
           <PrivilegeSelector
             privilegeOptions={collectionPrivilegeOptions}
@@ -438,7 +503,7 @@ export default function DBCollectionsSelector(
           />
         </div>
       )}
-    </div>
+    </Root>
   );
 }
 
@@ -485,14 +550,11 @@ const PrivilegeSelector = (props: {
     privilegeOptionType,
   } = props;
 
-  // style
-  const classes = useDBCollectionSelectorStyle();
-
   // i18n
   const { t: userTrans } = useTranslation('user');
 
   return (
-    <div className={classes.privileges}>
+    <Privileges>
       {selectedDB && selectedCollection && (
         <div>
           {privilegeOptions
@@ -505,7 +567,7 @@ const PrivilegeSelector = (props: {
             })
             .map(([category, categoryPrivileges]) => (
               <div key={category}>
-                <div className={classes.categoryHeader}>
+                <CategoryHeader>
                   <FormControlLabel
                     control={
                       <Checkbox
@@ -528,21 +590,19 @@ const PrivilegeSelector = (props: {
                           )
                         }
                         size="small"
-                        className={classes.selectAllCheckbox}
+                        sx={{ marginLeft: 1 }}
                         title={userTrans('selectAll')}
                       />
                     }
                     label={userTrans(category)}
                   />
-                </div>
-                <div className={classes.categoryBody}>
+                </CategoryHeader>
+                <CategoryBody>
                   {Object.entries(categoryPrivileges).map(([privilegeName]) => (
-                    <FormControlLabel
-                      className={classes.privilegeBody}
+                    <PrivilegeBody
                       key={privilegeName}
                       control={
                         <Checkbox
-                          className={classes.checkbox}
                           checked={
                             (selected[selectedDB.value] &&
                               selected[selectedDB.value].collections &&
@@ -565,11 +625,11 @@ const PrivilegeSelector = (props: {
                       label={privilegeName}
                     />
                   ))}
-                </div>
+                </CategoryBody>
               </div>
             ))}
         </div>
       )}
-    </div>
+    </Privileges>
   );
 };

+ 51 - 52
client/src/pages/user/dialogs/UpdatePrivilegeGroupDialog.tsx

@@ -5,6 +5,8 @@ import {
   AccordionSummary,
   AccordionDetails,
   Checkbox,
+  Box,
+  styled,
 } from '@mui/material';
 import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
 import { FC, useMemo, useState, useEffect } from 'react';
@@ -17,50 +19,53 @@ import { formatForm } from '@/utils';
 import { UserService } from '@/http';
 import { CreatePrivilegeGroupParams } from '../Types';
 import PrivilegeGroupOptions from './PrivilegeGroupOptions';
-import { makeStyles } from '@mui/styles';
 import { PrivilegeGroup } from '@server/types';
 
-const useStyles = makeStyles((theme: Theme) => ({
-  input: {
-    margin: theme.spacing(1, 0, 0.5),
-  },
-  dialogWrapper: {
-    maxWidth: theme.spacing(88),
-  },
-  checkBox: {
-    width: theme.spacing(24),
-  },
-  formGrp: {
-    marginBottom: theme.spacing(2),
-  },
-  subTitle: {
-    marginBottom: theme.spacing(0.5),
+const StyledAccordion = styled(Accordion)(({ theme }) => ({
+  margin: 0,
+  '&.Mui-expanded': {
+    opacity: 1,
   },
+  border: `1px solid ${theme.palette.divider}`,
+  borderBottom: 'none',
+}));
 
-  accordin: {
+const StyledAccordionSummary = styled(AccordionSummary)(({ theme }) => ({
+  backgroundColor: theme.palette.background.default,
+  minHeight: '48px !important',
+  '& .MuiAccordionSummary-content': {
     margin: 0,
-    '&.Mui-expanded': {
-      opacity: 1,
-    },
-    border: `1px solid ${theme.palette.divider}`,
-    borderBottom: 'none',
-  },
-  accordionSummary: {
-    backgroundColor: theme.palette.background.default,
-    minHeight: '48px !important',
-    '& .MuiAccordionSummary-content': {
-      margin: 0,
-      alignItems: 'center',
-      position: 'relative',
-      left: -10,
-    },
-  },
-  accordionDetail: {
-    backgroundColor: theme.palette.background.light,
-    borderTop: 'none',
+    alignItems: 'center',
+    position: 'relative',
+    left: -10,
   },
 }));
 
+const StyledAccordionDetails = styled(AccordionDetails)(({ theme }) => ({
+  backgroundColor: theme.palette.background.light,
+  borderTop: 'none',
+}));
+
+const InputWrapper = styled(Box)(({ theme }) => ({
+  margin: theme.spacing(1, 0, 0.5),
+}));
+
+const DialogWrapper = styled(Box)(({ theme }) => ({
+  maxWidth: theme.spacing(88),
+}));
+
+const CheckBoxWrapper = styled(Box)(({ theme }) => ({
+  width: theme.spacing(24),
+}));
+
+const FormGroup = styled(Box)(({ theme }) => ({
+  marginBottom: theme.spacing(2),
+}));
+
+const SubTitle = styled(Typography)(({ theme }) => ({
+  marginBottom: theme.spacing(0.5),
+}));
+
 export interface CreatePrivilegeGroupProps {
   onUpdate: (data: {
     data: CreatePrivilegeGroupParams;
@@ -82,7 +87,6 @@ const UpdateRoleDialog: FC<CreatePrivilegeGroupProps> = ({
 
   const fetchRBAC = async () => {
     const rbacOptions = await UserService.getAllPrivilegeGroups();
-
     setRbacOptions(rbacOptions);
   };
 
@@ -102,12 +106,9 @@ const UpdateRoleDialog: FC<CreatePrivilegeGroupProps> = ({
   }, [form]);
   const { validation, checkIsValid, disabled } = useFormValidation(checkedForm);
 
-  const classes = useStyles();
-
   const handleInputChange = (key: 'group_name', value: string) => {
     setForm(v => {
       const newFrom = { ...v, [key]: value };
-
       return newFrom;
     });
   };
@@ -118,7 +119,7 @@ const UpdateRoleDialog: FC<CreatePrivilegeGroupProps> = ({
       key: 'group_name',
       onChange: (value: string) => handleInputChange('group_name', value),
       variant: 'filled',
-      className: classes.input,
+      className: 'input',
       placeholder: userTrans('privilegeGroup'),
       fullWidth: true,
       validations: [
@@ -188,7 +189,7 @@ const UpdateRoleDialog: FC<CreatePrivilegeGroupProps> = ({
       confirmLabel={btnTrans(isEditing ? 'update' : 'create')}
       handleConfirm={handleCreatePrivilegeGroup}
       confirmDisabled={disabled}
-      dialogClass={classes.dialogWrapper}
+      dialogClass="dialog-wrapper"
     >
       <>
         {createConfigs.map(v => (
@@ -200,9 +201,9 @@ const UpdateRoleDialog: FC<CreatePrivilegeGroupProps> = ({
             key={v.label}
           />
         ))}
-        <Typography variant="h5" component="h5" className={classes.subTitle}>
+        <SubTitle variant="h5">
           {userTrans('privileges')}
-        </Typography>
+        </SubTitle>
 
         {rbacOptions.map((grp, index) => {
           const groupPrivileges = grp.privileges.map(p => p.name);
@@ -216,13 +217,11 @@ const UpdateRoleDialog: FC<CreatePrivilegeGroupProps> = ({
           );
 
           return (
-            <Accordion
+            <StyledAccordion
               key={`${grp.group_name}-${index}`}
-              className={classes.accordin}
               elevation={0}
             >
-              <AccordionSummary
-                className={classes.accordionSummary}
+              <StyledAccordionSummary
                 expandIcon={<ExpandMoreIcon />}
                 aria-controls={`${grp.group_name}-content`}
                 id={`${grp.group_name}-header`}
@@ -254,16 +253,16 @@ const UpdateRoleDialog: FC<CreatePrivilegeGroupProps> = ({
                   }
                   /{new Set(groupPrivileges).size})
                 </Typography>
-              </AccordionSummary>
-              <AccordionDetails className={classes.accordionDetail}>
+              </StyledAccordionSummary>
+              <StyledAccordionDetails>
                 <PrivilegeGroupOptions
                   options={groupPrivileges}
                   selection={form.privileges}
                   group_name={grp.group_name}
                   onChange={onChange}
                 />
-              </AccordionDetails>
-            </Accordion>
+              </StyledAccordionDetails>
+            </StyledAccordion>
           );
         })}
       </>

+ 0 - 77
client/src/pages/user/dialogs/styles.ts

@@ -1,77 +0,0 @@
-import { makeStyles } from '@mui/styles';
-import { Theme } from '@mui/material';
-
-export const useDBCollectionSelectorStyle = makeStyles((theme: Theme) => ({
-  root: {
-    display: 'flex',
-    flexDirection: 'column',
-    gap: theme.spacing(1),
-    backgroundColor: theme.palette.background.paper,
-  },
-  dbCollections: {
-    display: 'flex',
-    flexDirection: 'row',
-    gap: theme.spacing(1),
-  },
-  selectorDB: {
-    flex: 1,
-    marginBottom: theme.spacing(2),
-  },
-  selectorCollection: {
-    flex: 1,
-  },
-  categoryHeader: {
-    display: 'flex',
-    alignItems: 'center',
-    padding: theme.spacing(0.5),
-    backgroundColor: theme.palette.action.hover,
-    borderRadius: theme.shape.borderRadius,
-    marginBottom: 0,
-    '& .MuiTypography-root': {
-      fontSize: 14,
-      fontWeight: 600,
-      color: theme.palette.text.primary,
-    },
-  },
-  categoryBody: {
-    padding: theme.spacing(0.5, 1.5),
-    borderRadius: theme.shape.borderRadius,
-    backgroundColor: theme.palette.background.paper,
-  },
-  privilegeTitle: {
-    fontWeight: 600,
-    fontSize: 14,
-    color: theme.palette.text.primary,
-    margin: 0,
-    marginLeft: theme.spacing(-2),
-  },
-  privileges: {
-    display: 'flex',
-    flex: 1,
-    flexDirection: 'column',
-    height: 'auto',
-    minHeight: 200,
-    width: '100%',
-    borderRadius: theme.shape.borderRadius,
-  },
-
-  privilegeBody: {
-    minWidth: 200,
-  },
-
-  selectAllCheckbox: {
-    marginLeft: 8,
-  },
-  checkbox: {},
-  toggle: {
-    fontSize: 13,
-    '& .toggle-label': {
-      padding: 0,
-      marginRight: theme.spacing(1),
-      fontWeight: '400',
-    },
-    '& .MuiRadio-root': {
-      paddingRight: 8,
-    },
-  },
-}));