TypeScript 97.5%
SQL 1.4%
Python 0.8%
1import Link from "next/link";2import { requireAdmin } from "@/lib/session";3import { internalApi, InternalApiError } from "@/lib/api";4import { getEnvPresence, getFeatureFlags, getIncidents, getTableCounts } from "@/lib/queries/admin";5import { formatNumber, titleCase } from "@/lib/format";6import { PageHeader } from "@/components/ui/page-header";7import { Alert } from "@/components/ui/alert";8import { Badge, StatusBadge } from "@/components/ui/badge";9import { Button } from "@/components/ui/button";10import { Table, TableBody, TableCell, TableEmpty, TableHead, TableHeader, TableRow } from "@/components/ui/table";11import { DateCell, KV, Mono, Panel, numCell } from "@/components/admin/primitives";12import { FlagSwitch } from "@/components/admin/flag-controls";13import { NewIncidentDialog, ResolveIncidentButton } from "@/components/admin/incident-controls";1415export const dynamic = "force-dynamic";1617async function safe<T>(fn: () => Promise<T>): Promise<{ ok: true; value: T } | { ok: false; error: string }> {18 try {19 return { ok: true, value: await fn() };20 } catch (e) {21 return { ok: false, error: e instanceof InternalApiError ? `${e.code}: ${e.message}` : (e as Error).message };22 }23}2425export default async function AdminSystemPage() {26 await requireAdmin();27 const [health, ready, dbStats, flags, incidents] = await Promise.all([safe(() => internalApi.health()), safe(() => internalApi.ready()), safe(getTableCounts), getFeatureFlags(), getIncidents(50)]);28 const env = getEnvPresence();29 const groups = Array.from(new Set(env.map((e) => e.group)));30 const openIncidents = incidents.filter((i) => !i.resolvedAt).length;3132 return (33 <>34 <PageHeader eyebrow="Operations" title="System" description="Service health, database footprint, environment presence, feature flags and public status incidents." actions={<NewIncidentDialog />} />3536 <div className="grid gap-6 lg:grid-cols-2">37 <Panel title="API service" description="GET /health and /ready on the Fastify service, called server-side with the internal token.">38 {health.ok ? (39 <KV40 cols={2}41 items={[42 { label: "Health", value: <StatusBadge status={health.value.status === "ok" ? "healthy" : health.value.status} /> },43 { label: "Version", value: health.value.version, mono: true },44 { label: "Readiness", value: ready.ok ? <StatusBadge status={ready.value.status === "ok" || ready.value.status === "ready" ? "healthy" : ready.value.status} /> : <StatusBadge status="down" /> },45 { label: "Available networks", value: ready.ok && ready.value.available_networks.length ? ready.value.available_networks.join(", ") : "none" },46 {47 label: "Readiness checks",48 span: true,49 value: ready.ok ? (50 Object.keys(ready.value.checks).length ? (51 <ul className="flex flex-wrap gap-1.5">52 {Object.entries(ready.value.checks).map(([k, v]) => (53 <li key={k}>54 <Badge variant={v ? "success" : "danger"} dot>55 {k}56 </Badge>57 </li>58 ))}59 </ul>60 ) : (61 <span className="text-fg-muted">no check details (degraded)</span>62 )63 ) : (64 <span className="text-danger">{ready.error}</span>65 ),66 },67 ]}68 />69 ) : (70 <Alert variant="danger" title="API unreachable">71 {health.error}72 </Alert>73 )}74 </Panel>7576 <Panel title="Database" description={dbStats.ok ? `${dbStats.value.pgVersion} · ${dbStats.value.dbSize}` : undefined} flush>77 {dbStats.ok ? (78 <Table>79 <TableHeader>80 <TableRow>81 <TableHead>Table</TableHead>82 <TableHead className="text-right">Rows</TableHead>83 </TableRow>84 </TableHeader>85 <TableBody>86 {Object.entries(dbStats.value.counts).map(([t, n]) => (87 <TableRow key={t}>88 <TableCell>89 <Mono>{t}</Mono>90 </TableCell>91 <TableCell className={numCell}>{formatNumber(n)}</TableCell>92 </TableRow>93 ))}94 </TableBody>95 </Table>96 ) : (97 <div className="p-4">98 <Alert variant="danger" title="Database query failed">99 {dbStats.error}100 </Alert>101 </div>102 )}103 </Panel>104 </div>105106 <Panel title="Environment presence" description="Whether each variable is set in the web process. Values are never read or displayed. The API service has its own environment — provider credentials must be set there too." className="mt-6">107 <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">108 {groups.map((g) => (109 <div key={g}>110 <h3 className="mb-1.5 text-[12px] font-semibold uppercase tracking-wide text-fg-subtle">{g}</h3>111 <ul className="space-y-1">112 {env113 .filter((e) => e.group === g)114 .map((e) => (115 <li key={e.key} className="flex items-center justify-between gap-2 text-[12.5px]">116 <Mono>{e.key}</Mono>117 {e.present ? <Badge variant="success" dot>set</Badge> : <Badge variant="outline">missing</Badge>}118 </li>119 ))}120 </ul>121 </div>122 ))}123 </div>124 </Panel>125126 <div className="mt-6 grid gap-6 xl:grid-cols-2">127 <Panel title="Feature flags" right={<Button asChild variant="ghost" size="xs"><Link href="/admin/flags">Manage →</Link></Button>} flush>128 <Table>129 <TableHeader>130 <TableRow>131 <TableHead>Key</TableHead>132 <TableHead>Description</TableHead>133 <TableHead>Targeting</TableHead>134 <TableHead>Enabled</TableHead>135 </TableRow>136 </TableHeader>137 <TableBody>138 {flags.length === 0 ? (139 <TableEmpty colSpan={4}>140 No flags. <Link href="/admin/flags" className="text-accent hover:underline">Create one</Link>.141 </TableEmpty>142 ) : (143 flags.map((f) => (144 <TableRow key={f.key}>145 <TableCell>146 <Mono>{f.key}</Mono>147 </TableCell>148 <TableCell className="max-w-[260px] truncate text-fg-muted">{f.description ?? "—"}</TableCell>149 <TableCell className="text-[12px] text-fg-muted">150 {f.plans?.length ? `plans: ${f.plans.join(", ")}` : null}151 {f.plans?.length && f.organizationIds?.length ? " · " : null}152 {f.organizationIds?.length ? `${f.organizationIds.length} org(s)` : null}153 {!f.plans?.length && !f.organizationIds?.length ? "everyone" : null}154 </TableCell>155 <TableCell>156 <FlagSwitch flagKey={f.key} enabled={f.enabled} />157 </TableCell>158 </TableRow>159 ))160 )}161 </TableBody>162 </Table>163 </Panel>164165 <Panel166 title={167 <span className="flex items-center gap-2">168 Status incidents {openIncidents ? <Badge variant="danger" dot>{openIncidents} open</Badge> : <Badge variant="success" dot>all resolved</Badge>}169 </span>170 }171 description="Shown on the public /status page. Write for customers; never name upstream providers."172 flush173 >174 <Table>175 <TableHeader>176 <TableRow>177 <TableHead>Started</TableHead>178 <TableHead>Component</TableHead>179 <TableHead>Severity</TableHead>180 <TableHead>Title</TableHead>181 <TableHead>Status</TableHead>182 <TableHead></TableHead>183 </TableRow>184 </TableHeader>185 <TableBody>186 {incidents.length === 0 ? (187 <TableEmpty colSpan={6}>No incidents recorded. The status page shows all systems operational.</TableEmpty>188 ) : (189 incidents.map((i) => (190 <TableRow key={i.id} className={i.resolvedAt ? "opacity-70" : undefined}>191 <TableCell>192 <DateCell value={i.startedAt} relative={false} />193 </TableCell>194 <TableCell>195 <Badge variant="outline">{titleCase(i.component)}</Badge>196 </TableCell>197 <TableCell>198 <Badge variant={i.severity === "critical" || i.severity === "major" ? "danger" : i.severity === "maintenance" ? "info" : "warning"}>{i.severity}</Badge>199 </TableCell>200 <TableCell className="max-w-[280px]">201 <div className="truncate font-medium" title={i.title}>202 {i.title}203 </div>204 {i.body ? (205 <div className="truncate text-[12px] text-fg-muted" title={i.body}>206 {i.body}207 </div>208 ) : null}209 </TableCell>210 <TableCell>211 {i.resolvedAt ? (212 <span className="text-[12px] text-fg-muted">213 resolved <DateCell value={i.resolvedAt} />214 </span>215 ) : (216 <Badge variant="danger" dot>217 open218 </Badge>219 )}220 </TableCell>221 <TableCell className="text-right">222 <ResolveIncidentButton id={i.id} resolved={Boolean(i.resolvedAt)} />223 </TableCell>224 </TableRow>225 ))226 )}227 </TableBody>228 </Table>229 </Panel>230 </div>231 </>232 );233}234