|
@@ -1,105 +1,34 @@
|
|
|
import { FC, useContext, useState, MouseEvent } from 'react';
|
|
|
import { useTranslation } from 'react-i18next';
|
|
|
-import Typography from '@mui/material/Typography';
|
|
|
-import Tooltip from '@mui/material/Tooltip';
|
|
|
-import Menu from '@mui/material/Menu';
|
|
|
-import MenuItem from '@mui/material/MenuItem';
|
|
|
+import {
|
|
|
+ AppBar,
|
|
|
+ Toolbar,
|
|
|
+ Typography,
|
|
|
+ Tooltip,
|
|
|
+ Menu,
|
|
|
+ MenuItem,
|
|
|
+ IconButton,
|
|
|
+ Box,
|
|
|
+ Stack,
|
|
|
+} from '@mui/material';
|
|
|
import { useNavigate } from 'react-router-dom';
|
|
|
-import { navContext, dataContext, authContext, rootContext } from '@/context';
|
|
|
+import {
|
|
|
+ navContext,
|
|
|
+ dataContext,
|
|
|
+ authContext,
|
|
|
+ rootContext,
|
|
|
+ ColorModeContext,
|
|
|
+} from '@/context';
|
|
|
import { MilvusService } from '@/http';
|
|
|
-import CustomSelector from '@/components/customSelector/CustomSelector';
|
|
|
-import StatusIcon from '@/components/status/StatusIcon';
|
|
|
import UpdateUser from '@/pages/user/dialogs/UpdateUserPassDialog';
|
|
|
import icons from '../icons/Icons';
|
|
|
-import { styled } from '@mui/material/styles';
|
|
|
-import IconButton from '@mui/material/IconButton';
|
|
|
-import { ColorModeContext } from '@/context';
|
|
|
-import { LoadingType } from '@/components/status/StatusIcon';
|
|
|
-
|
|
|
-const HeaderWrapper = styled('header')(({ theme }) => ({
|
|
|
- display: 'flex',
|
|
|
- alignItems: 'center',
|
|
|
- color: theme.palette.text.primary,
|
|
|
- backgroundColor: theme.palette.background.paper,
|
|
|
- paddingRight: theme.spacing(1),
|
|
|
- borderBottom: `1px solid ${theme.palette.divider}`,
|
|
|
- height: 48,
|
|
|
-}));
|
|
|
-
|
|
|
-const ContentWrapper = styled('div')({
|
|
|
- display: 'flex',
|
|
|
- justifyContent: 'space-between',
|
|
|
- alignItems: 'center',
|
|
|
- flex: 1,
|
|
|
- height: 48,
|
|
|
-});
|
|
|
-
|
|
|
-const Navigation = styled('div')({
|
|
|
- display: 'flex',
|
|
|
- alignItems: 'center',
|
|
|
-});
|
|
|
-
|
|
|
-const StyledIcon = styled('div')(({ theme }) => ({
|
|
|
- color: theme.palette.primary.main,
|
|
|
- cursor: 'pointer',
|
|
|
- marginRight: theme.spacing(1),
|
|
|
-}));
|
|
|
-
|
|
|
-const AddressWrapper = styled('div')(({ theme }) => ({
|
|
|
- display: 'flex',
|
|
|
- alignItems: 'center',
|
|
|
-
|
|
|
- '& .text': {
|
|
|
- marginRight: theme.spacing(2),
|
|
|
-
|
|
|
- '& .address': {
|
|
|
- fontSize: '12px',
|
|
|
- lineHeight: 1.3,
|
|
|
- },
|
|
|
-
|
|
|
- '& .status': {
|
|
|
- fontSize: '12px',
|
|
|
- lineHeight: 1.3,
|
|
|
- color: '#1ba954',
|
|
|
- },
|
|
|
- },
|
|
|
-}));
|
|
|
-
|
|
|
-const Title = styled(Typography)(({ theme }) => ({
|
|
|
- paddingLeft: theme.spacing(2),
|
|
|
-}));
|
|
|
-
|
|
|
-const DatabaseSelector = styled(CustomSelector)(({ theme }) => ({
|
|
|
- transform: 'translateY(-4px)',
|
|
|
- width: 'auto',
|
|
|
- minWidth: 120,
|
|
|
- '& .MuiInputLabel-root': {
|
|
|
- top: '4px',
|
|
|
- },
|
|
|
-}));
|
|
|
-
|
|
|
-const ModeButton = styled(IconButton)(({ theme }) => ({
|
|
|
- marginRight: theme.spacing(1),
|
|
|
- '& svg': {
|
|
|
- fontSize: 18,
|
|
|
- color: theme.palette.text.primary,
|
|
|
- },
|
|
|
-}));
|
|
|
-
|
|
|
-const Extra = styled('span')(({ theme }) => ({
|
|
|
- marginLeft: theme.spacing(0.5),
|
|
|
- display: 'flex',
|
|
|
- '& svg': {
|
|
|
- fontSize: 15,
|
|
|
- color: theme.palette.primary.main,
|
|
|
- },
|
|
|
-}));
|
|
|
+import Breadcrumbs from '@mui/material/Breadcrumbs';
|
|
|
|
|
|
const Header: FC = () => {
|
|
|
// use context
|
|
|
const { navInfo } = useContext(navContext);
|
|
|
const { mode, toggleColorMode } = useContext(ColorModeContext);
|
|
|
- const { database, databases, setDatabase, loading } = useContext(dataContext);
|
|
|
+ const { database, databases, setDatabase } = useContext(dataContext);
|
|
|
const { authReq, logout } = useContext(authContext);
|
|
|
const { setDialog, handleCloseDialog, openSnackBar } =
|
|
|
useContext(rootContext);
|
|
@@ -108,11 +37,11 @@ const Header: FC = () => {
|
|
|
const navigate = useNavigate();
|
|
|
|
|
|
// UI states
|
|
|
- const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
|
|
+ const [dbAnchorEl, setDbAnchorEl] = useState<null | HTMLElement>(null);
|
|
|
+ const [userAnchorEl, setUserAnchorEl] = useState<null | HTMLElement>(null);
|
|
|
|
|
|
- // i8n
|
|
|
+ // i18n
|
|
|
const { t: commonTrans } = useTranslation();
|
|
|
- const { t: dbTrans } = useTranslation('database');
|
|
|
const { t: successTrans } = useTranslation('success');
|
|
|
const { t: userTrans } = useTranslation('user');
|
|
|
|
|
@@ -134,16 +63,16 @@ const Header: FC = () => {
|
|
|
await MilvusService.useDatabase({ database });
|
|
|
};
|
|
|
|
|
|
- const handleUserMenuClick = (event: MouseEvent<HTMLDivElement>) => {
|
|
|
- setAnchorEl(event.currentTarget);
|
|
|
+ const handleUserMenuClick = (event: MouseEvent<HTMLButtonElement>) => {
|
|
|
+ setUserAnchorEl(event.currentTarget);
|
|
|
};
|
|
|
|
|
|
const handleUserMenuClose = () => {
|
|
|
- setAnchorEl(null);
|
|
|
+ setUserAnchorEl(null);
|
|
|
};
|
|
|
|
|
|
const handleChangePassword = () => {
|
|
|
- setAnchorEl(null);
|
|
|
+ setUserAnchorEl(null);
|
|
|
setDialog({
|
|
|
open: true,
|
|
|
type: 'custom',
|
|
@@ -151,10 +80,10 @@ const Header: FC = () => {
|
|
|
component: (
|
|
|
<UpdateUser
|
|
|
username={username}
|
|
|
- onUpdate={res => {
|
|
|
+ onUpdate={() => {
|
|
|
openSnackBar(successTrans('passwordChanged'));
|
|
|
handleCloseDialog();
|
|
|
- setAnchorEl(null);
|
|
|
+ setUserAnchorEl(null);
|
|
|
logout();
|
|
|
}}
|
|
|
handleClose={handleCloseDialog}
|
|
@@ -164,95 +93,162 @@ const Header: FC = () => {
|
|
|
});
|
|
|
};
|
|
|
|
|
|
+ const handleDbClick = (event: MouseEvent<HTMLElement>) => {
|
|
|
+ setDbAnchorEl(event.currentTarget);
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleDbMenuClose = () => {
|
|
|
+ setDbAnchorEl(null);
|
|
|
+ };
|
|
|
+
|
|
|
// local computes
|
|
|
const dbOptions = databases.map(d => ({ value: d.name, label: d.name }));
|
|
|
- const isLoadingDb = dbOptions.length === 0;
|
|
|
|
|
|
return (
|
|
|
- <HeaderWrapper>
|
|
|
- <ContentWrapper>
|
|
|
- <Navigation>
|
|
|
+ <AppBar
|
|
|
+ position="static"
|
|
|
+ color="default"
|
|
|
+ elevation={0}
|
|
|
+ sx={{
|
|
|
+ borderBottom: theme => `1px solid ${theme.palette.divider}`,
|
|
|
+ height: 48,
|
|
|
+ justifyContent: 'center',
|
|
|
+ backgroundColor: theme =>
|
|
|
+ mode === 'dark' ? theme.palette.background.default : '#fff',
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <Toolbar
|
|
|
+ disableGutters
|
|
|
+ sx={{
|
|
|
+ minHeight: 48,
|
|
|
+ px: 2,
|
|
|
+ display: 'flex',
|
|
|
+ justifyContent: 'space-between',
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <Stack direction="row" alignItems="center" spacing={1}>
|
|
|
{navInfo.backPath !== '' && (
|
|
|
- <StyledIcon onClick={() => handleBack(navInfo.backPath)}>
|
|
|
+ <IconButton
|
|
|
+ size="small"
|
|
|
+ onClick={() => handleBack(navInfo.backPath)}
|
|
|
+ sx={{ color: 'primary.main' }}
|
|
|
+ >
|
|
|
<BackIcon />
|
|
|
- </StyledIcon>
|
|
|
+ </IconButton>
|
|
|
)}
|
|
|
- {navInfo.showDatabaseSelector &&
|
|
|
- (!isLoadingDb ? (
|
|
|
- <DatabaseSelector
|
|
|
- label={dbTrans('database')}
|
|
|
- value={database}
|
|
|
- onChange={async (e: { target: { value: unknown } }) => {
|
|
|
- const database = e.target.value as string;
|
|
|
- await useDatabase(database);
|
|
|
- setDatabase(database);
|
|
|
-
|
|
|
- // if url contains databases, go to the database page
|
|
|
- if (window.location.hash.includes('databases')) {
|
|
|
- navigate(`/databases/${database}/collections`);
|
|
|
- }
|
|
|
- }}
|
|
|
- options={dbOptions}
|
|
|
- variant="filled"
|
|
|
- disabled={loading}
|
|
|
- />
|
|
|
- ) : (
|
|
|
- <StatusIcon type={LoadingType.CREATING} />
|
|
|
- ))}
|
|
|
-
|
|
|
- <Title variant="h5" color="textPrimary">
|
|
|
- {navInfo.navTitle}
|
|
|
- </Title>
|
|
|
- <Extra>{navInfo.extra}</Extra>
|
|
|
- </Navigation>
|
|
|
-
|
|
|
- <AddressWrapper>
|
|
|
- <ModeButton onClick={toggleColorMode} color="inherit">
|
|
|
- {mode === 'dark' ? <icons.night /> : <icons.day />}
|
|
|
- </ModeButton>
|
|
|
- <div className="text">
|
|
|
- <Typography className="address">{address}</Typography>
|
|
|
- <Typography className="status">
|
|
|
- {commonTrans('status.running')}
|
|
|
- </Typography>
|
|
|
- </div>
|
|
|
- {username && (
|
|
|
- <>
|
|
|
- <Tooltip title={username}>
|
|
|
- <StyledIcon
|
|
|
- onClick={handleUserMenuClick}
|
|
|
- style={{ cursor: 'pointer' }}
|
|
|
- >
|
|
|
- <Avatar />
|
|
|
- </StyledIcon>
|
|
|
- </Tooltip>
|
|
|
+ {navInfo.showDatabaseSelector && (
|
|
|
+ <Breadcrumbs aria-label="breadcrumb" sx={{ mx: 1 }}>
|
|
|
+ <Typography
|
|
|
+ sx={{ cursor: 'pointer', fontWeight: 500 }}
|
|
|
+ color="primary"
|
|
|
+ onClick={handleDbClick}
|
|
|
+ >
|
|
|
+ {database}
|
|
|
+ </Typography>
|
|
|
<Menu
|
|
|
- anchorEl={anchorEl}
|
|
|
- open={Boolean(anchorEl)}
|
|
|
- onClose={handleUserMenuClose}
|
|
|
- anchorOrigin={{
|
|
|
- vertical: 'bottom',
|
|
|
- horizontal: 'right',
|
|
|
- }}
|
|
|
- transformOrigin={{
|
|
|
- vertical: 'top',
|
|
|
- horizontal: 'right',
|
|
|
- }}
|
|
|
+ anchorEl={dbAnchorEl}
|
|
|
+ open={Boolean(dbAnchorEl)}
|
|
|
+ onClose={handleDbMenuClose}
|
|
|
+ anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
|
|
|
+ transformOrigin={{ vertical: 'top', horizontal: 'left' }}
|
|
|
>
|
|
|
- <MenuItem onClick={handleChangePassword}>
|
|
|
- {userTrans('changePassword')}
|
|
|
- </MenuItem>
|
|
|
+ {dbOptions.map(option => (
|
|
|
+ <MenuItem
|
|
|
+ key={option.value}
|
|
|
+ selected={option.value === database}
|
|
|
+ onClick={async () => {
|
|
|
+ await useDatabase(option.value);
|
|
|
+ setDatabase(option.value);
|
|
|
+ setDbAnchorEl(null);
|
|
|
+ if (window.location.hash.includes('databases')) {
|
|
|
+ navigate(`/databases/${option.value}/collections`);
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {option.label}
|
|
|
+ </MenuItem>
|
|
|
+ ))}
|
|
|
</Menu>
|
|
|
- </>
|
|
|
+ </Breadcrumbs>
|
|
|
)}
|
|
|
- <Tooltip title={'disconnect'}>
|
|
|
- <StyledIcon>
|
|
|
- <LogoutIcon onClick={handleLogout} />
|
|
|
- </StyledIcon>
|
|
|
- </Tooltip>
|
|
|
- </AddressWrapper>
|
|
|
- </ContentWrapper>
|
|
|
- </HeaderWrapper>
|
|
|
+ <Typography variant="h5" color="text.primary">
|
|
|
+ {navInfo.navTitle}
|
|
|
+ </Typography>
|
|
|
+ {navInfo.extra && (
|
|
|
+ <Box
|
|
|
+ sx={{
|
|
|
+ ml: 0.5,
|
|
|
+ display: 'flex',
|
|
|
+ alignItems: 'center',
|
|
|
+ '& svg': { fontSize: 15, color: 'primary.main' },
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {navInfo.extra}
|
|
|
+ </Box>
|
|
|
+ )}
|
|
|
+ </Stack>
|
|
|
+ <Stack direction="row" alignItems="center" spacing={1}>
|
|
|
+ <IconButton onClick={toggleColorMode} color="inherit" size="small">
|
|
|
+ {mode === 'dark' ? <icons.night /> : <icons.day />}
|
|
|
+ </IconButton>
|
|
|
+ <Box sx={{ display: 'flex', alignItems: 'center', mr: 2 }}>
|
|
|
+ <Box sx={{ mr: 2 }}>
|
|
|
+ <Typography
|
|
|
+ className="address"
|
|
|
+ sx={{ fontSize: 12, lineHeight: 1.3 }}
|
|
|
+ >
|
|
|
+ {address}
|
|
|
+ </Typography>
|
|
|
+ <Typography
|
|
|
+ className="status"
|
|
|
+ sx={{ fontSize: 12, lineHeight: 1.3, color: '#1ba954' }}
|
|
|
+ >
|
|
|
+ {commonTrans('status.running')}
|
|
|
+ </Typography>
|
|
|
+ </Box>
|
|
|
+ {username && (
|
|
|
+ <>
|
|
|
+ <Tooltip title={username}>
|
|
|
+ <IconButton
|
|
|
+ size="small"
|
|
|
+ onClick={handleUserMenuClick}
|
|
|
+ sx={{ color: 'primary.main' }}
|
|
|
+ >
|
|
|
+ <Avatar />
|
|
|
+ </IconButton>
|
|
|
+ </Tooltip>
|
|
|
+ <Menu
|
|
|
+ anchorEl={userAnchorEl}
|
|
|
+ open={Boolean(userAnchorEl)}
|
|
|
+ onClose={handleUserMenuClose}
|
|
|
+ anchorOrigin={{
|
|
|
+ vertical: 'bottom',
|
|
|
+ horizontal: 'right',
|
|
|
+ }}
|
|
|
+ transformOrigin={{
|
|
|
+ vertical: 'top',
|
|
|
+ horizontal: 'right',
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <MenuItem onClick={handleChangePassword}>
|
|
|
+ {userTrans('changePassword')}
|
|
|
+ </MenuItem>
|
|
|
+ </Menu>
|
|
|
+ </>
|
|
|
+ )}
|
|
|
+ <Tooltip title={'disconnect'}>
|
|
|
+ <IconButton
|
|
|
+ size="small"
|
|
|
+ sx={{ color: 'primary.main' }}
|
|
|
+ onClick={handleLogout}
|
|
|
+ >
|
|
|
+ <LogoutIcon />
|
|
|
+ </IconButton>
|
|
|
+ </Tooltip>
|
|
|
+ </Box>
|
|
|
+ </Stack>
|
|
|
+ </Toolbar>
|
|
|
+ </AppBar>
|
|
|
);
|
|
|
};
|
|
|
|