57 lines
3.4 KiB
TypeScript
57 lines
3.4 KiB
TypeScript
import { events } from "@minecraft-account-manager/database";
|
|
import { eq } from "drizzle-orm";
|
|
import Link from "next/link";
|
|
import { notFound } from "next/navigation";
|
|
import { db } from "@/lib/database";
|
|
import { eventIpSummary } from "@/lib/event-ip-summary";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
|
|
export default async function EventDetailPage({ params }: { params: Promise<{ eventId: string }> }) {
|
|
const { eventId } = await params;
|
|
if (!UUID_PATTERN.test(eventId)) notFound();
|
|
const [event] = await db.select().from(events).where(eq(events.id, eventId)).limit(1);
|
|
if (!event) notFound();
|
|
const network = eventIpSummary(event.data);
|
|
|
|
return (
|
|
<main className="mx-auto max-w-5xl px-6 py-12">
|
|
<Link className="font-mono text-[10px] font-bold uppercase tracking-widest text-muted underline underline-offset-4" href="/admin/events">← Event explorer</Link>
|
|
<header className="mt-7 border-b border-line pb-8">
|
|
<p className="font-mono text-xs font-bold uppercase tracking-[0.25em] text-accent">CloudEvent detail</p>
|
|
<h1 className="mt-4 break-words font-display text-3xl font-black uppercase sm:text-5xl">{event.type}</h1>
|
|
<p className="mt-4 break-all font-mono text-xs text-muted">{event.id}</p>
|
|
</header>
|
|
|
|
<section className="mt-8 border border-line bg-panel p-6 shadow-[7px_7px_0_var(--color-shadow)]">
|
|
<h2 className="font-display text-2xl font-black uppercase">Envelope</h2>
|
|
<dl className="mt-5 grid gap-x-8 gap-y-5 sm:grid-cols-2">
|
|
<Detail label="Time"><time dateTime={event.time.toISOString()}>{event.time.toISOString()}</time></Detail>
|
|
<Detail label="Spec version">{event.specVersion}</Detail>
|
|
<Detail label="Source">{event.source}</Detail>
|
|
<Detail label="Subject">{event.subject ?? "Not provided"}</Detail>
|
|
<Detail label="Content type">{event.dataContentType}</Detail>
|
|
<Detail label="Data schema">{event.dataSchema ?? "Not provided"}</Detail>
|
|
<Detail label="Actor user">{event.actorUserId ? <Link className="underline underline-offset-4" href={`/admin/users/${event.actorUserId}`}>{event.actorUserId}</Link> : "Not provided"}</Detail>
|
|
<Detail label="Correlation ID">{event.correlationId ?? "Not provided"}</Detail>
|
|
<Detail label="IP address">{event.ipAddress ?? "Not provided"}</Detail>
|
|
<Detail label="Network">{network.classification || network.location ? `${network.classification ?? "unknown"} · ${network.location ?? "location unavailable"}` : "Not provided"}</Detail>
|
|
<Detail label="Published">{event.publishedAt ? event.publishedAt.toISOString() : "Pending publication"}</Detail>
|
|
<Detail label="Recorded">{event.createdAt.toISOString()}</Detail>
|
|
</dl>
|
|
</section>
|
|
|
|
<section className="mt-10">
|
|
<h2 className="font-display text-2xl font-black uppercase">Event data</h2>
|
|
<pre className="mt-4 overflow-x-auto border border-line bg-ink p-5 font-mono text-xs leading-6 text-canvas" tabIndex={0}>{JSON.stringify(event.data, null, 2)}</pre>
|
|
</section>
|
|
</main>
|
|
);
|
|
}
|
|
|
|
function Detail({ label, children }: { label: string; children: React.ReactNode }) {
|
|
return <div><dt className="font-mono text-[9px] font-bold uppercase tracking-widest text-muted">{label}</dt><dd className="mt-1 break-all text-sm">{children}</dd></div>;
|
|
}
|