page.tsx265 lines · main
1import type { Metadata } from 'next';
2import Link from 'next/link';
3
4import { BackgroundGrid } from '../../components/marketing/background-grid';
5import { SiteFooter, WebDownLink } from '../../components/marketing/site-footer';
6import { SiteHeader } from '../../components/marketing/site-header';
7import { getSessionUser } from '../../lib/session';
8
9export const metadata: Metadata = {
10 title: 'status — briven',
11 description: 'live health of every briven service. probes run when this page is rendered.',
12};
13
14export const dynamic = 'force-dynamic';
15export const revalidate = 0;
16
17interface ServiceProbe {
18 name: string;
19 url: string;
20 description: string;
21 expandChecks?: boolean;
22}
23
24const PROBES: readonly ServiceProbe[] = [
25 {
26 name: 'api',
27 url: process.env.BRIVEN_STATUS_API_URL ?? 'https://api.briven.tech/ready',
28 description: 'control plane — accounts, projects, billing, deploy intake',
29 expandChecks: true,
30 },
31 {
32 name: 'realtime',
33 url: process.env.BRIVEN_STATUS_REALTIME_URL ?? 'https://realtime.briven.tech/ready',
34 description: 'reactive query websocket service',
35 },
36 {
37 name: 'web',
38 url: process.env.BRIVEN_STATUS_WEB_URL ?? 'https://briven.tech/health',
39 description: 'marketing site + signed-in dashboard',
40 },
41 {
42 name: 'docs',
43 url: process.env.BRIVEN_STATUS_DOCS_URL ?? 'https://docs.briven.tech',
44 description: 'developer docs + sdk reference',
45 },
46];
47
48interface ProbeResult {
49 name: string;
50 description: string;
51 ok: boolean;
52 status: number | null;
53 durationMs: number;
54 detail: string | null;
55 checks: Record<string, 'ok' | 'unreachable' | 'not_configured' | 'degraded'> | null;
56}
57
58async function probe(svc: ServiceProbe): Promise<ProbeResult> {
59 const t0 = Date.now();
60 try {
61 const res = await fetch(svc.url, {
62 signal: AbortSignal.timeout(3000),
63 headers: { accept: 'application/json' },
64 cache: 'no-store',
65 });
66 const durationMs = Date.now() - t0;
67 const body = await res.text().catch(() => '');
68 let detail: string | null = null;
69 let checks: ProbeResult['checks'] = null;
70 if (svc.expandChecks) {
71 try {
72 const parsed = JSON.parse(body) as { checks?: Record<string, string> };
73 if (parsed.checks) checks = parsed.checks as ProbeResult['checks'];
74 } catch {
75 // non-JSON body; checks stay null
76 }
77 }
78 if (!res.ok && !detail) {
79 detail = body.slice(0, 200) || null;
80 if (detail && body.length > 200) detail = `${detail}…`;
81 }
82 return {
83 name: svc.name,
84 description: svc.description,
85 ok: res.ok,
86 status: res.status,
87 durationMs,
88 detail,
89 checks,
90 };
91 } catch (err) {
92 return {
93 name: svc.name,
94 description: svc.description,
95 ok: false,
96 status: null,
97 durationMs: Date.now() - t0,
98 detail: err instanceof Error ? err.message : 'unreachable',
99 checks: null,
100 };
101 }
102}
103
104export default async function StatusPage() {
105 const [user, probes] = await Promise.all([
106 getSessionUser().catch(() => null),
107 Promise.all(PROBES.map(probe)),
108 ]);
109 const allOk = probes.every((p) => p.ok);
110 const anyDown = probes.some((p) => !p.ok);
111
112 return (
113 <main className="relative min-h-dvh overflow-hidden bg-[var(--color-bg)] text-[var(--color-text)]">
114 <BackgroundGrid />
115 <SiteHeader user={user} />
116
117 <section className="relative z-10 mx-auto w-full max-w-4xl px-6 pb-12 pt-16 sm:pt-20">
118 <p className="font-mono uppercase tracking-[0.12em] text-[var(--color-primary)] text-[var(--text-xs)]">
119 status
120 </p>
121 <h1 className="mt-4 font-sans font-medium leading-[1.05] tracking-[-0.03em] text-[var(--color-text)] text-[var(--text-display-3)]">
122 briven, live.
123 </h1>
124 <p className="mt-4 max-w-2xl leading-[1.6] text-[var(--color-text-muted)] text-[var(--text-body)]">
125 probes run when this page is rendered — no cached numbers, no marketing dashboards. for
126 incident history and post-mortems, see the{' '}
127 <Link
128 href="https://docs.briven.tech/status"
129 className="text-[var(--color-text-link)] underline-offset-2 hover:underline"
130 >
131 full status page
132 </Link>{' '}
133 on docs.
134 </p>
135 <p className="mt-4 font-mono text-xs text-[var(--color-text-subtle)]">
136 every briven server &amp; domain is guarded around the clock by{' '}
137 <WebDownLink>web down</WebDownLink> — our independent uptime watchdog.
138 </p>
139 </section>
140
141 <section className="relative z-10 mx-auto w-full max-w-4xl px-6 pb-4">
142 <div className="rounded-[var(--radius-md)] border border-[var(--color-border-subtle)] bg-[var(--color-surface)] p-5 font-mono text-xs text-[var(--color-text-muted)]">
143 <p className="text-[var(--color-text)]">what if sign-in feels broken?</p>
144 <ul className="mt-3 list-disc space-y-2 pl-5">
145 <li>
146 check <code className="text-[var(--color-text)]">redis</code> under the api probe
147 above — rate limits need it healthy; logins can still work if only cache is down.
148 </li>
149 <li>
150 already-open sessions usually keep working; new sign-ups / magic links need the mail
151 path.
152 </li>
153 <li>
154 we post short incident notes here and on{' '}
155 <Link
156 href="https://docs.briven.tech/status"
157 className="text-[var(--color-text-link)] underline-offset-2 hover:underline"
158 >
159 docs status
160 </Link>{' '}
161 when something is customer-visible.
162 </li>
163 </ul>
164 </div>
165 </section>
166
167 <section className="relative z-10 mx-auto w-full max-w-4xl px-6">
168 <div
169 className={`rounded-[var(--radius-md)] border p-5 font-mono text-sm ${
170 allOk
171 ? 'border-[var(--color-border-primary)] bg-[var(--color-primary-subtle)]'
172 : 'border-[var(--color-error)] bg-[var(--color-surface)]'
173 }`}
174 >
175 <p className="text-[var(--color-text)]">
176 <strong>
177 {allOk
178 ? 'all systems operational'
179 : anyDown
180 ? 'incident in progress'
181 : 'partial degradation'}
182 </strong>
183 </p>
184 <p className="mt-1 text-xs text-[var(--color-text-muted)]">
185 checked at {new Date().toISOString()} · ttl 0s
186 </p>
187 </div>
188 </section>
189
190 <section className="relative z-10 mx-auto mt-8 w-full max-w-4xl px-6 pb-20">
191 <ul className="flex flex-col gap-3">
192 {probes.map((p) => (
193 <li
194 key={p.name}
195 className="rounded-[var(--radius-md)] border border-[var(--color-border-subtle)] bg-[var(--color-surface)] p-5"
196 >
197 <div className="flex items-start justify-between gap-4">
198 <div>
199 <p className="font-mono text-sm text-[var(--color-text)]">{p.name}</p>
200 <p className="mt-1 font-mono text-xs text-[var(--color-text-muted)]">
201 {p.description}
202 </p>
203 </div>
204 <div className="text-right">
205 <span
206 className={`inline-flex items-center gap-2 rounded-[var(--radius-full)] border px-2 py-0.5 font-mono text-[10px] uppercase tracking-wider ${
207 p.ok
208 ? 'border-[var(--color-border-primary)] bg-[var(--color-primary-subtle)] text-[var(--color-primary)]'
209 : 'border-[var(--color-error)] text-[var(--color-error)]'
210 }`}
211 >
212 <span
213 className={`h-1.5 w-1.5 rounded-full ${p.ok ? 'bg-[var(--color-primary)]' : 'bg-[var(--color-error)]'}`}
214 />
215 {p.ok ? 'ok' : 'down'}
216 </span>
217 <p className="mt-1.5 font-mono text-[10px] text-[var(--color-text-subtle)]">
218 {p.status ?? '—'} · {p.durationMs}ms
219 </p>
220 </div>
221 </div>
222
223 {p.checks ? (
224 <ul className="mt-4 grid grid-cols-2 gap-2 border-t border-[var(--color-border-subtle)] pt-4 sm:grid-cols-3 md:grid-cols-4">
225 {Object.entries(p.checks).map(([k, v]) => (
226 <li key={k} className="flex items-center gap-2">
227 <span
228 className={`h-1.5 w-1.5 rounded-full ${
229 v === 'ok'
230 ? 'bg-[var(--color-primary)]'
231 : v === 'degraded'
232 ? 'bg-[var(--color-warning)]'
233 : 'bg-[var(--color-error)]'
234 }`}
235 />
236 <span className="font-mono text-xs text-[var(--color-text-muted)]">
237 {k}
238 </span>
239 <span className="ml-auto font-mono text-[10px] text-[var(--color-text-subtle)]">
240 {v}
241 </span>
242 </li>
243 ))}
244 </ul>
245 ) : null}
246
247 {p.detail ? (
248 <pre className="mt-3 overflow-x-auto rounded-[var(--radius-sm)] bg-[var(--color-code-bg)] p-3 font-mono text-[11px] text-[var(--color-code-text)]">
249 {p.detail}
250 </pre>
251 ) : null}
252 </li>
253 ))}
254 </ul>
255
256 <p className="mt-8 font-mono text-xs text-[var(--color-text-subtle)]">
257 probes timeout at 3000ms. /ready returns the service&apos;s view of its dependencies;
258 /health only confirms the process is alive.
259 </p>
260 </section>
261
262 <SiteFooter />
263 </main>
264 );
265}