feat(ui): rebuild Sidebar with Fleet/Config/Admin structure

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jason Staack
2026-03-16 17:39:12 -05:00
parent a10e609c02
commit b9bbcf4a45

View File

@@ -7,30 +7,17 @@ import {
Settings, Settings,
ChevronLeft, ChevronLeft,
ChevronRight, ChevronRight,
Bell,
BellRing,
Download, Download,
Terminal, Terminal,
FileCode, FileCode,
FileText,
MapPin,
LayoutDashboard, LayoutDashboard,
Network,
Wrench,
ClipboardList, ClipboardList,
Calendar, Wifi,
Key, BarChart3,
Layers,
Shield,
ShieldCheck,
Eye,
Info,
} from 'lucide-react' } from 'lucide-react'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { useAuth, isSuperAdmin, isTenantAdmin } from '@/lib/auth' import { useAuth, isSuperAdmin, isTenantAdmin } from '@/lib/auth'
import { useUIStore } from '@/lib/store' import { useUIStore } from '@/lib/store'
import { AlertBadge } from '@/components/alerts/AlertBadge'
import { RugLogo } from '@/components/brand/RugLogo' import { RugLogo } from '@/components/brand/RugLogo'
interface NavItem { interface NavItem {
@@ -38,7 +25,6 @@ interface NavItem {
href: string href: string
icon: React.FC<{ className?: string }> icon: React.FC<{ className?: string }>
exact?: boolean exact?: boolean
badge?: React.ReactNode
} }
interface NavSection { interface NavSection {
@@ -109,12 +95,12 @@ export function Sidebar() {
visible: true, visible: true,
items: [ items: [
{ {
label: 'Dashboard', label: 'Overview',
href: '/', href: '/',
icon: LayoutDashboard, icon: LayoutDashboard,
exact: true, exact: true,
}, },
// Only show Devices for non-super_admin (super_admin uses Organizations in Admin) // Only show Devices for non-super_admin with a tenant_id
...(!isSuperAdmin(user) && user?.tenant_id ...(!isSuperAdmin(user) && user?.tenant_id
? [ ? [
{ {
@@ -125,31 +111,26 @@ export function Sidebar() {
] ]
: []), : []),
{ {
label: 'Map', label: 'Wireless',
href: '/map', href: '/wireless',
icon: MapPin, icon: Wifi,
},
{
label: 'Traffic',
href: '/traffic',
icon: BarChart3,
}, },
], ],
}, },
{ {
label: 'Manage', label: 'Config',
visible: true, visible: true,
items: [ items: [
{ {
label: 'Config Editor', label: 'Editor',
href: '/config-editor', href: '/config-editor',
icon: Terminal, icon: Terminal,
}, },
{
label: 'Batch Config',
href: '/batch-config',
icon: Wrench,
},
{
label: 'Bulk Commands',
href: '/bulk-commands',
icon: Layers,
},
{ {
label: 'Templates', label: 'Templates',
href: '/templates', href: '/templates',
@@ -160,63 +141,6 @@ export function Sidebar() {
href: '/firmware', href: '/firmware',
icon: Download, icon: Download,
}, },
{
label: 'Maintenance',
href: '/maintenance',
icon: Calendar,
},
{
label: 'VPN',
href: '/vpn',
icon: Shield,
},
{
label: 'Certificates',
href: '/certificates',
icon: ShieldCheck,
},
],
},
{
label: 'Monitor',
visible: true,
items: [
{
label: 'Topology',
href: '/topology',
icon: Network,
},
{
label: 'Alerts',
href: '/alerts',
icon: Bell,
badge: <AlertBadge />,
},
{
label: 'Alert Rules',
href: '/alert-rules',
icon: BellRing,
},
{
label: 'Audit Trail',
href: '/audit',
icon: ClipboardList,
},
...(isTenantAdmin(user)
? [
{
label: 'Transparency',
href: '/transparency',
icon: Eye,
},
]
: []),
{
label: 'Reports',
href: '/reports',
icon: FileText,
},
], ],
}, },
{ {
@@ -242,20 +166,15 @@ export function Sidebar() {
] ]
: []), : []),
{ {
label: 'API Keys', label: 'Audit Log',
href: '/settings/api-keys', href: '/audit',
icon: Key, icon: ClipboardList,
}, },
{ {
label: 'Settings', label: 'Settings',
href: '/settings', href: '/settings',
icon: Settings, icon: Settings,
}, },
{
label: 'About',
href: '/about',
icon: Info,
},
], ],
}, },
] ]
@@ -310,8 +229,8 @@ export function Sidebar() {
className={cn( className={cn(
'flex items-center gap-2.5 px-3 py-2 mx-1 rounded-md text-sm transition-colors min-h-[44px]', 'flex items-center gap-2.5 px-3 py-2 mx-1 rounded-md text-sm transition-colors min-h-[44px]',
active active
? 'bg-accent-muted text-accent' ? 'bg-[hsl(var(--accent-muted))] text-accent rounded-md'
: 'text-text-secondary hover:text-text-primary hover:bg-elevated/50', : 'text-text-muted hover:text-text-primary hover:bg-elevated/50 rounded-md',
showCollapsed && 'justify-center px-0', showCollapsed && 'justify-center px-0',
)} )}
title={showCollapsed ? item.label : undefined} title={showCollapsed ? item.label : undefined}
@@ -320,10 +239,7 @@ export function Sidebar() {
> >
<Icon className="h-4 w-4 flex-shrink-0" aria-hidden="true" /> <Icon className="h-4 w-4 flex-shrink-0" aria-hidden="true" />
{!showCollapsed && ( {!showCollapsed && (
<>
<span className="truncate">{item.label}</span> <span className="truncate">{item.label}</span>
{item.badge && <span className="ml-auto">{item.badge}</span>}
</>
)} )}
</Link> </Link>
) )
@@ -332,6 +248,13 @@ export function Sidebar() {
))} ))}
</nav> </nav>
{/* Version identifier */}
{!showCollapsed && (
<div className="px-3 py-1 text-center">
<span className="font-mono text-[9px] text-text-muted">TOD v9.5</span>
</div>
)}
{/* Collapse toggle (hidden on mobile) */} {/* Collapse toggle (hidden on mobile) */}
<button <button
onClick={toggleSidebar} onClick={toggleSidebar}
@@ -354,9 +277,10 @@ export function Sidebar() {
{/* Desktop sidebar */} {/* Desktop sidebar */}
<aside <aside
data-testid="sidebar" data-testid="sidebar"
data-sidebar
className={cn( className={cn(
'hidden lg:flex flex-col border-r border-border bg-sidebar transition-all duration-200', 'hidden lg:flex flex-col border-r border-border bg-sidebar transition-all duration-200',
sidebarCollapsed ? 'w-12' : 'w-60', sidebarCollapsed ? 'w-14' : 'w-[180px]',
)} )}
> >
{sidebarContent(sidebarCollapsed)} {sidebarContent(sidebarCollapsed)}
@@ -374,7 +298,7 @@ export function Sidebar() {
role="dialog" role="dialog"
aria-modal="true" aria-modal="true"
aria-label="Navigation" aria-label="Navigation"
className="lg:hidden fixed inset-y-0 left-0 z-50 w-60 flex flex-col bg-sidebar border-r border-border shadow-xl" className="lg:hidden fixed inset-y-0 left-0 z-50 w-[180px] flex flex-col bg-sidebar border-r border-border shadow-xl"
> >
{sidebarContent(false)} {sidebarContent(false)}
</aside> </aside>