import { NavLink } from "react-router-dom"; import { useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { LayoutDashboard, FlaskConical, BookOpen, BarChart3, Settings, Users, FileText, ChevronDown, ListChecks, CheckCircle, Database, Crosshair, Zap, Grid3X3, List, Gauge, ShieldCheck, GitCompareArrows, ScrollText, ClipboardCheck, } from "lucide-react"; import { useAuth } from "../context/AuthContext"; import { getTechniques } from "../api/techniques"; interface NavItem { to: string; label: string; icon: React.FC<{ className?: string }>; /** Roles allowed to see this item. undefined = everyone. */ roles?: string[]; children?: NavItem[]; } const mainLinks: NavItem[] = [ { to: "/dashboard", label: "Dashboard", icon: LayoutDashboard }, { to: "/executive-dashboard", label: "Executive Dashboard", icon: Gauge, roles: ["admin", "red_lead", "blue_lead", "viewer"] }, { to: "/matrix", label: "ATT&CK", icon: Grid3X3, children: [ { to: "/techniques", label: "Techniques", icon: List }, { to: "/matrix", label: "Coverage Matrix", icon: Grid3X3 }, { to: "/techniques/review-queue", label: "Review Queue", icon: ClipboardCheck, roles: ["admin", "red_lead", "blue_lead"] }, ], }, { to: "/tests", label: "Tests", icon: FlaskConical, children: [ { to: "/tests", label: "All Tests", icon: ListChecks }, { to: "/tests/validated", label: "Validated Tests", icon: CheckCircle }, { to: "/test-catalog", label: "Test Catalog", icon: BookOpen }, ], }, { to: "/campaigns", label: "Campaigns", icon: Zap }, { to: "/threat-actors", label: "Threat Actors", icon: Crosshair }, { to: "/compliance", label: "Compliance", icon: ShieldCheck }, { to: "/comparison", label: "Comparison", icon: GitCompareArrows, roles: ["admin", "red_lead", "blue_lead", "viewer"] }, { to: "/reports", label: "Reports", icon: BarChart3 }, { to: "/settings", label: "Settings", icon: Settings }, ]; const systemLinks: NavItem[] = [ { to: "/data-sources", label: "Data Sources", icon: Database }, { to: "/system", label: "MITRE Sync", icon: ScrollText }, { to: "/users", label: "Users", icon: Users }, { to: "/audit", label: "Audit Log", icon: FileText }, ]; function SidebarLink({ item, badge }: { item: NavItem; badge?: number }) { const [expanded, setExpanded] = useState(false); const { user } = useAuth(); const role = user?.role ?? ""; const isAdmin = role === "admin"; const childCanSee = (child: NavItem) => { if (!child.roles) return true; if (isAdmin) return true; return child.roles.includes(role); }; if (item.children) { const visibleChildren = item.children.filter(childCanSee); return (
{expanded && (
{visibleChildren.map((child) => ( `flex items-center gap-3 rounded-lg px-3 py-2 text-sm transition-colors ${ isActive ? "bg-cyan-500/10 text-cyan-400" : "text-gray-500 hover:bg-gray-800 hover:text-gray-200" }` } > {child.label} ))}
)}
); } return ( `flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors ${ isActive ? "bg-cyan-500/10 text-cyan-400" : "text-gray-400 hover:bg-gray-800 hover:text-gray-200" }` } > {item.label} {badge !== undefined && badge > 0 && ( {badge} )} ); } export default function Sidebar() { const { user } = useAuth(); const role = user?.role ?? ""; const isAdmin = role === "admin"; const canSeeReviewQueue = isAdmin || role === "red_lead" || role === "blue_lead"; // Fetch review queue count for the badge (only for roles that can see it) const { data: reviewQueue } = useQuery({ queryKey: ["techniques", "review-queue"], queryFn: () => getTechniques({ review_required: true }), enabled: canSeeReviewQueue, staleTime: 5 * 60 * 1000, // 5 min — don't hammer the API }); const reviewCount = reviewQueue?.length ?? 0; /** Returns true when the current user is allowed to see `item`. */ const canSee = (item: NavItem) => { if (!item.roles) return true; if (isAdmin) return true; return item.roles.includes(role); }; return ( ); }