/** The frame every page sits in: a left rail with the wordmark, the sections, * an on-this-page list, and the theme toggle. Content to the right. */ import { useEffect, useState, type ReactNode } from "react"; import { Link, NavLink, useLocation, useParams } from "react-router-dom"; import { useBuildCheck } from "../lib/useBuildCheck"; import { useTheme } from "../lib/theme"; import { forgetTokens } from "../lib/tokens"; import { CAPABILITIES, stageFor } from "../content/capabilities"; import { CommandPalette } from "./CommandPalette"; import { DotArt } from "./DotArt"; /** ⌘ on a Mac, Ctrl everywhere else. Showing the wrong one teaches the wrong * shortcut, which is worse than showing none. */ const MOD = typeof navigator !== "undefined" && /Mac|iP(hone|ad)/.test(navigator.platform) ? "\u2318K" : "Ctrl K"; const PAGES = [ { to: "/", label: "Overview" }, { to: "/case", label: "This case" }, { to: "/monitor", label: "Live monitor" }, { to: "/alerts", label: "Alert queue" }, { to: "/investigate", label: "Investigation graph" }, { to: "/peers", label: "Peer lookup" }, { to: "/redteam", label: "Red team" }, { to: "/custody", label: "Chain of custody" }, ]; export interface Anchor { id: string; label: string; } export function Shell({ anchors, children, variant = "rail", }: { anchors?: Anchor[]; children: ReactNode; /** "top" puts the navigation in a bar across the page, for the showcase. */ variant?: "rail" | "top"; }) { const [theme, toggleTheme] = useTheme(); const build = useBuildCheck(); const [paletteOpen, setPaletteOpen] = useState(false); const [activeAnchor, setActiveAnchor] = useState(null); const { pathname } = useLocation(); const params = useParams(); const stage = variant === "rail" ? stageFor(pathname) : null; // A case and a transaction are places too. Showing the open one in the rail // is how a reader learns the console has more than two pages. const open = params.id ? { label: `Case ${params.id.slice(0, 10)}…`, to: pathname } : params.txid ? { label: `Transaction ${params.txid.slice(0, 8)}…`, to: pathname } : params.peer ? { label: `Peer ${params.peer.slice(0, 15)}`, to: pathname } : null; useEffect(() => { const onKey = (event: KeyboardEvent) => { if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") { event.preventDefault(); setPaletteOpen(true); } }; addEventListener("keydown", onKey); return () => removeEventListener("keydown", onKey); }, []); // Which section the reader is in. Observed, not computed on scroll, so the // rail never costs a frame while the table is being worked. useEffect(() => { if (!anchors?.length) return; setActiveAnchor(anchors[0].id); const observer = new IntersectionObserver( (entries) => { const seen = entries.filter((e) => e.isIntersecting); if (seen.length) setActiveAnchor(seen[0].target.id); }, { rootMargin: "-10% 0px -70% 0px" }, ); for (const anchor of anchors) { const node = document.getElementById(anchor.id); if (node) observer.observe(node); } return () => observer.disconnect(); }, [anchors, pathname]); return (
{variant === "rail" && anchors?.length ? ( <>

On this page

) : null}
{variant === "rail" && (

Offline, synthetic data

)}
{(build.mismatch || build.unreachable) && (

{build.mismatch ? "The API is a different build" : "The API is not answering"}

{build.mismatch ? ( <> This page was built from {build.ours}; the server at {"/version"} reports{" "} {build.theirs}, started{" "} {build.startedAt?.replace("T", " ").replace("+00:00", " UTC")}. Restart it with offline/run_offline.sh before trusting anything on screen. ) : ( <> Nothing answered /version. Either the API is not running, or it is an older build without that endpoint — start it with{" "} offline/run_offline.sh. )}

)} {stage && ( stage {String(CAPABILITIES.indexOf(stage) + 1).padStart(2, "0")} of{" "} {CAPABILITIES.length} {stage.name} {stage.title} )} {children}
setPaletteOpen(false)} />
); }