"use client"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { useEffect, useRef } from "react"; import { clsx } from "clsx"; import { useAuth } from "@/components/AuthProvider"; import { auth } from "@/lib/api"; import { Logo } from "./Logo"; interface NavItem { href: string; label: string; icon: React.ReactNode; /** Restricted to owner/admin the roles the backing API requires. */ adminOnly?: boolean; } function ServerIcon() { return ( ); } function KeyIcon() { return ( ); } function SecretIcon() { return ( ); } function WorkflowIcon() { return ( ); } function AuditIcon() { return ( ); } function LicenceIcon() { return ( ); } function SettingsIcon() { return ( ); } function MonitorIcon() { return ( ); } function StepsIcon() { return ( ); } const navItems: NavItem[] = [ { href: "/servers", label: "Servers", icon: }, { href: "/monitors", label: "Monitors", icon: }, { href: "/keys", label: "SSH Keys", icon: }, { href: "/secrets", label: "Secrets", icon: }, { href: "/workflows", label: "Workflows", icon: }, { href: "/steps", label: "Steps", icon: }, { href: "/audit", label: "Audit Log", icon: }, { href: "/settings/license", label: "Licence", icon: , adminOnly: true }, { href: "/settings", label: "Settings", icon: , adminOnly: true }, ]; /** Shared by the permanent aside and the offcanvas drawer one copy of the nav. */ export function SidebarContent({ onNavigate }: { onNavigate?: () => void }) { const pathname = usePathname(); const { user, instance, isAdmin } = useAuth(); const visibleItems = navItems.filter((item) => !item.adminOnly || isAdmin); const activeHref = visibleItems.reduce((best, item) => { const matches = pathname === item.href || pathname.startsWith(item.href + "/"); if (!matches) return best; return best === null || item.href.length > best.length ? item.href : best; }, null); async function handleLogout() { try { await auth.logout(); } catch {} window.location.href = "/login"; } return ( <>
Vantage {instance && {instance.name}}
{user && (

{user.name || user.email}

{user.email} {user.role && ยท {user.role}}

)}

Vantage v1.0

{user && ( )}
); } /** The permanent sidebar. Below lg the drawer takes over. */ export function Sidebar() { return ( ); } /** * The offcanvas below lg. Always mounted so the slide runs in both directions; * closed it is inert (invisible + pointer-events-none) rather than unmounted. */ export function SidebarDrawer({ open, onClose }: { open: boolean; onClose: () => void }) { const panelRef = useRef(null); useEffect(() => { if (!open) return; const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") onClose(); }; window.addEventListener("keydown", onKey); const previousOverflow = document.body.style.overflow; document.body.style.overflow = "hidden"; panelRef.current?.focus(); return () => { window.removeEventListener("keydown", onKey); document.body.style.overflow = previousOverflow; }; }, [open, onClose]); return (
); }