SPB Git forge

spb/websensor

Public
33commits 1branches 0releases
3.4 MBsize
maindefault branch
10 days agolast push
TypeScript 55.4% Python 43.2% SQL 1.2%
3.7 KB · 73 lines tsx
Raw Blame History
1import Link from "next/link";2import type { Metadata } from "next";3import { Chip, Empty, ExtLink, PageHeader, Panel, Table, Td } from "@/components/ui";4import { api } from "@/lib/api";5import { fmtBytes, shortHash, utcDateTime } from "@/lib/format";6import { CompareForm } from "./compare-form";78export const dynamic = "force-dynamic";9export const metadata: Metadata = { title: "URL history", robots: { index: false } };1011export default async function UrlPage({ searchParams }: { searchParams: Promise<{ u?: string }> }) {12  const { u } = await searchParams;13  if (!u) {14    return (15      <>16        <PageHeader title="URL history" description="Paste a monitored URL to see its snapshots and changes." />17        <form action="/url" className="flex gap-2">18          <input name="u" placeholder="https://…" className="h-8 w-full max-w-xl rounded-md border border-line bg-panel px-2.5 font-mono text-[12.5px]" />19          <button type="submit" className="h-8 rounded-md border border-line bg-panel-2 px-3 text-[12.5px]">Look up</button>20        </form>21      </>22    );23  }24  const d = await api.urlHistory(u);25  const info = d?.url;26  return (27    <>28      <PageHeader kicker={<Link href={`/domain/${info?.domain ?? ""}`} className="font-mono hover:underline">{info?.domain}</Link>} title={<span className="break-all font-mono text-lg">{u}</span>} actions={<><ExtLink href={u} className="text-[12px]">Open ↗</ExtLink>{info?.status && <Chip tone={info.status === "active" ? "ok" : info.status === "removed" ? "danger" : "warn"}>{info.status}</Chip>}</>} />29      <div className="grid gap-4 lg:grid-cols-[1fr_1fr]">30        <Panel title={`History · ${d?.history.length ?? 0}`} dense>31          {d?.history.length ? (32            <Table head={["When", "Kind", "Detail"]}>33              {d.history.map((h) => (34                <tr key={h.id}>35                  <Td mono className="text-fg-subtle">{utcDateTime(h.at)}</Td>36                  <Td><Chip tone={h.kind === "event" ? "signal" : h.kind === "removed" ? "danger" : h.kind === "change" ? "info" : "default"}>{h.kind}</Chip></Td>37                  <Td>38                    {h.event_slug ? <Link href={`/event/${h.event_slug}`} className="hover:underline">{h.event_title}</Link> : h.change_id ? <span className="text-fg-muted">{h.change_kind} change · signal {h.signal?.toFixed(2)}</span> : h.note ?? <span className="text-fg-subtle">snapshot {h.snapshot_id}</span>}39                  </Td>40                </tr>41              ))}42            </Table>43          ) : (44            <Empty>No history for this URL.</Empty>45          )}46        </Panel>47        <div className="flex flex-col gap-4">48          <Panel title="Compare any two versions">49            <CompareForm snapshots={(d?.snapshots ?? []).map((s) => ({ id: s.id, label: `${utcDateTime(s.captured_at)} · ${shortHash(s.canonical_hash, 8)}` }))} />50          </Panel>51          <Panel title={`Snapshots · ${d?.snapshots.length ?? 0}`} dense>52            {d?.snapshots.length ? (53              <Table head={["Captured", "HTTP", "Size", "Hash", ""]}>54                {d.snapshots.map((s) => (55                  <tr key={s.id}>56                    <Td mono className="text-fg-subtle">{utcDateTime(s.captured_at)}</Td>57                    <Td mono>{s.http_status ?? "—"}</Td>58                    <Td mono>{fmtBytes(s.content_length)}</Td>59                    <Td mono className="text-fg-subtle">{shortHash(s.canonical_hash)}</Td>60                    <Td><a href={`/api/v1/snapshots/${s.id}?raw=1`} target="_blank" rel="noopener noreferrer" className="text-info hover:underline">raw</a></Td>61                  </tr>62                ))}63              </Table>64            ) : (65              <Empty>No snapshots.</Empty>66            )}67          </Panel>68        </div>69      </div>70    </>71  );72}73