import { useEffect, useState } from "react"; import { Link, Route, useLocation } from "wouter"; import { Activity, AlertTriangle, Globe, LayoutGrid, Megaphone, Moon, Plus, Search, Server, Settings, Sun } from "lucide-react"; import { BoardProvider, useBoard } from "@/board/store"; import { BoardSwitcher } from "@/components/BoardSwitcher"; import { Board } from "@/board/Board"; import { ServiceDialog } from "@/components/ServiceDialog"; import { WidgetDialog } from "@/components/WidgetDialog"; import { SettingsDialog } from "@/components/SettingsDialog"; import { CommandPalette } from "@/components/CommandPalette"; import { MonitorsPage } from "@/pages/MonitorsPage"; import { MonitorDetailPage } from "@/pages/MonitorDetailPage"; import { DomainsPage } from "@/pages/DomainsPage"; import { DomainDetailPage } from "@/pages/DomainDetailPage"; import { SystemsPage } from "@/pages/SystemsPage"; import { SystemDetailPage } from "@/pages/SystemDetailPage"; import { IncidentsPage } from "@/pages/IncidentsPage"; import { StatusPagesPage } from "@/pages/StatusPagesPage"; import { StatusPublicPage } from "@/pages/StatusPublicPage"; import { LoginPage } from "@/pages/LoginPage"; import { api } from "@/api"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; import { t, type Key } from "@/i18n"; import type { Item } from "@/types"; export default function App() { return ( ); } const NAV: { href: string; labelKey: Key; icon: typeof LayoutGrid }[] = [ { href: "/", labelKey: "nav.board", icon: LayoutGrid }, { href: "/monitors", labelKey: "nav.monitors", icon: Activity }, { href: "/domains", labelKey: "nav.domains", icon: Globe }, { href: "/systems", labelKey: "nav.systems", icon: Server }, { href: "/incidents", labelKey: "nav.incidents", icon: AlertTriangle }, { href: "/status", labelKey: "nav.status", icon: Megaphone }, ]; function Shell() { const board = useBoard(); const [loc] = useLocation(); // null = still checking; gate blocks everything except public status pages. const [auth, setAuth] = useState<{ enabled?: boolean; setup?: boolean; authed?: boolean } | null>(null); const [settingsOpen, setSettingsOpen] = useState(false); const [paletteOpen, setPaletteOpen] = useState(false); const [svcOpen, setSvcOpen] = useState(false); const [wdgOpen, setWdgOpen] = useState(false); const [editing, setEditing] = useState(); // ⌘K / Ctrl+K opens the palette; / does too when not typing in a field. useEffect(() => { const onKey = (e: KeyboardEvent) => { const typing = e.target instanceof HTMLElement && (e.target.tagName === "INPUT" || e.target.tagName === "TEXTAREA" || e.target.isContentEditable); if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") { e.preventDefault(); setPaletteOpen((o) => !o); } else if (e.key === "/" && !typing && !e.metaKey && !e.ctrlKey) { e.preventDefault(); setPaletteOpen(true); } }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, []); // Auth gate: check the session once, and again whenever the client sees a // 401 (middleware in api.ts dispatches dash:unauthorized). useEffect(() => { const load = () => api.GET("/api/auth/session").then(({ data }) => setAuth(data ?? {})); load(); window.addEventListener("dash:unauthorized", load); return () => window.removeEventListener("dash:unauthorized", load); }, []); // URL is the board source of truth: / -> default, /b/ -> that board. const setActiveSlug = board.setActiveSlug; useEffect(() => { setActiveSlug(loc.startsWith("/b/") ? decodeURIComponent(loc.slice(3)) : ""); }, [loc, setActiveSlug]); function openEditor(it: Item) { setEditing(it); if (it.kind === "widget") setWdgOpen(true); else setSvcOpen(true); } // Public status pages stay reachable when auth is on — the backend serves // their API unauthenticated, so the SPA must let the route through too. const publicPage = loc.startsWith("/status/"); if (auth?.enabled && !auth.authed && !publicPage) { return location.reload()} />; } return (
Dash
{(p) => } {(p) => } {(p) => } {(p) => } { setEditing(undefined); setSvcOpen(true); }} onAddWidget={() => { setEditing(undefined); setWdgOpen(true); }} onSettings={() => setSettingsOpen(true)} />
); }