ipmi-fan-control/frontend/src/components/Layout.tsx

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>
);
}