feat(admin): pousse le monitoring en WebSocket au lieu du polling client #45

Merged
kyuno merged 2 commits from feat/admin-monitoring-websocket into main 2026-09-05 20:11:51 +02:00
Owner

Contexte

Reprise de la #35, dont le merge a atterri sur fix/admin-monitoring-spa-route-collision (branche déjà fusionnée dans main via la #33) — le travail WebSocket n'a donc jamais atteint main. Cette PR le rebranche directement sur main et corrige au passage la fuite de handle qui bloquait le job test en CI.

Contenu

13d92f8 — le travail d'origine de la #35 :

Le tableau de bord /admin/monitoring fonctionnait en polling côté client (15 s) déclenchant une sonde active côté serveur à chaque appel — deux onglets admin ouverts = deux séries de sondes redondantes. Le mécanisme est inversé : le serveur sonde une fois par minute et pousse le résultat aux clients connectés via WebSocket (socket.io, room "monitoring").

  • ExpressServer (packages/express-tools) expose désormais son http.Server sous-jacent, créé explicitement plutôt que dans .listen() — socket.io s'y attache directement.
  • MonitoringBroadcastService (nouveau) : sonde immédiate au démarrage puis toutes les 60 s (réutilise getMonitoring() tel quel), diffuse à la room "monitoring", rejoue le dernier résultat en cache à tout admin qui rejoint.
  • requireAdminSocket (nouveau) : middleware de handshake socket.io, équivalent WebSocket de requireAdmin (vérification factorisée dans resolveAdminFromToken).
  • GET /admin/api/monitoring supprimée (route + service de test associés).
  • Front : MonitoringSocketClient (wrapper socket.io-client injectable), MonitoringPage sépare data et connectionState (une coupure affiche un badge « reconnexion » sans effacer le dernier board connu).

23be1e4 — correctif CI :

Le hook after de admin-monitoring-socket.test.ts ne fermait jamais le serveur socket.io ni son http.Server. Mocha tournant sans --exit, ce handle en fuite empêchait le process de se terminer — le job test affichait « N passing » puis restait bloqué des heures (runs #80/#81). io.close() est maintenant appelé en fin de suite.

Tests

  • apps/api/test/admin-monitoring-socket.test.ts — handshake auth (rejet sans cookie / avec cookie non-admin), push monitoring:update couvrant les 4 cibles, diffusion partagée entre plusieurs admins.
  • apps/web/cypress/component/MonitoringPage.cy.tsx — rendu du board, badge de reconnexion, conservation du dernier état connu.

🤖 Generated with Claude Code

## Contexte Reprise de la #35, dont le merge a atterri sur `fix/admin-monitoring-spa-route-collision` (branche déjà fusionnée dans `main` via la #33) — le travail WebSocket n'a donc jamais atteint `main`. Cette PR le rebranche directement sur `main` et corrige au passage la fuite de handle qui bloquait le job `test` en CI. ## Contenu `13d92f8` — le travail d'origine de la #35 : Le tableau de bord `/admin/monitoring` fonctionnait en polling côté client (15 s) déclenchant une sonde active côté serveur à chaque appel — deux onglets admin ouverts = deux séries de sondes redondantes. Le mécanisme est inversé : le serveur sonde une fois par minute et pousse le résultat aux clients connectés via WebSocket (socket.io, room `"monitoring"`). - `ExpressServer` (`packages/express-tools`) expose désormais son `http.Server` sous-jacent, créé explicitement plutôt que dans `.listen()` — socket.io s'y attache directement. - `MonitoringBroadcastService` (nouveau) : sonde immédiate au démarrage puis toutes les 60 s (réutilise `getMonitoring()` tel quel), diffuse à la room `"monitoring"`, rejoue le dernier résultat en cache à tout admin qui rejoint. - `requireAdminSocket` (nouveau) : middleware de handshake socket.io, équivalent WebSocket de `requireAdmin` (vérification factorisée dans `resolveAdminFromToken`). - `GET /admin/api/monitoring` supprimée (route + service de test associés). - Front : `MonitoringSocketClient` (wrapper socket.io-client injectable), `MonitoringPage` sépare `data` et `connectionState` (une coupure affiche un badge « reconnexion » sans effacer le dernier board connu). `23be1e4` — correctif CI : Le hook `after` de `admin-monitoring-socket.test.ts` ne fermait jamais le serveur socket.io ni son `http.Server`. Mocha tournant sans `--exit`, ce handle en fuite empêchait le process de se terminer — le job `test` affichait « N passing » puis restait bloqué des heures (runs #80/#81). `io.close()` est maintenant appelé en fin de suite. ## Tests - `apps/api/test/admin-monitoring-socket.test.ts` — handshake auth (rejet sans cookie / avec cookie non-admin), push `monitoring:update` couvrant les 4 cibles, diffusion partagée entre plusieurs admins. - `apps/web/cypress/component/MonitoringPage.cy.tsx` — rendu du board, badge de reconnexion, conservation du dernier état connu. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
kyuno added 2 commits 2026-09-05 19:57:10 +02:00
feat(admin): pousse le monitoring en WebSocket au lieu du polling client
Some checks failed
CI / lint (push) Successful in 2m1s
CI / build (push) Successful in 1m21s
CI / intent-service-test (push) Successful in 4m25s
CI / e2e (push) Successful in 6m12s
CI / test (push) Failing after 3h13m30s
13d92f8fa8
Le tableau de bord /admin/monitoring fonctionnait en polling côté client
(15 s) déclenchant une sonde active côté serveur à chaque appel — deux
onglets admin ouverts = deux séries de sondes redondantes.

Inverse le mécanisme : le serveur sonde une fois par minute et pousse le
résultat aux clients connectés via WebSocket (socket.io, room
"monitoring"). Décisions actées avec l'utilisateur : l'endpoint REST
disparaît entièrement (plus de repli, 100% WebSocket) ; la sonde tourne
en continu dès le démarrage, sans lien avec la présence d'un admin
connecté (le plus simple des deux, coût des sondes négligeable).

Backend :
- ExpressServer (packages/express-tools) expose désormais son
  http.Server sous-jacent (créé explicitement, plus dans .listen()) —
  socket.io s'attache au serveur HTTP lui-même, pas à Express/un port.
- MonitoringBroadcastService (nouveau) : sonde immédiate au démarrage
  puis toutes les 60 s (réutilise getMonitoring() tel quel, aucune
  duplication), diffuse à la room "monitoring", rejoue le dernier
  résultat en cache à tout nouvel admin qui rejoint (pas d'attente
  jusqu'à une minute pour un onglet fraîchement ouvert).
- requireAdminSocket (nouveau) : middleware de handshake socket.io,
  équivalent WebSocket de requireAdmin — même vérification (cookie +
  JWT + tokenVersion), factorisée dans resolveAdminFromToken
  (require-admin.ts) puisque les deux ne diffèrent que par où ils lisent
  le cookie brut (un handshake socket.io ne passe pas par
  cookie-parser).
- GET /admin/api/monitoring supprimée (route + service de test associés).
- server.ts (pas app.ts) instancie socket.io — les tests supertest n'ont
  pas besoin d'un serveur WebSocket.

Frontend :
- MonitoringSocketClient (nouveau, api/monitoring-socket.ts) : wrapper
  socket.io-client, interface MonitoringSocketClientLike volontairement
  petite et injectable dans MonitoringPage (prop optionnelle) pour rester
  testable sans serveur réel.
- MonitoringPage : data et connectionState séparés — une coupure de
  connexion après un premier chargement affiche un badge "reconnexion en
  cours" sans jamais effacer le dernier board connu (repose sur la
  reconnexion automatique de socket.io-client). Seul le tout premier
  chargement affiche l'état plein-écran chargement/erreur.
- adminApiClient.getMonitoring() supprimée (plus utilisée).

Tests :
- Nouveau apps/api/test/admin-monitoring-socket.test.ts : vraie connexion
  socket.io-client contre un serveur réel démarré dans le test (port 0),
  authentification avec un admin seedé + cookie signé, vérifie la
  réception de monitoring:update et le rejet sans cookie admin valide.
  Les tests heartbeat de l'ancien admin-monitoring.test.ts (route
  /internal/tech-steps/heartbeat, sans rapport avec ce changement) sont
  déplacés vers tech-step-worker.routes.test.ts, leur router d'origine.
