Notifications
Quand on gere une plateforme, il est impossible de surveiller en permanence chaque metrique, chaque inscription, chaque ticket. Le centre de notifications joue le role de tour de controle : il rassemble tous les evenements qui meritent votre attention dans une interface unique, pour que vous puissiez reagir au bon moment sans rien rater.
Le systeme combine deux types de notifications :
- Notifications persistantes : stockees en base de donnees (table
notifications), generees automatiquement parnotifyAdmins()pour chaque evenement admin (securite, comptes, billing, serveur, import) ainsi que par les tickets support. Elles restent visibles jusqu'a ce que vous les lisiez ou les supprimiez. Chaque notification inclut un lien de navigation directe vers la page concernee. - Notifications synthetiques : calculees a la volee (en temps reel) a chaque requete, basees sur les seuils d'alerte configures dans les Parametres. Elles apparaissent tant que la condition problematique persiste --- par exemple, tant que le CPU reste au-dessus de 80%.
Architecture
A chaque ouverture du centre de notifications, le systeme rassemble des informations de sources tres differentes --- la base de donnees, le serveur de monitoring, Docker, le support --- et les fusionne en une liste triee par date. Voici le schema de ce processus.
┌──────────────────────────────────┐
│ GET /api/admin/notifications │
│ (verifyAdminFromRequest) │
└──────────┬───────────────────────┘
│
┌────────────────┼────────────────┐
│ │ │
┌─────────▼──────┐ ┌─────▼──────┐ ┌──────▼──────────────┐
│ Notifications │ │ Synthetiques│ │ Synthetiques pre- │
│ persistantes │ │ monitoring │ │ existantes │
│ (DB) │ │ (6 checks) │ │ (2 checks) │
└────────────────┘ └────────────┘ └─────────────────────┘
│ │ │
│ table │ Beszel API │ Supabase profiles
│ notifications │ Docker API │ Peppermint API
│ │ /data/backups │
│ │ api_error_logs │
└────────────────────┴────────────────────┘
│
┌──────────▼───────────────────────┐
│ Merge + tri par date │
│ Synthétiques sur page 1 (offset=0)│
│ ID prefixe synth_ (pas de collision)│
└──────────────────────────────────┘
Types de notifications
Les notifications sont organisees en huit categories. Chaque categorie est identifiee par un type, une icone et une couleur distincte dans l'interface.
| Type | Icone | Couleur | Description |
|---|---|---|---|
security | Bouclier | Rouge (error) | Alertes securite : login admin, TOTP, ban, suppression compte, wireguard, dependencies |
account | Utilisateur+ | Vert (success) | Comptes : nouvelles inscriptions, demandes de role |
billing | Carte credit | Orange (warning) | Facturation : changements abonnement Stripe (upgrade, downgrade, annulation, paiement) |
server | Serveur | Bleu (primary) | Serveur : redemarrage Docker, backup, mises a jour, alertes Brevo |
import | Base donnees | Gris (secondary) | Import : reines reference, bulk F0 |
support | Ticket | Bleu clair (info) | Support : nouveaux tickets, commentaires, escalade chatbot |
chatbot | Robot | Bleu clair (info) | Escalade chatbot vers admin |
general | Cloche | Bleu (primary) | Notifications generales (fallback) |
Notifications persistantes (via notifyAdmins())
Depuis la v2.7.0, la fonction notifyAdmins() insere automatiquement une notification dans la table notifications pour chaque admin, en plus d'envoyer l'email Brevo et la push notification. Cela couvre 26 evenements repartis sur 7 categories, sans qu'aucune route appelante ne soit modifiee.
Les tickets support (support_tickets) sont exclus de l'insertion automatique par notifyAdmins() car ils sont deja inseres manuellement par 3 routes (creation ticket, commentaire utilisateur, escalade chatbot). Cela evite les doublons.
| Categorie | Evenements | Exemple |
|---|---|---|
| Securite (11) | Login admin, TOTP, changement mot de passe, ban, suppression compte, wireguard, parametres, dependencies | "Connexion admin reussie" |
| Comptes (2) | Inscription email, inscription OAuth | "Nouveau compte : [email protected]" |
| Roles (2) | Demande de role, approbation/rejet | "Demande de role : groupe_selection" |
| Facturation (4) | Nouvel abonnement, modification, annulation, paiement | "Changement plan : breeder → selector" |
| Serveur (4) | Restart Docker, backup, upgrades, bounce Brevo | "Backup manuel declenche" |
| Import (2) | Import reines reference, bulk F0 | "Import 150 reines reference" |
| Formulaire contact (1) | Soumission formulaire contact | "Nouveau message contact" |
Notifications synthetiques (calculees a la volee)
Les notifications systeme sont les "sentinelles" de votre infrastructure. Elles ne sont pas stockees en base de donnees --- elles sont calculees a chaque requete sur GET /api/admin/notifications. Tant que le probleme persiste, la notification apparait. Des qu'il est resolu, elle disparait. Leur identifiant est prefixe synth_ pour eviter les collisions avec les notifications persistantes.
| Notification | ID synthetique | Condition de declenchement | Source de donnees | Lien |
|---|---|---|---|---|
| Backup en retard | synth_backup_overdue | Age dernier backup > seuil backup_max_age_hours (defaut : 26h) | Fichiers /data/backups/*.dump | /backoffice/monitoring |
| Espace disque critique | synth_disk_warning | Usage disque > seuil disk_warning (defaut : 80%) | Beszel API (PocketBase) | /backoffice/monitoring |
| CPU eleve | synth_cpu_warning | Usage CPU > seuil cpu_warning (defaut : 80%) | Beszel API (PocketBase) | /backoffice/monitoring |
| Memoire elevee | synth_memory_warning | Usage memoire > seuil memory_warning (defaut : 80%) | Beszel API (PocketBase) | /backoffice/monitoring |
| Worker non sain | synth_worker_unhealthy | Container beepass-worker status = unhealthy | Docker Engine API (inspect) | /backoffice/docker |
| Pic d'erreurs API | synth_error_spike | Nombre d'erreurs 24h > seuil error_rate_threshold (defaut : 10) | Table api_error_logs (COUNT) | /backoffice/error-logs |
| Validation role en attente | synth_pending_roles | Comptes avec pending_* dans les roles | Table profiles (roles JSONB) | /backoffice/role-validation |
| Tickets sans reponse | synth_old_tickets | Tickets Peppermint ouverts > 48h sans reponse admin | Peppermint API | /backoffice/support |
Les 6 premiers checks utilisent les seuils definis dans les Parametres (carte "Seuils d'alerte"). Modifier un seuil impacte immediatement les prochaines ouvertures de la cloche ou de la page notifications.
Interface
Deux modes d'acces
Le centre de notifications est concu pour s'adapter a votre flux de travail : un acces rapide pour les verifications courantes, et un acces complet pour les sessions de rattrapage.
| Mode | Acces | Description |
|---|---|---|
| Cloche header | Icone cloche dans le header admin (toutes pages backoffice) | Dropdown compact, 5 dernieres notifications, badge compteur non-lues |
| Page dediee | /backoffice/notifications (lien "Voir toutes" dans le dropdown) | Vue complete paginee, filtres, actions groupees |
Liste des notifications
Chaque notification affiche :
| Element | Description |
|---|---|
| Icone de type | Icone coloree identifiant la categorie. Synthetiques : TbAlertTriangle (orange) |
| Titre | Resume concis de l'evenement |
| Description | Details complementaires (nom d'utilisateur, valeurs de seuil, numero de ticket) |
| Horodatage | Date et heure de l'evenement, affichee en temps relatif (relativeTime()) --- par exemple "il y a 5 min" |
| Statut lu/non lu | Indicateur visuel distinguant les notifications non lues (fond bleu clair) |
Cards de statistiques
Quatre indicateurs en haut de page vous donnent une vue d'ensemble instantanee :
| Card | Badge | Description |
|---|---|---|
| Total | primary | Nombre total de notifications (persistantes + synthetiques) |
| Non lues | warning | Nombre de notifications non lues |
| Securite | error | Nombre de notifications de type security |
| Support | info | Nombre de notifications liees au support |
Filtres et actions
| Filtre / Action | Description |
|---|---|
| Filtre par type | Support, Securite, Compte, Facturation, Serveur, Import, Chatbot, General |
| Recherche textuelle | Filtrer les notifications par mots-cles dans le titre et la description |
| Selection multiple | Cocher plusieurs notifications pour suppression groupee |
| Marquer comme lu | Cliquer sur une notification la marque automatiquement comme lue |
| Marquer tout comme lu | Action globale pour marquer toutes les notifications non lues comme lues |
| Suppression groupee | Supprimer les notifications selectionnees (AlertDialog confirmation) |
Notifications synthetiques --- fonctionnement technique
Calcul a la volee
Contrairement aux notifications classiques qui sont creees une fois et stockees, les notifications synthetiques sont recalculees a chaque ouverture. Cela garantit qu'elles refletent toujours l'etat actuel du systeme. Le processus est le suivant :
- La route charge les seuils d'alerte via
loadAlertThresholds()(cache memoire 5 minutes) - Les 8 checks sont executes en parallele
- Chaque check qui detecte un depassement genere un objet notification avec un ID prefixe
synth_ - Les synthetiques sont mergees avec les notifications persistantes et triees par date
Pagination et synthetiques
En mode page (parametre offset present dans la requete), les notifications synthetiques sont incluses uniquement sur la premiere page (offset=0). Elles sont ajoutees au compteur total et unreadCount pour un affichage coherent.
Resilience des checks
Chaque check est enveloppe dans son propre try/catch. Si un service est indisponible (Beszel down, Docker socket absent en dev, Peppermint hors ligne), les autres notifications continuent de fonctionner normalement. Le systeme est concu pour se degrader gracieusement : mieux vaut afficher 6 checks sur 8 que zero.
Check backup ──► try/catch ──► synth_backup_overdue (ou rien)
Check disque ──► try/catch ──► synth_disk_warning (ou rien)
Check CPU ──► try/catch ──► synth_cpu_warning (ou rien)
Check memoire ──► try/catch ──► synth_memory_warning (ou rien)
Check worker ──► try/catch ──► synth_worker_unhealthy (ou rien)
Check erreurs ──► try/catch ──► synth_error_spike (ou rien)
Check roles ──► try/catch ──► synth_pending_roles (ou rien)
Check tickets ──► try/catch ──► synth_old_tickets (ou rien)
En developpement local (Windows sans Docker), les checks Beszel, Docker et Peppermint echouent silencieusement. Seuls les checks fichiers (backup) et base de donnees (erreurs, roles) peuvent fonctionner.
Sources de donnees par check
| Check | API / Source | Donnee lue |
|---|---|---|
| Backup | Fichiers locaux /data/backups/*.dump | Date du fichier le plus recent (fs.statSync) |
| Disque | Beszel API (PocketBase REST) | disk_percent du record systeme le plus recent |
| CPU | Beszel API (PocketBase REST) | cpu du record systeme le plus recent |
| Memoire | Beszel API (PocketBase REST) | memory_percent du record systeme le plus recent |
| Worker | Docker Engine API (/containers/json + inspect) | Health.Status du container beepass-worker |
| Erreurs | Table api_error_logs (Supabase service_role) | COUNT(*) des erreurs des 24 dernieres heures |
| Roles | Table profiles (Supabase service_role) | Profils avec pending_* dans roles JSONB |
| Tickets | Peppermint API via peppermintFetch() | Tickets needs_support crees il y a > 48h sans commentaire admin |
Seuils d'alerte utilises
Les seuils sont les valeurs limites qui declenchent une alerte. Ils proviennent de la cle alert_thresholds dans site_settings (JSONB), charges et caches via src/lib/alert-thresholds.ts. Vous pouvez les ajuster dans les Parametres.
| Parametre | Defaut | Plage | Impact |
|---|---|---|---|
cpu_warning | 80% | 0-100 | Seuil notification CPU |
cpu_critical | 95% | 0-100 | Reserve futur (gravite) |
memory_warning | 80% | 0-100 | Seuil notification memoire |
memory_critical | 95% | 0-100 | Reserve futur (gravite) |
disk_warning | 80% | 0-100 | Seuil notification disque |
disk_critical | 90% | 0-100 | Reserve futur (gravite) |
backup_max_age_hours | 26 | 1-168 | Age max backup (heures) |
tls_cert_warning_days | 30 | 1-90 | Reserve futur (TLS) |
worker_unhealthy_notify | true | boolean | Active/desactive check worker |
error_rate_threshold | 10 | 1-1000 | Nombre max erreurs API sur 24h |
La modification des seuils est effectuee depuis la page Parametres. Le cache est invalide immediatement apres sauvegarde.
Deep-link et navigation
Pour eviter de perdre du temps a chercher la page concernee, chaque notification inclut un lien de navigation directe vers la source du probleme :
| Source notification | Type | Lien deep-link | Comportement |
|---|---|---|---|
| Ticket support (admin) | support | /backoffice/support?ticket=ID | Ticket auto-ouvert dans le panneau detail |
| Ticket support (eleveur) | support | /apps/tickets?ticket=ID | Ticket auto-ouvert cote eleveur |
| Login admin / TOTP / ban | security | /backoffice/audit-log | Journal d'audit |
| Nouvelle inscription | account | /backoffice/users | Liste utilisateurs |
| Demande de role | account | /backoffice/role-validation | Page validation roles |
| Changement abonnement | billing | /backoffice/orders | Commandes Stripe |
| Restart Docker / backup | server | /backoffice/docker ou /backoffice/monitoring | Page Docker ou monitoring |
| Import reines | import | /backoffice/reference-queens | Page reines reference |
| Backup en retard (synth.) | — | /backoffice/monitoring | Page monitoring (carte backup) |
| CPU / Memoire / Disque (synth.) | — | /backoffice/monitoring | Page monitoring (Beszel) |
| Worker unhealthy (synth.) | — | /backoffice/docker | Page Docker services |
| Pic erreurs (synth.) | — | /backoffice/error-logs | Page journal erreurs API |
| Role en attente (synth.) | — | /backoffice/role-validation | Page validation roles |
Mark-as-read par ticket
Pour les notifications support, le marquage comme lu est granulaire : PATCH /api/admin/notifications avec { ticket_id } marque comme lues uniquement les notifications de ce ticket (pas toutes les notifications support). Quand vous ouvrez un ticket, seules ses notifications sont marquees comme lues.
Preferences de notification
Les preferences controlent quels types d'alertes vous recevez par email, push notification et dans la cloche (notifications in-app). Depuis la v2.7.0, notifyAdmins() verifie les preferences avant d'inserer en base --- un admin qui desactive import_alerts ne recevra ni email, ni push, ni notification in-app pour les imports. Elles sont configurables depuis les parametres du compte administrateur (/backoffice/account-settings, onglet Notifications).
Notifications activables/desactivables
| Preference | Cle JSONB | Description | Desactivable |
|---|---|---|---|
| Alertes de securite | security_alerts | Findings critiques, tentatives de connexion suspectes | Non (toujours active, verrou) |
| Nouvelles inscriptions | new_accounts | Creation de comptes utilisateur | Oui |
| Mises a jour tickets | support_tickets | Nouveaux tickets et reponses support | Oui |
| Demandes de validation de role | role_requests | Demandes d'attribution de roles proteges | Oui |
| Changements d'abonnement | subscription_changes | Upgrades, downgrades et annulations Stripe | Oui |
| Alertes serveur | server_alerts | Depassements de seuils CPU, memoire, disque (reserve futur) | Oui |
| Alertes d'import | import_alerts | Resultats des imports de donnees (succes, erreurs) | Oui |
Les preferences sont stockees dans profiles.notification_prefs (JSONB) et chargees par le module src/lib/admin-email.ts avant chaque envoi. Le cache des admins destinataires a un TTL de 5 minutes.
Sources d'evenements par toggle
| Toggle | Evenement | Source code |
|---|---|---|
new_accounts | Nouvelle inscription | /auth/callback (profil < 60s) |
support_tickets | Nouveau ticket | /api/support/tickets POST |
role_requests | Demande role protege | /api/profiles/role-request-notify POST |
subscription_changes | Evenement Stripe | /api/billing/webhook (4 events) |
import_alerts | Import reines | /api/admin/queens/f0/bulk + /reference-queens/import |
Fuseau horaire
Le fuseau horaire selectionne dans les preferences determine l'affichage des horodatages dans le centre de notifications et dans les emails de notification. Le champ est en lecture seule (derive automatiquement de Intl.DateTimeFormat().resolvedOptions().timeZone, c'est-a-dire le fuseau de votre navigateur).
Notifications push (PWA)
Depuis la version 2.6.0, les notifications admin sont envoyees en parallele par email Brevo, push notification native (Web Push API) et webhook (Google Chat, Slack, Discord). Les push notifications arrivent directement sur le telephone de l'administrateur, meme quand le navigateur est ferme. Les webhooks envoient un message formate vers une URL externe configuree dans les canaux d'alerte.
Prerequis
| Condition | Description |
|---|---|
| PWA installee | BeePass doit etre installe comme application (icone sur l'ecran d'accueil) |
| Permission push | L'utilisateur doit accepter les notifications push dans le navigateur |
| VAPID keys | Variables d'environnement NEXT_PUBLIC_VAPID_PUBLIC_KEY et VAPID_PRIVATE_KEY configurees |
Activation
Deux moyens d'activer les push :
| Methode | Quand |
|---|---|
| Bandeau automatique | Apparait 1.5s apres l'ouverture de la PWA en mode standalone, si les push ne sont pas encore actives. Bouton "Activer" + bouton fermer (X). Ne reapparait plus apres action (localStorage beepass-push-banner-dismissed). |
| Toggle manuel | Dans /backoffice/account-settings > onglet Notifications > card "Notifications push". Bouton Activer/Desactiver. |
Multi-appareils
Chaque appareil a sa propre subscription push. Un admin peut recevoir les push sur son desktop et son telephone simultanement. Les subscriptions sont stockees dans la table push_subscriptions (une ligne par appareil).
Cleanup automatique
Quand un appareil ne repond plus (subscription expiree ou desabonnee), le serveur recoit un HTTP 404/410 et supprime automatiquement la subscription de la base de donnees.
Digest email (eleveurs)
Les eleveurs peuvent recevoir un resume periodique de leurs notifications non lues par email :
| Frequence | Cron | Description |
|---|---|---|
| Quotidien | 8h UTC | Resume des notifications non lues depuis le dernier digest |
| Hebdomadaire | Lundi 8h UTC | Resume hebdomadaire |
Le digest respecte les heures de silence (quiet_hours_start / quiet_hours_end) configurees par l'utilisateur, en tenant compte de son fuseau horaire (profiles.timezone). Les notifications deja incluses dans un digest precedent sont marquees digest_sent = true.
L'endpoint cron est POST /api/cron/send-digests (Bearer CRON_SECRET, timingSafeEqual).
Comportement technique
| Aspect | Detail |
|---|---|
| Rafraichissement | Focus refetch uniquement --- les notifications se rechargent quand l'administrateur revient sur l'onglet |
| Realtime | Non active --- pas d'abonnement Supabase Realtime sur cette vue |
| Polling | Non active --- pas de polling periodique |
| Fetch initial | useEffect(() => { fetchFn() }, []) au montage du composant |
| Cloche header | Polling 30s sur GET /api/admin/notifications (mode dropdown, 5 items) |
Le centre de notifications admin utilise uniquement le refetch au focus (retour sur l'onglet). Ce choix evite de maintenir une connexion WebSocket permanente pour une vue consultee ponctuellement. Les notifications critiques (alertes serveur) sont egalement envoyees par email via Brevo pour garantir leur reception, sous reserve que le toggle correspondant soit active dans les preferences.
API routes
| Endpoint | Methode | Auth | Description |
|---|---|---|---|
/api/admin/notifications | GET | HMAC admin | Notifications paginees + 8 synthetiques (page 1). Params : limit, offset, type (support, security, account, billing, server, import, chatbot, general), status, month |
/api/admin/notifications | PATCH | HMAC admin | Marquer comme lu. Body : { id } ou { ticket_id } (granulaire par ticket) |
/api/admin/notifications | DELETE | HMAC admin | Supprimer notification(s). Body : { ids: string[] } |
/api/admin/account/notifications | GET | HMAC admin | Preferences notification (7 toggles JSONB) |
/api/admin/account/notifications | PUT | HMAC admin | Sauvegarder preferences notification |
/api/push/subscribe | POST | SSR + HMAC | Sauvegarder subscription push (endpoint, p256dh, auth) |
/api/push/subscribe | DELETE | SSR + HMAC | Supprimer subscription push |
/api/cron/send-digests | POST | Bearer CRON_SECRET | Envoi digest email (daily/weekly) |
Mode dropdown vs mode page
La route GET /api/admin/notifications supporte deux modes d'utilisation :
| Parametre | Mode | Comportement synthetiques |
|---|---|---|
Sans offset | Dropdown (cloche header) | Synthetiques toujours incluses |
Avec offset=0, sans filtre type | Page 1 | Synthetiques incluses, ajoutees au total/unreadCount |
Avec offset=0, avec filtre type | Page 1 filtree | Synthetiques exclues (elles n'ont pas de type DB) |
Avec offset > 0 | Pages suivantes | Synthetiques exclues (deja comptees) |
Fichiers sources
| Fichier | Description |
|---|---|
src/app/components/admin/AdminNotificationCenter.tsx | Page notifications complete (filtres 8 types, badges colores, stats, CSV export) |
src/app/(DashboardAdmin)/layout/vertical/header/Messages.tsx | Dropdown cloche header (10 items, badge compteur, icones/couleurs par type, navigation via link) |
src/app/components/admin/account-settings/AdminNotificationTab.tsx | Onglet preferences (7 toggles + timezone) |
src/app/api/admin/notifications/route.ts | API route GET/PATCH/DELETE + 8 checks synthetiques |
src/lib/alert-thresholds.ts | Chargement seuils, cache 5min, defaults, invalidation |
src/lib/admin-email.ts | Module envoi emails admin Brevo + INSERT notifications DB + webhook (sendWebhook() SSRF-safe, timeout 5s) (check prefs avant envoi, mapping EVENT_TO_NOTIF_TYPE) |
src/lib/web-push.ts | Module push notifications VAPID (sendPushToUser, pushNotifyAdmins, cleanup auto) |
src/components/push-notification-prompt.tsx | Toggle push (enregistrement Service Worker + subscription) |
src/components/push-install-banner.tsx | Bandeau activation push auto (PWA standalone) |
src/app/api/push/subscribe/route.ts | API subscribe/unsubscribe push (double auth SSR + HMAC) |
src/app/api/cron/send-digests/route.ts | Cron digest email (Bearer CRON_SECRET) |
src/lib/notification-helpers.ts | Helpers digest (buildDigestEmailBody, isInsideQuietHours) |