TypeScript 55.4%
Python 43.2%
SQL 1.2%
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