import React, { useState, useEffect } from 'react'; import { NavLink, useLocation } from 'react-router-dom'; import { Menu, X, Search, Activity, LayoutDashboard, Terminal, Settings, LogOut, Server, Archive, Package, Network, ChevronDown, ChevronRight, HardDrive, ShieldAlert, Bell, Webhook, Lock, } from '../icons'; import { prefetchRoute } from '../routePrefetch'; import './Layout.css'; type ThreatLevel = 'nominal' | 'elevated' | 'critical'; interface LayoutProps { children: React.ReactNode; onLogout: () => void; onSearch: (q: string) => void; onOpenCmd?: () => void; sector?: string; persona?: string; threat?: ThreatLevel; alertCount?: number; build?: string; } const ROUTE_LABELS: Record = { '/': 'DASHBOARD', '/fleet': 'FLEET', '/mazenet': 'MAZENET', '/live-logs': 'LIVE LOGS', '/webhooks': 'WEBHOOKS', '/bounty': 'BOUNTY', '/credentials': 'CREDENTIALS', '/attackers': 'ATTACKERS', '/config': 'CONFIG', '/swarm-updates': 'REMOTE UPDATES', '/swarm/hosts': 'SWARM HOSTS', }; function labelForPath(pathname: string): string { if (ROUTE_LABELS[pathname]) return ROUTE_LABELS[pathname]; const prefix = Object.keys(ROUTE_LABELS).find(p => p !== '/' && pathname.startsWith(p)); return prefix ? ROUTE_LABELS[prefix] : pathname.replace(/^\//, '').toUpperCase(); } function formatClock(d: Date): string { const pad = (n: number) => String(n).padStart(2, '0'); return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`; } const Layout: React.FC = ({ children, onLogout, onSearch, onOpenCmd, sector = 'PRODUCTION', persona = 'ADMIN', threat: threatProp = 'nominal', alertCount: alertCountProp = 0, build = 'v0.1', }) => { const [sidebarOpen, setSidebarOpen] = useState(true); const [search, setSearch] = useState(''); const [systemActive, setSystemActive] = useState(false); const [clockTime, setClockTime] = useState(() => formatClock(new Date())); const [threat, setThreat] = useState(threatProp); const [alertCount, setAlertCount] = useState(alertCountProp); const location = useLocation(); const handleSearchSubmit = (e: React.FormEvent) => { e.preventDefault(); onSearch(search); }; useEffect(() => { const onStats = (e: Event) => { const detail = (e as CustomEvent).detail; setSystemActive(detail.deployed_deckies > 0); if (detail.threat) setThreat(detail.threat as ThreatLevel); if (typeof detail.alert_count === 'number') setAlertCount(detail.alert_count); }; window.addEventListener('decnet:stats', onStats); return () => window.removeEventListener('decnet:stats', onStats); }, []); useEffect(() => { const iv = setInterval(() => setClockTime(formatClock(new Date())), 1000); return () => clearInterval(iv); }, []); const routeLabel = labelForPath(location.pathname); const showThreat = threat !== 'nominal'; const threatLabel = threat.toUpperCase(); return (
{/* Sidebar */} {/* Main Content Area */}
{/* Topbar */}
{sector.toUpperCase()} / {routeLabel}
setSearch(e.target.value)} onFocus={() => onOpenCmd?.()} /> Alt+K
{showThreat && (
THREAT: {threatLabel}
)}
SYSTEM: {systemActive ? 'ACTIVE' : 'INACTIVE'}
{clockTime}
{/* Dynamic Content */}
{children}
); }; interface NavItemProps { to: string; icon: React.ReactNode; label: string; open: boolean; indent?: boolean; badge?: number; } const NavItem: React.FC = ({ to, icon, label, open, indent, badge }) => ( `nav-item ${isActive ? 'active' : ''} ${indent ? 'nav-subitem' : ''}`} end={to === '/'} onMouseEnter={() => prefetchRoute(to)} onFocus={() => prefetchRoute(to)} > {icon} {open && {label}} {open && badge !== undefined && badge > 0 && ( {badge > 99 ? '99+' : badge} )} ); interface NavGroupProps { label: string; icon: React.ReactNode; open: boolean; children: React.ReactNode; } const NavGroup: React.FC = ({ label, icon, open, children }) => { const [expanded, setExpanded] = useState(true); return (
{expanded &&
{children}
}
); }; export default Layout;