SPB Git forge
7commits 1branches 0releases
229.0 KBsize
maindefault branch
12 days agolast push
TypeScript 91.8% HTML 3.2% JavaScript 3% SQL 1.4% CSS 0.7%
3.3 KB · 79 lines tsx
Raw Blame History
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