import { useState, useEffect } from 'react';
import { NavLink } from 'react-router';
import { Sun, Moon, Monitor, Shield, Lock, ShieldAlert, ShieldBan, ShieldCheck, Wifi, LayoutDashboard, Globe, Network, ChevronRight, KeyRound } from 'lucide-react';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from './ui/collapsible';
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarRail,
useSidebar,
} from './ui/sidebar';
import { useTheme } from '../contexts/ThemeContext';
import { useCentralStatus } from '../hooks/useCentralStatus';
function WildCentralLogo({ className }: { className?: string }) {
return (
);
}
const ADVANCED_OPEN_KEY = 'wild-central:advanced-open';
export function AppSidebar() {
const { theme, setTheme } = useTheme();
const { state } = useSidebar();
const { data: centralStatus } = useCentralStatus();
const [advancedOpen, setAdvancedOpen] = useState(() => localStorage.getItem(ADVANCED_OPEN_KEY) === 'true');
useEffect(() => {
localStorage.setItem(ADVANCED_OPEN_KEY, String(advancedOpen));
}, [advancedOpen]);
const cycleTheme = () => {
if (theme === 'light') {
setTheme('dark');
} else if (theme === 'dark') {
setTheme('system');
} else {
setTheme('light');
}
};
const getThemeIcon = () => {
switch (theme) {
case 'light':
return ;
case 'dark':
return ;
default:
return ;
}
};
const getThemeLabel = () => {
switch (theme) {
case 'light':
return 'Light mode';
case 'dark':
return 'Dark mode';
default:
return 'System theme';
}
};
const domainsItems = [
{ to: '/', icon: LayoutDashboard, label: 'Dashboard', end: true },
{ to: '/domains', icon: Globe, label: 'Domains' },
];
const centralItems = [
{ to: '/auth', icon: KeyRound, label: 'Authentication', daemon: 'authelia' as const },
{ to: '/vpn', icon: Lock, label: 'VPN', daemon: 'wireguard' as const },
{ to: '/firewall', icon: Shield, label: 'Firewall', daemon: 'nftables' as const },
{ to: '/crowdsec', icon: ShieldAlert, label: 'CrowdSec', daemon: 'crowdsec' as const },
{ to: '/dhcp', icon: Wifi, label: 'DHCP', daemon: 'dnsmasq' as const },
{ to: '/dns-filter', icon: ShieldBan, label: 'DNS Filter', daemon: 'dnsmasq' as const },
];
const advancedItems = [
{ to: '/advanced/haproxy', icon: Network, label: 'HAProxy', daemon: 'haproxy' as const },
{ to: '/advanced/dnsmasq', icon: Wifi, label: 'dnsmasq', daemon: 'dnsmasq' as const },
{ to: '/advanced/nftables', icon: Shield, label: 'nftables', daemon: 'nftables' as const },
{ to: '/advanced/wireguard', icon: Lock, label: 'WireGuard', daemon: 'wireguard' as const },
{ to: '/advanced/crowdsec', icon: ShieldAlert, label: 'CrowdSec', daemon: 'crowdsec' as const },
{ to: '/advanced/authelia', icon: KeyRound, label: 'Authelia', daemon: 'authelia' as const },
{ to: '/advanced/ddns', icon: Globe, label: 'DDNS', daemon: undefined },
{ to: '/advanced/certificates', icon: ShieldCheck, label: 'Certificates', daemon: 'certbot' as const },
];
return (
{state === 'collapsed' ? (
{[...domainsItems, ...centralItems, ...advancedItems].map((item) => {
const daemon = 'daemon' in item ? (item as any).daemon : undefined;
const active = daemon ? centralStatus?.daemons?.[daemon]?.active : undefined;
return (
{({ isActive }) => (
{item.label}
{active !== undefined && (
)}
)}
);
})}
) : (
<>
Domains
{domainsItems.map(({ to, icon: Icon, label, end }) => (
{({ isActive }) => (
{label}
)}
))}
Central
{centralItems.map(({ to, icon: Icon, label, daemon }) => {
const active = centralStatus?.daemons?.[daemon]?.active;
return (
{({ isActive }) => (
{label}
{active !== undefined && (
)}
)}
);
})}
Advanced
{advancedItems.map(({ to, icon: Icon, label, daemon }) => {
const active = daemon ? centralStatus?.daemons?.[daemon]?.active : undefined;
return (
{({ isActive }) => (
{label}
{active !== undefined && (
)}
)}
);
})}
>
)}
{getThemeIcon()}
{getThemeLabel()}
);
}