- Nouveau cypress/component/MonitoringPage.cy.tsx : monte la page avec un
  client factice, vérifie rendu des cartes, badge de reconnexion sans
  perte du board, états chargement/erreur — pas de test e2e Gherkin
  contre un vrai WebSocket (aucun mécanisme pour ça dans ce repo,
  fragile ; le test Mocha ci-dessus est la preuve bout-en-bout serveur).
- apps/web/cypress/e2e/admin-monitoring.cy.ts (REST) supprimé, remplacé
  par le test composant ci-dessus.

Vérification : pnpm lint clean, build complet du monorepo clean, suite
Cypress e2e complète 27/27 fichiers (114/114 tests), suite composant
complète 5/5 fichiers (54/54 tests, dont les 4 nouveaux). Suite Mocha
apps/api non exécutable cette session (Postgres/Docker injoignable,
comme pour #33) — le nouveau test socket.io n'a donc pas pu être
run-vérifié, seulement relu attentivement et vérifié par le lint.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
fix(admin): ferme le serveur socket.io en fin de suite de test monitoring
All checks were successful
CI / build (push) Successful in 2m18s
CI / lint (push) Successful in 2m21s
CI / intent-service-test (push) Successful in 3m55s
CI / e2e (push) Successful in 7m45s
CI / test (push) Successful in 14m30s
23be1e4d04
Le hook `after` de admin-monitoring-socket.test.ts appelait seulement
`broadcast.stop()` (qui ne fait que couper l'intervalle de sonde) et
`prisma.$disconnect()`, mais ne fermait jamais le serveur socket.io ni le
`http.Server` en écoute sur le port 0. socket.io garde alors vivants les
timers ping d'engine.io et la socket d'écoute ; comme Mocha tourne sans
`--exit` (.mocharc.json), ce seul handle en fuite empêche le process de se
terminer une fois le dernier test passé.

C'est ce qui bloquait le job `test` en CI : « N passing » affiché, puis
plusieurs heures d'attente jusqu'au kill du runner (runs #80/#81).

Hisse `io` au scope de la suite et le ferme dans `after` via `io.close()`
— ce qui déconnecte les sockets restantes, stoppe les timers d'engine.io
et ferme le `http.Server` attaché.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
kyuno merged commit 41031b4474 into main 2026-09-05 20:11:51 +02:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: kyuno/batchCooking#45
No description provided.