Skip to content

Commit efc10e2

Browse files
committed
menu
1 parent ba3a2cc commit efc10e2

File tree

1 file changed

+52
-20
lines changed

1 file changed

+52
-20
lines changed

ui/components/dashboard-layout.tsx

Lines changed: 52 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,19 @@ import {
3636
Users,
3737
Zap,
3838
BarChart,
39-
Grid3x3
39+
Grid3x3,
40+
ChevronDown,
41+
ChevronRight
4042
} from 'lucide-react'
4143

4244
const 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

9493
interface DashboardLayoutProps {
@@ -97,10 +96,23 @@ interface DashboardLayoutProps {
9796

9897
export 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

Comments
 (0)