Santé système
Page admin qui ping les intégrations externes (Stripe, Resend, OneSignal, DB) et affiche des métriques 24h.
Page /admin/system — admin uniquement. Vue d'ensemble santé +
métriques opérationnelles des dernières 24h.
Checks externes
API GET /api/admin/system/health ping en parallèle :
- Database —
prisma.$queryRaw\SELECT 1`` - Stripe —
stripe.balance.retrieve()(siSTRIPE_SECRET_KEYconfigurée) - Resend —
GET https://api.resend.com/domains(siRESEND_API_KEY) - OneSignal —
GET https://api.onesignal.com/apps/<id>(si configuré)
Chaque check tourne avec un timeout 5s via AbortSignal.timeout(5000),
en parallèle (Promise.all). Une dépendance qui hang ne bloque pas la
route — la page reste utile précisément quand quelque chose va mal.
Service non configuré → ok: true avec detail: "not configured" (pas
considéré comme un échec).
Métriques 24h
- Dernier cron exécuté (lu depuis
AuditLogoùaction LIKE 'cron.%') - Broadcasts envoyés / en erreur (depuis
Broadcast.sentCount+errorCount) - Bounces emails (depuis
EmailLogstatus="bounced") - Plaintes spam (depuis
EmailLogstatus="complained")
Tout warning visuel si errors > 0 ou bounces > 0.
Statut global
overall = "healthy" | "degraded" — calculé sur l'agrégat de tous les
checks externes. Visible en badge en haut de page.
Bouton Actualiser
fetch("/api/admin/system/health", { cache: "no-store" }) — pas de
revalidation automatique, seulement à la demande.
Étendre
Pour ajouter un check (ex. Redis, Sentry, etc.), édite
app/api/admin/system/health/route.ts. Le helper timed(fn) wrappe
n'importe quelle promise async avec timeout + mesure de latence.
const myCheck = await timed(async (signal) => {
const res = await fetch("https://api.monservice.com/health", { signal });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
});
checks.push({ name: "monservice", ok: myCheck.ok, latencyMs: myCheck.ms });