error.tsx58 lines · main
1'use client';
2
3import { useEffect } from 'react';
4
5import { TriangleAlertIcon } from '@/components/ui/triangle-alert';
6
7/**
8 * Cockpit-wide error boundary. Any data-fetching admin page that throws
9 * (e.g. an /v1/admin/* call fails) lands here instead of a blank crash —
10 * a calm, themed note plus a retry, never a stack trace or fabricated
11 * data. Covers overview, billing, health, and mcp in one place.
12 *
13 * Must be a Client Component (Next.js error boundary contract).
14 */
15export default function AdminCockpitError({
16 error,
17 reset,
18}: {
19 error: Error & { digest?: string };
20 reset: () => void;
21}) {
22 useEffect(() => {
23 // Surface to the browser console for the operator; no PII rendered.
24 console.error('[cockpit] page error:', error);
25 }, [error]);
26
27 return (
28 <section
29 role="alert"
30 className="flex flex-col items-start gap-4 rounded-[var(--radius-md)] border border-[var(--color-border-subtle)] bg-[var(--color-surface)] p-6"
31 >
32 <div className="flex items-center gap-2">
33 <span className="text-[var(--color-warning)]">
34 <TriangleAlertIcon size={20} />
35 </span>
36 <h1 className="font-mono text-lg tracking-tight text-[var(--color-text)]">
37 this view couldn&apos;t load
38 </h1>
39 </div>
40 <p className="max-w-prose font-mono text-sm text-[var(--color-text-muted)]">
41 the cockpit reached the api but didn&apos;t get an answer it could trust — so it&apos;s
42 showing nothing rather than a fake number. this is usually a transient blip; try again.
43 </p>
44 {error.digest ? (
45 <p className="font-mono text-[10px] text-[var(--color-text-subtle)]">
46 reference: {error.digest}
47 </p>
48 ) : null}
49 <button
50 type="button"
51 onClick={reset}
52 className="rounded-[var(--radius-md)] border border-[var(--color-border)] px-3 py-1.5 font-mono text-xs text-[var(--color-text-muted)] transition hover:border-[var(--color-border-strong)] hover:text-[var(--color-text)]"
53 >
54 try again
55 </button>
56 </section>
57 );
58}