@@ -36,16 +36,19 @@ import {
3636 Users ,
3737 Zap ,
3838 BarChart ,
39- Grid3x3
39+ Grid3x3 ,
40+ ChevronDown ,
41+ ChevronRight
4042} from 'lucide-react'
4143
4244const navigationItems = [
43- // Overview
44- { id : 'overview' , label : 'Overview' , icon : Home , href : '/' , section : 'overview' } ,
45- { id : 'all' , label : 'All Resources' , icon : Grid3x3 , href : '/all' , section : 'overview' } ,
45+ // Top items (no section)
46+ { id : 'overview' , label : 'Overview' , icon : Home , href : '/' } ,
47+ { id : 'all' , label : 'All Resources' , icon : Grid3x3 , href : '/all' } ,
48+ { id : 'templates' , label : 'Templates' , icon : FileCode2 , href : '/templates' } ,
4649
4750 // Workloads
48- { id : 'workloads-header' , label : 'Workloads' , isHeader : true } ,
51+ { id : 'workloads-header' , label : 'Workloads' , isHeader : true , section : 'workloads' } ,
4952 { id : 'deployments' , label : 'Deployments' , icon : Package , href : '/deployments' , section : 'workloads' } ,
5053 { id : 'replicasets' , label : 'ReplicaSets' , icon : Copy , href : '/replicasets' , section : 'workloads' } ,
5154 { id : 'statefulsets' , label : 'StatefulSets' , icon : Layers , href : '/statefulsets' , section : 'workloads' } ,
@@ -55,7 +58,7 @@ const navigationItems = [
5558 { id : 'cronjobs' , label : 'CronJobs' , icon : Calendar , href : '/cronjobs' , section : 'workloads' } ,
5659
5760 // Config & Storage
58- { id : 'config-header' , label : 'Config & Storage' , isHeader : true } ,
61+ { id : 'config-header' , label : 'Config & Storage' , isHeader : true , section : 'config' } ,
5962 { id : 'configmaps' , label : 'ConfigMaps' , icon : Settings , href : '/configmaps' , section : 'config' } ,
6063 { id : 'secrets' , label : 'Secrets' , icon : Key , href : '/secrets' , section : 'config' } ,
6164 { id : 'pvcs' , label : 'Persistent Volume Claims' , icon : HardDrive , href : '/pvcs' , section : 'config' } ,
@@ -64,31 +67,27 @@ const navigationItems = [
6467 { id : 'volumeattachments' , label : 'Volume Attachments' , icon : Paperclip , href : '/volumeattachments' , section : 'config' } ,
6568
6669 // Network
67- { id : 'network-header' , label : 'Network' , isHeader : true } ,
70+ { id : 'network-header' , label : 'Network' , isHeader : true , section : 'network' } ,
6871 { id : 'services' , label : 'Services' , icon : Server , href : '/services' , section : 'network' } ,
6972 { id : 'ingresses' , label : 'Ingresses' , icon : Globe , href : '/ingresses' , section : 'network' } ,
7073 { id : 'networkpolicies' , label : 'Network Policies' , icon : Shield , href : '/networkpolicies' , section : 'network' } ,
7174 { id : 'endpoints' , label : 'Endpoints' , icon : Network , href : '/endpoints' , section : 'network' } ,
7275 { id : 'endpointslices' , label : 'Endpoint Slices' , icon : Network , href : '/endpointslices' , section : 'network' } ,
7376
7477 // Access Control
75- { id : 'rbac-header' , label : 'Access Control' , isHeader : true } ,
78+ { id : 'rbac-header' , label : 'Access Control' , isHeader : true , section : 'rbac' } ,
7679 { id : 'serviceaccounts' , label : 'Service Accounts' , icon : Bot , href : '/serviceaccounts' , section : 'rbac' } ,
7780 { id : 'roles' , label : 'Roles' , icon : UserCheck , href : '/roles' , section : 'rbac' } ,
7881 { id : 'rolebindings' , label : 'Role Bindings' , icon : Users , href : '/rolebindings' , section : 'rbac' } ,
7982
8083 // Cluster
81- { id : 'cluster-header' , label : 'Cluster' , isHeader : true } ,
84+ { id : 'cluster-header' , label : 'Cluster' , isHeader : true , section : 'cluster' } ,
8285 { id : 'resourcequotas' , label : 'Resource Quotas' , icon : Gauge , href : '/resourcequotas' , section : 'cluster' } ,
8386 { id : 'hpas' , label : 'Horizontal Pod Autoscalers' , icon : BarChart , href : '/hpas' , section : 'cluster' } ,
8487 { id : 'pdbs' , label : 'Pod Disruption Budgets' , icon : ShieldCheck , href : '/pdbs' , section : 'cluster' } ,
8588 { id : 'priorityclasses' , label : 'Priority Classes' , icon : Star , href : '/priorityclasses' , section : 'cluster' } ,
8689 { id : 'runtimeclasses' , label : 'Runtime Classes' , icon : Cpu , href : '/runtimeclasses' , section : 'cluster' } ,
8790 { id : 'events' , label : 'Events' , icon : Clock , href : '/events' , section : 'cluster' } ,
88-
89- // Templates
90- { id : 'templates-header' , label : 'Templates' , isHeader : true } ,
91- { id : 'templates' , label : 'YAML Templates' , icon : FileCode2 , href : '/templates' , section : 'templates' } ,
9291]
9392
9493interface DashboardLayoutProps {
@@ -97,10 +96,23 @@ interface DashboardLayoutProps {
9796
9897export function DashboardLayout ( { children } : DashboardLayoutProps ) {
9998 const [ sidebarOpen , setSidebarOpen ] = useState ( true )
99+ const [ expandedSections , setExpandedSections ] = useState < Set < string > > ( new Set ( [ 'workloads' , 'config' , 'network' , 'rbac' , 'cluster' ] ) )
100100 const pathname = usePathname ( )
101- const { config } = useKubernetes ( ) ;
101+ const { config } = useKubernetes ( )
102+
102103 // Find active section based on pathname
103104 const activeSection = navigationItems . find ( item => ! item . isHeader && item . href === pathname ) ?. label || 'Overview'
105+
106+ // Toggle section expansion
107+ const toggleSection = ( section : string ) => {
108+ const newExpanded = new Set ( expandedSections )
109+ if ( newExpanded . has ( section ) ) {
110+ newExpanded . delete ( section )
111+ } else {
112+ newExpanded . add ( section )
113+ }
114+ setExpandedSections ( newExpanded )
115+ }
104116
105117 return (
106118 < div className = "flex h-screen bg-background" >
@@ -109,24 +121,44 @@ export function DashboardLayout({ children }: DashboardLayoutProps) {
109121 < div className = "p-4" >
110122 < h1 className = "text-2xl font-bold text-primary" > K8s Dashboard</ h1 >
111123 </ div >
112- < nav className = "mt-8 pb-4" >
124+ < nav className = "mt-8 pb-4 overflow-y-auto " >
113125 { navigationItems . map ( ( item ) => {
126+ // Headers (collapsible sections)
114127 if ( item . isHeader ) {
128+ const isExpanded = expandedSections . has ( item . section ! )
115129 return (
116- < div key = { item . id } className = "px-4 py-2 mt-4 mb-1 text-xs font-semibold text-muted-foreground uppercase tracking-wider" >
117- { item . label }
118- </ div >
130+ < button
131+ key = { item . id }
132+ onClick = { ( ) => toggleSection ( item . section ! ) }
133+ className = "w-full flex items-center justify-between px-4 py-2 mt-4 mb-1 text-xs font-semibold text-muted-foreground uppercase tracking-wider hover:text-foreground transition-colors"
134+ >
135+ < span > { item . label } </ span >
136+ { isExpanded ? (
137+ < ChevronDown className = "w-3 h-3" />
138+ ) : (
139+ < ChevronRight className = "w-3 h-3" />
140+ ) }
141+ </ button >
119142 )
120143 }
144+
145+ // Skip items in collapsed sections
146+ if ( item . section && ! expandedSections . has ( item . section ) ) {
147+ return null
148+ }
149+
150+ // Regular navigation items
121151 const Icon = item . icon
122152 const isActive = pathname === item . href
153+ const marginLeft = item . section ? 'ml-4' : '' // Indent items in sections
154+
123155 return (
124156 < NextLink
125157 key = { item . id }
126158 href = { item . href }
127- className = { `w-full flex items-center px-4 py-2 text-sm text-left hover:bg-accent transition-colors ${
159+ className = { `w-full flex items-center px-4 py-2 text-sm hover:bg-accent transition-colors ${
128160 isActive ? 'bg-accent border-l-4 border-primary' : ''
129- } `}
161+ } ${ marginLeft } `}
130162 >
131163 < Icon className = "w-4 h-4 mr-3 flex-shrink-0" />
132164 < span className = "truncate" > { item . label } </ span >
0 commit comments