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()} { handleMenuClose(); navigate('/profile') }}> {/* Sidebar */} {/* Mobile drawer */} {drawer} {/* Desktop drawer */} {drawer} {/* Main content */} ) }