spb/social-runtime-crawler
Public
TypeScript 91.8%
HTML 3.2%
JavaScript 3%
SQL 1.4%
CSS 0.7%
1"use client";23import Link from "next/link";4import { usePathname } from "next/navigation";5import clsx from "clsx";6import { Activity, Braces, Database, Eye, Layers, Network, PlayCircle, Radar } from "lucide-react";7import { Brand } from "./Brand";8import { useApi, useLive } from "@/lib/api";910const NAV = [11 { href: "/", label: "Observatory", icon: Radar },12 { href: "/sessions", label: "Sessions", icon: Eye },13 { href: "/entities", label: "Entities", icon: Database },14 { href: "/schemas", label: "Runtime APIs", icon: Braces },15 { href: "/platforms", label: "Connectors", icon: Layers },16 { href: "/jobs", label: "Missions", icon: PlayCircle },17];1819export function Shell({ children }: { children: React.ReactNode }) {20 const path = usePathname();21 if (path.startsWith("/access")) return <>{children}</>;22 return (23 <div className="flex min-h-screen">24 <aside className="hidden md:flex w-60 shrink-0 flex-col border-r border-line bg-bg-2/60 backdrop-blur sticky top-0 h-screen">25 <div className="px-5 py-5 border-b border-line">26 <Link href="/">27 <Brand />28 </Link>29 </div>30 <nav className="p-3 space-y-1">31 {NAV.map((n) => {32 const active = n.href === "/" ? path === "/" : path.startsWith(n.href);33 return (34 <Link key={n.href} href={n.href} className={clsx("flex items-center gap-3 rounded-lg px-3 py-2 text-sm transition", active ? "bg-cyan/10 text-cyan" : "text-fg-2 hover:bg-white/5 hover:text-fg")}>35 <n.icon className="h-4 w-4" />36 {n.label}37 </Link>38 );39 })}40 </nav>41 <div className="mt-auto p-4 text-[11px] text-dim leading-relaxed border-t border-line">42 <div className="flex items-center gap-2 mb-2">43 <Network className="h-3.5 w-3.5" /> MacLustr · read-only crawler44 </div>45 Observes only what the authenticated account can legitimately see. No likes, follows, comments.46 </div>47 </aside>48 <div className="flex-1 min-w-0 flex flex-col">49 <TopBar />50 <main className="flex-1 p-4 md:p-6 space-y-6">{children}</main>51 </div>52 </div>53 );54}5556function TopBar() {57 const { data } = useApi<{ jobs_running: number; ts: string }>("/health", { refreshMs: 10_000 });58 const { connected, rate } = useLive({ max: 1 });59 return (60 <header className="sticky top-0 z-20 flex items-center gap-4 px-4 md:px-6 h-14 border-b border-line bg-bg/70 backdrop-blur">61 <div className="md:hidden">62 <Brand size="sm" />63 </div>64 <div className="ml-auto flex items-center gap-5 text-xs">65 <span className="flex items-center gap-2 text-fg-2">66 <span className={clsx("relative inline-block h-2 w-2 rounded-full", connected ? "bg-green text-green live-dot" : "bg-dim")} />67 {connected ? "live" : "offline"}68 <span className="mono text-dim">{rate}/min</span>69 </span>70 <span className="flex items-center gap-2 text-fg-2">71 <Activity className="h-3.5 w-3.5 text-amber" />72 <span className="mono">{data?.jobs_running ?? 0}</span> running73 </span>74 <span className="mono text-dim hidden sm:inline">{data?.ts ? new Date(data.ts).toLocaleTimeString("en-CA", { hour12: false }) : ""}</span>75 </div>76 </header>77 );78}79