192 lines
4.8 KiB
TypeScript
192 lines
4.8 KiB
TypeScript
import { Outlet, useNavigate, useLocation } from 'react-router-dom';
|
|
import {
|
|
Box,
|
|
Drawer,
|
|
AppBar,
|
|
Toolbar,
|
|
List,
|
|
ListItem,
|
|
ListItemButton,
|
|
ListItemIcon,
|
|
ListItemText,
|
|
Typography,
|
|
IconButton,
|
|
Divider,
|
|
Menu,
|
|
MenuItem,
|
|
} from '@mui/material';
|
|
import {
|
|
Dashboard as DashboardIcon,
|
|
Dns as ServerIcon,
|
|
Timeline as LogsIcon,
|
|
Menu as MenuIcon,
|
|
Logout as LogoutIcon,
|
|
AccountCircle,
|
|
} from '@mui/icons-material';
|
|
import { useState } from 'react';
|
|
import { useAuthStore } from '../stores/authStore';
|
|
|
|
const drawerWidth = 240;
|
|
|
|
const menuItems = [
|
|
{ text: 'Dashboard', icon: <DashboardIcon />, path: '/' },
|
|
{ text: 'Servers', icon: <ServerIcon />, path: '/servers' },
|
|
{ text: 'Logs', icon: <LogsIcon />, path: '/logs' },
|
|
];
|
|
|
|
export default function Layout() {
|
|
const navigate = useNavigate();
|
|
const location = useLocation();
|
|
const { user, logout } = useAuthStore();
|
|
const [mobileOpen, setMobileOpen] = useState(false);
|
|
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
|
|
|
const handleDrawerToggle = () => {
|
|
setMobileOpen(!mobileOpen);
|
|
};
|
|
|
|
const handleProfileMenuOpen = (event: React.MouseEvent<HTMLElement>) => {
|
|
setAnchorEl(event.currentTarget);
|
|
};
|
|
|
|
const handleProfileMenuClose = () => {
|
|
setAnchorEl(null);
|
|
};
|
|
|
|
const handleLogout = () => {
|
|
handleProfileMenuClose();
|
|
logout();
|
|
navigate('/login');
|
|
};
|
|
|
|
const drawer = (
|
|
<div>
|
|
<Toolbar>
|
|
<Typography variant="h6" noWrap component="div">
|
|
IPMI Fan Control
|
|
</Typography>
|
|
</Toolbar>
|
|
<Divider />
|
|
<List>
|
|
{menuItems.map((item) => (
|
|
<ListItem key={item.text} disablePadding>
|
|
<ListItemButton
|
|
selected={location.pathname === item.path}
|
|
onClick={() => {
|
|
navigate(item.path);
|
|
setMobileOpen(false);
|
|
}}
|
|
>
|
|
<ListItemIcon>{item.icon}</ListItemIcon>
|
|
<ListItemText primary={item.text} />
|
|
</ListItemButton>
|
|
</ListItem>
|
|
))}
|
|
</List>
|
|
</div>
|
|
);
|
|
|
|
return (
|
|
<Box sx={{ display: 'flex' }}>
|
|
<AppBar
|
|
position="fixed"
|
|
sx={{
|
|
width: { md: `calc(100% - ${drawerWidth}px)` },
|
|
ml: { md: `${drawerWidth}px` },
|
|
}}
|
|
>
|
|
<Toolbar>
|
|
<IconButton
|
|
color="inherit"
|
|
aria-label="open drawer"
|
|
edge="start"
|
|
onClick={handleDrawerToggle}
|
|
sx={{ mr: 2, display: { md: 'none' } }}
|
|
>
|
|
<MenuIcon />
|
|
</IconButton>
|
|
<Typography variant="h6" noWrap component="div" sx={{ flexGrow: 1 }}>
|
|
{menuItems.find((item) => item.path === location.pathname)?.text || 'IPMI Fan Control'}
|
|
</Typography>
|
|
<IconButton
|
|
size="large"
|
|
aria-label="account of current user"
|
|
aria-controls="menu-appbar"
|
|
aria-haspopup="true"
|
|
onClick={handleProfileMenuOpen}
|
|
color="inherit"
|
|
>
|
|
<AccountCircle />
|
|
</IconButton>
|
|
<Menu
|
|
id="menu-appbar"
|
|
anchorEl={anchorEl}
|
|
anchorOrigin={{
|
|
vertical: 'bottom',
|
|
horizontal: 'right',
|
|
}}
|
|
keepMounted
|
|
transformOrigin={{
|
|
vertical: 'top',
|
|
horizontal: 'right',
|
|
}}
|
|
open={Boolean(anchorEl)}
|
|
onClose={handleProfileMenuClose}
|
|
>
|
|
<MenuItem disabled>
|
|
<Typography variant="body2">{user?.username}</Typography>
|
|
</MenuItem>
|
|
<Divider />
|
|
<MenuItem onClick={handleLogout}>
|
|
<ListItemIcon>
|
|
<LogoutIcon fontSize="small" />
|
|
</ListItemIcon>
|
|
Logout
|
|
</MenuItem>
|
|
</Menu>
|
|
</Toolbar>
|
|
</AppBar>
|
|
<Box
|
|
component="nav"
|
|
sx={{ width: { md: drawerWidth }, flexShrink: { md: 0 } }}
|
|
>
|
|
<Drawer
|
|
variant="temporary"
|
|
open={mobileOpen}
|
|
onClose={handleDrawerToggle}
|
|
ModalProps={{
|
|
keepMounted: true,
|
|
}}
|
|
sx={{
|
|
display: { xs: 'block', md: 'none' },
|
|
'& .MuiDrawer-paper': { boxSizing: 'border-box', width: drawerWidth },
|
|
}}
|
|
>
|
|
{drawer}
|
|
</Drawer>
|
|
<Drawer
|
|
variant="permanent"
|
|
sx={{
|
|
display: { xs: 'none', md: 'block' },
|
|
'& .MuiDrawer-paper': { boxSizing: 'border-box', width: drawerWidth },
|
|
}}
|
|
open
|
|
>
|
|
{drawer}
|
|
</Drawer>
|
|
</Box>
|
|
<Box
|
|
component="main"
|
|
sx={{
|
|
flexGrow: 1,
|
|
p: 3,
|
|
width: { md: `calc(100% - ${drawerWidth}px)` },
|
|
mt: 8,
|
|
}}
|
|
>
|
|
<Outlet />
|
|
</Box>
|
|
</Box>
|
|
);
|
|
}
|