import { useEffect, useRef, useState } from "react"; import { NavLink, useNavigate } from "react-router-dom"; import { useQuery } from "@tanstack/react-query"; import { LayoutDashboard, Boxes, Network, Image, Database, FolderTree, LayoutTemplate, ScrollText, Settings, Moon, Sun, LogOut, ShieldOff, Menu, X, } from "lucide-react"; import { cn } from "@/lib/utils"; import { useAuthStore } from "@/store/auth"; import { useThemeStore } from "@/store/theme"; import { agentsApi } from "@/api/agents"; import { VersionBadge } from "./VersionBadge"; type NavItem = { to: string; label: string; icon: typeof LayoutDashboard; end?: boolean; /** Hidden for non-admins — the matching API routes require the admin role. */ adminOnly?: boolean; }; export const NAV_ITEMS: NavItem[] = [ { to: "/", label: "Dashboard", icon: LayoutDashboard, end: true }, { to: "/stacks", label: "Stacks", icon: Boxes }, { to: "/networks", label: "Networks", icon: Network }, { to: "/images", label: "Images", icon: Image }, { to: "/volumes", label: "Volumes", icon: Database }, { to: "/files", label: "Files", icon: FolderTree, adminOnly: true }, { to: "/templates", label: "Templates", icon: LayoutTemplate }, { to: "/audit", label: "Audit", icon: ScrollText, adminOnly: true }, { to: "/settings", label: "Settings", icon: Settings }, ]; function LogoMark() { return ( ); } function initials(name: string | undefined): string { if (!name) return "?"; return name.slice(0, 2).toUpperCase(); } export function TopNav() { const [drawerOpen, setDrawerOpen] = useState(false); const [menuOpen, setMenuOpen] = useState(false); const menuRef = useRef(null); const navigate = useNavigate(); const user = useAuthStore((s) => s.user); const navItems = NAV_ITEMS.filter((i) => !i.adminOnly || user?.role === "admin"); const logout = useAuthStore((s) => s.logout); const signOutEverywhere = useAuthStore((s) => s.signOutEverywhere); const { theme, toggle } = useThemeStore(); const agents = useQuery({ queryKey: ["agents"], queryFn: () => agentsApi.list(), refetchInterval: 30000, }); const agentCount = agents.data?.length ?? 0; const agentsOnline = agents.data?.filter((a) => a.status === "online").length ?? 0; // Close avatar menu on outside click. useEffect(() => { if (!menuOpen) return; const onClick = (e: MouseEvent) => { if (menuRef.current && !menuRef.current.contains(e.target as Node)) { setMenuOpen(false); } }; document.addEventListener("mousedown", onClick); return () => document.removeEventListener("mousedown", onClick); }, [menuOpen]); return ( <>
{/* Hamburger (mobile / narrow) */} {/* Logo */} StackPilot {/* Pill nav */} {/* Right cluster */}
{agentCount > 0 && ( )}
{menuOpen && (

{user?.username ?? "—"}

{user?.role === "admin" &&

admin

}
)}
{/* Off-canvas drawer (narrow screens) */} {drawerOpen && (
setDrawerOpen(false)} /> )} ); }