feat: Add Wild Central web app (Central-only UI)
Extract Central pages from the wild-cloud web app into a standalone React app for Wild Central. Includes: - Central overview, DNS, DHCP, Firewall, VPN, Ingress, CrowdSec pages - Simplified sidebar with Central-only navigation - Branding updated to "Wild Central" - All Cloud-specific pages, components, hooks, and API services removed - TypeScript type-check and production build pass Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
134
web/src/components/AppSidebar.tsx
Normal file
134
web/src/components/AppSidebar.tsx
Normal file
@@ -0,0 +1,134 @@
|
||||
import { NavLink } from 'react-router';
|
||||
import { Sun, Moon, Monitor, Shield, Lock, Network, ShieldAlert, Router, Cloud, Wifi, Server } from 'lucide-react';
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
SidebarFooter,
|
||||
SidebarGroup,
|
||||
SidebarGroupContent,
|
||||
SidebarGroupLabel,
|
||||
SidebarHeader,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
SidebarRail,
|
||||
useSidebar,
|
||||
} from './ui/sidebar';
|
||||
import { useTheme } from '../contexts/ThemeContext';
|
||||
|
||||
export function AppSidebar() {
|
||||
const { theme, setTheme } = useTheme();
|
||||
const { state } = useSidebar();
|
||||
|
||||
const cycleTheme = () => {
|
||||
if (theme === 'light') {
|
||||
setTheme('dark');
|
||||
} else if (theme === 'dark') {
|
||||
setTheme('system');
|
||||
} else {
|
||||
setTheme('light');
|
||||
}
|
||||
};
|
||||
|
||||
const getThemeIcon = () => {
|
||||
switch (theme) {
|
||||
case 'light':
|
||||
return <Sun className="h-4 w-4" />;
|
||||
case 'dark':
|
||||
return <Moon className="h-4 w-4" />;
|
||||
default:
|
||||
return <Monitor className="h-4 w-4" />;
|
||||
}
|
||||
};
|
||||
|
||||
const getThemeLabel = () => {
|
||||
switch (theme) {
|
||||
case 'light':
|
||||
return 'Light mode';
|
||||
case 'dark':
|
||||
return 'Dark mode';
|
||||
default:
|
||||
return 'System theme';
|
||||
}
|
||||
};
|
||||
|
||||
const centralItems = [
|
||||
{ to: '/central', icon: Server, label: 'Overview', end: true },
|
||||
{ to: '/central/cloudflare', icon: Cloud, label: 'Cloudflare' },
|
||||
{ to: '/central/dns', icon: Router, label: 'DNS' },
|
||||
{ to: '/central/ingress', icon: Network, label: 'Ingress Proxy' },
|
||||
{ to: '/central/firewall', icon: Shield, label: 'Firewall' },
|
||||
{ to: '/central/vpn', icon: Lock, label: 'VPN' },
|
||||
{ to: '/central/crowdsec', icon: ShieldAlert, label: 'CrowdSec' },
|
||||
{ to: '/central/dhcp', icon: Wifi, label: 'DHCP' },
|
||||
];
|
||||
|
||||
return (
|
||||
<Sidebar variant="sidebar" collapsible="icon">
|
||||
<SidebarHeader>
|
||||
<div className="flex items-center justify-center pb-2">
|
||||
<div className="p-1 bg-primary/10 rounded-lg">
|
||||
<Server className="h-6 w-6 text-primary" />
|
||||
</div>
|
||||
<div className="overflow-hidden transition-all duration-200 ease-linear ml-2 max-w-[200px] opacity-100 group-data-[collapsible=icon]:ml-0 group-data-[collapsible=icon]:max-w-0 group-data-[collapsible=icon]:opacity-0">
|
||||
<h2 className="text-lg font-bold text-foreground whitespace-nowrap">Wild Central</h2>
|
||||
</div>
|
||||
</div>
|
||||
</SidebarHeader>
|
||||
|
||||
<SidebarContent>
|
||||
{state === 'collapsed' ? (
|
||||
<SidebarMenu>
|
||||
{centralItems.map(({ to, icon: Icon, label, end }) => (
|
||||
<SidebarMenuItem key={to}>
|
||||
<NavLink to={to} end={end}>
|
||||
{({ isActive }) => (
|
||||
<SidebarMenuButton isActive={isActive} tooltip={label}>
|
||||
<Icon className="h-4 w-4" />
|
||||
<span>{label}</span>
|
||||
</SidebarMenuButton>
|
||||
)}
|
||||
</NavLink>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
) : (
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>Central</SidebarGroupLabel>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
{centralItems.map(({ to, icon: Icon, label, end }) => (
|
||||
<SidebarMenuItem key={to}>
|
||||
<NavLink to={to} end={end}>
|
||||
{({ isActive }) => (
|
||||
<SidebarMenuButton isActive={isActive}>
|
||||
<Icon className="h-4 w-4" />
|
||||
<span className="truncate">{label}</span>
|
||||
</SidebarMenuButton>
|
||||
)}
|
||||
</NavLink>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
)}
|
||||
</SidebarContent>
|
||||
|
||||
<SidebarFooter>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
onClick={cycleTheme}
|
||||
tooltip={`Current: ${getThemeLabel()}. Click to cycle themes.`}
|
||||
>
|
||||
{getThemeIcon()}
|
||||
<span>{getThemeLabel()}</span>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarFooter>
|
||||
<SidebarRail/>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user