import { Link, useLocation } from "react-router"; import { LayoutDashboard, Radio, Library, LayoutPanelTop, PanelsTopLeft, Users, MonitorSmartphone, History, Captions, Download, SlidersHorizontal, Server, Bot, ArrowLeft, Wrench, KeyRound, CalendarClock, ScrollText, Blocks, Puzzle, Send, } from "lucide-react"; import type { ReactNode } from "react"; import { SiloBrand } from "@/components/SiloBrand"; import { navigateToPluginRoute } from "@/lib/buildPluginHref"; import { useAdminPluginInstallations } from "@/hooks/queries/admin/plugins"; import { useAdminSessions } from "@/hooks/queries/admin/stats"; import { useBuildInfo } from "@/hooks/queries/admin/system"; import { pluginRouteHref } from "@/lib/pluginRouteHref"; interface SidebarItem { label: string; icon: ReactNode; href: string; exact?: boolean; badge?: ReactNode; // external=true means render as (full page navigation) instead of // react-router . Used for plugin routes mounted at /api/v1/plugins/... external?: boolean; } interface SidebarSection { label: string; items: SidebarItem[]; } interface AdminSidebarProps { onNavigate?: () => void; } function useSessionCount() { const { data: sessions = [] } = useAdminSessions(); return sessions.length; } export default function AdminSidebar({ onNavigate }: AdminSidebarProps) { const location = useLocation(); const sessionCount = useSessionCount(); const buildInfo = useBuildInfo(); let buildDisplay = "unavailable"; if (buildInfo.isPending && !buildInfo.data) { buildDisplay = "loading..."; } else if (buildInfo.isError) { buildDisplay = "load failed"; } else if (buildInfo.data?.available) { buildDisplay = buildInfo.data.display; } const sections: SidebarSection[] = [ { label: "Overview", items: [ { label: "Dashboard", icon: , href: "/admin", exact: true, }, { label: "Activity", icon: , href: "/admin/activity", badge: sessionCount > 0 ? {sessionCount} live : undefined, }, { label: "Logs", icon: , href: "/admin/logs", }, ], }, { label: "Content", items: [ { label: "Libraries", icon: , href: "/admin/libraries", }, { label: "Collections", icon: , href: "/admin/collections", }, { label: "Requests", icon: , href: "/admin/requests", }, { label: "Sections", icon: , href: "/admin/sections", }, { label: "Subtitles", icon: , href: "/admin/subtitles", }, ], }, { label: "Users", items: [ { label: "Users", icon: , href: "/admin/users", }, { label: "Devices", icon: , href: "/admin/devices", }, { label: "Playback History", icon: , href: "/admin/history", }, { label: "History Import", icon: , href: "/admin/history-import", }, ], }, { label: "Server", items: [ { label: "Scheduled Tasks", icon: , href: "/admin/tasks", }, { label: "Nodes", icon: , href: "/admin/nodes", }, { label: "Maintenance", icon: , href: "/admin/maintenance", }, { label: "Plugins", icon: , href: "/admin/plugins", }, { label: "Settings", icon: , href: "/admin/settings", }, { label: "Recommendations", icon: , href: "/admin/recommendations", }, { label: "API Keys", icon: , href: "/admin/api-keys", }, ], }, ]; // Use the admin installations endpoint, not /settings/plugins — the user // settings endpoint filters to plugins that expose user settings / a user- // navigable route, which excludes admin-only plugins like arrproxy and // arrouter. The admin sidebar needs the full installation list to render // its "Plugin Apps" group. const { data: adminInstallations } = useAdminPluginInstallations(); const adminPluginItems: SidebarItem[] = []; for (const inst of adminInstallations ?? []) { if (!inst.enabled) continue; for (const route of inst.routes ?? []) { if (!route.navigable || route.navigation_kind !== "admin") continue; adminPluginItems.push({ label: route.navigation_label || inst.plugin_id, icon: , href: pluginRouteHref(inst.id, route.path), external: true, }); } } if (adminPluginItems.length > 0) { sections.push({ label: "Plugin Apps", items: adminPluginItems }); } function isActive(item: SidebarItem) { if (item.exact) return location.pathname === item.href; return location.pathname === item.href || location.pathname.startsWith(`${item.href}/`); } return ( ); }