import { useState } from 'react'
import { Outlet, useNavigate, useLocation } from 'react-router-dom'
import {
AppBar, Box, CssBaseline, Divider, Drawer, IconButton,
List, ListItem, ListItemButton, ListItemIcon, ListItemText,
Toolbar, Typography, Avatar, Menu, MenuItem, Tooltip,
} from '@mui/material'
import {
Dashboard as DashboardIcon,
Computer as HostsIcon,
Group as GroupsIcon,
Build as DeployIcon,
Assignment as JobsIcon,
Schedule as MaintenanceIcon,
People as UsersIcon,
VerifiedUser as CertsIcon,
Assessment as ReportsIcon,
Settings as SettingsIcon,
Menu as MenuIcon,
Logout as LogoutIcon,
Person as PersonIcon,
} from '@mui/icons-material'
import { useAuthStore } from '../store/authStore'
const DRAWER_WIDTH = 240
interface NavItem {
label: string
path: string
icon: React.ReactElement
adminOnly?: boolean
writeOnly?: boolean
}
const navGroups: { heading: string; items: NavItem[] }[] = [
{
heading: 'Overview',
items: [
{ label: 'Dashboard', path: '/dashboard', icon: },
],
},
{
heading: 'Fleet',
items: [
{ label: 'Hosts', path: '/hosts', icon: },
{ label: 'Groups', path: '/groups', icon: },
{ label: 'Deploy', path: '/deployment', icon: , writeOnly: true },
],
},
{
heading: 'Operations',
items: [
{ label: 'Jobs', path: '/jobs', icon: },
{ label: 'Maintenance', path: '/maintenance', icon: , writeOnly: true },
],
},
{
heading: 'Administration',
items: [
{ label: 'Users', path: '/users', icon: , adminOnly: true },
{ label: 'Certificates', path: '/certificates', icon: },
{ label: 'Reports', path: '/reports', icon: },
{ label: 'Settings', path: '/settings', icon: },
],
},
]
export default function AppLayout() {
const navigate = useNavigate()
const location = useLocation()
const { user, logout } = useAuthStore()
const [mobileOpen, setMobileOpen] = useState(false)
const [anchorEl, setAnchorEl] = useState(null)
const isAdmin = user?.role === 'admin'
const canWrite = user?.role === 'admin' || user?.role === 'operator'
const handleDrawerToggle = () => setMobileOpen(!mobileOpen)
const handleMenuOpen = (e: React.MouseEvent) => setAnchorEl(e.currentTarget)
const handleMenuClose = () => setAnchorEl(null)
const handleLogout = () => {
handleMenuClose()
logout()
navigate('/login', { replace: true })
}
const drawer = (
🐉 Patch Manager
{navGroups.map((group) => {
const visibleItems = group.items.filter((item) => {
if (item.adminOnly && !isAdmin) return false
if (item.writeOnly && !canWrite) return false
return true
})
if (visibleItems.length === 0) return null
return (
{group.heading}
{visibleItems.map((item) => {
const isActive = location.pathname === item.path || location.pathname.startsWith(item.path + '/')
return (
navigate(item.path)}
sx={{
borderRadius: 1,
mx: 0.5,
'&.Mui-selected': {
bgcolor: 'primary.main',
color: 'primary.contrastText',
'&:hover': { bgcolor: 'primary.dark' },
'& .MuiListItemIcon-root': { color: 'primary.contrastText' },
},
}}
>
{item.icon}
)
})}
)
})}
Linux Patch Manager v{__APP_VERSION__}
)
return (
{/* App Bar */}
theme.zIndex.drawer + 1,
borderBottom: 1,
borderColor: 'divider',
}}
>
{navGroups.flatMap((g) => g.items).find((i) => location.pathname === i.path || location.pathname.startsWith(i.path + '/'))?.label || 'Patch Manager'}
{(user?.display_name || user?.username || '?')[0].toUpperCase()}
{/* Sidebar */}
{/* Mobile drawer */}
{drawer}
{/* Desktop drawer */}
{drawer}
{/* Main content */}
)
}