import { BrowserRouter, Routes, Route, NavLink, useLocation, Outlet, Navigate, } from "react-router-dom"; import { QueryClient, QueryClientProvider, useQuery, } from "@tanstack/react-query"; import { useEffect, useMemo, useState } from "react"; import type { LucideIcon } from "lucide-react"; import { AuthProvider, useAuth } from "react-oidc-context"; import { Dashboard } from "./pages/Dashboard"; import { NamedDashboardPage } from "./pages/NamedDashboardPage"; import { Settings } from "./pages/Settings"; import { ServicePage } from "./pages/ServicePage"; import { ServiceTypePage } from "./pages/ServiceTypePage"; import { getOidcConfig, isOidcConfigured, setAccessToken } from "./auth"; import { fetchAppVersion } from "./api/client"; import { FRONTEND_VERSION_LABEL } from "./version"; import { usePersistentState } from "./hooks/usePersistentState"; import { useIsMobile } from "./hooks/useIsMobile"; import { useServiceInstances } from "./hooks/useServices"; import { useDashboards } from "./hooks/useDashboards"; import { configuredNavEntries, remoteMachineNavEntries, } from "./integrations/navEntries"; import { Button } from "@/components/ui/button"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger, } from "@/components/ui/sheet"; import { LayoutDashboard, Settings as SettingsIcon, Menu, Sun, Moon, LogOut, ChevronLeft, ChevronRight, LayoutTemplate, } from "lucide-react"; const queryClient = new QueryClient({ defaultOptions: { queries: { retry: 1, refetchOnWindowFocus: false, refetchIntervalInBackground: false, }, }, }); function useDarkMode() { const [darkMode, setDarkMode] = usePersistentState( "dark-mode", () => window.matchMedia("(prefers-color-scheme: dark)").matches, ); useEffect(() => { const root = document.documentElement; if (darkMode) { root.classList.add("dark"); } else { root.classList.remove("dark"); } }, [darkMode]); return [darkMode, () => setDarkMode((prev) => !prev)] as const; } // Navigation items are data-driven (spec R1). Built from configured services + dashboards. interface NavItem { path: string; label: string; icon: LucideIcon; } function useNavItems() { const { data: services = [] } = useServiceInstances(); const { data: dashboards = [] } = useDashboards(); return useMemo(() => { const configuredTypes = new Set( services.filter((s) => s.enabled).map((s) => s.service_type), ); const serviceEntries = [ ...configuredNavEntries(configuredTypes), ...remoteMachineNavEntries(services), ].map((entry) => ({ path: entry.path, label: entry.label, icon: entry.icon, })); const dashboardEntries = dashboards.map((d) => ({ path: `/d/${d.slug}`, label: d.label, icon: LayoutTemplate, })); return [ { path: "/", label: "Dashboard", icon: LayoutDashboard }, ...dashboardEntries, ...serviceEntries, { path: "/settings", label: "Settings", icon: SettingsIcon }, ]; }, [services, dashboards]); } function Sidebar({ collapsed, onToggle, isMobile, }: { collapsed: boolean; onToggle: () => void; isMobile: boolean; }) { const location = useLocation(); const navItems = useNavItems(); if (isMobile) return null; return ( ); } function MobileDrawer() { const [open, setOpen] = useState(false); const location = useLocation(); const navItems = useNavItems(); return (
M
Manage
); } function TopBar({ darkMode, authLabel, onSignOut, onToggleDarkMode, }: { darkMode: boolean; authLabel?: string; onSignOut?: () => void; onToggleDarkMode: () => void; }) { const location = useLocation(); const { data: appVersion } = useQuery({ queryKey: ["app-version"], queryFn: fetchAppVersion, staleTime: 60 * 60 * 1000, }); const backendLabel = appVersion?.backend_label || "…"; const navItems = useNavItems(); const pageTitle = navItems.find((item) => item.path === location.pathname)?.label || "Dashboard"; return (

{pageTitle}

FE {FRONTEND_VERSION_LABEL} BE {backendLabel} {authLabel && ( {authLabel} )} {onSignOut && ( )}
); } function ShellLayout({ darkMode, authLabel, onSignOut, onToggleDarkMode, }: { darkMode: boolean; authLabel?: string; onSignOut?: () => void; onToggleDarkMode: () => void; }) { const [sidebarCollapsed, setSidebarCollapsed] = useState(false); const isMobile = useIsMobile(); return (
setSidebarCollapsed(!sidebarCollapsed)} isMobile={isMobile} />
); } function LoadingScreen({ label }: { label: string }) { return (

{label}

); } function SignInScreen({ onSignIn }: { onSignIn: () => void }) { return (

Sign in required

Use your Authentik account to access Manage.

); } function AuthenticatedApp() { const auth = useAuth(); const [darkMode, toggleDarkMode] = useDarkMode(); useEffect(() => { setAccessToken(auth.user?.access_token ?? null); }, [auth.user?.access_token]); const authLabel = useMemo(() => { const profile = auth.user?.profile as Record | undefined; return String( profile?.name ?? profile?.preferred_username ?? profile?.email ?? auth.user?.profile?.sub ?? "Authenticated", ); }, [auth.user]); if (auth.isLoading || auth.activeNavigator) { return ; } if (auth.error) { return ( ); } if (!auth.isAuthenticated) { return void auth.signinRedirect()} />; } return ( void auth.signoutRedirect()} onToggleDarkMode={toggleDarkMode} /> ); } function NotFoundPage() { return (

Not found

This page doesn't exist.

); } function AppInner() { const [darkMode, toggleDarkMode] = useDarkMode(); return ( {isOidcConfigured() ? ( }> } /> } /> } /> } /> } /> } /> } /> ) : ( } > } /> } /> } /> } /> } /> } /> } /> )} ); } export default function App() { return ; }