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>
26 lines
1.2 KiB
TypeScript
26 lines
1.2 KiB
TypeScript
import { Router } from "express";
|
|
import { adminAuthRouter } from "./admin-auth.routes.js";
|
|
import { adminCatalogRouter } from "./admin-catalog.routes.js";
|
|
import { adminMetricsRouter } from "./admin-metrics.routes.js";
|
|
import { adminTechStepsRouter } from "./admin-tech-steps.routes.js";
|
|
|
|
/**
|
|
* Aggregator for the admin application's API surface, mounted at
|
|
* `/admin/api` in `app.ts` (not bare `/admin` — see that mount point's own
|
|
* comment for why: it would collide with apps/web's `/admin/*` page
|
|
* routes). Every sub-router here backs one of those pages —
|
|
* `/admin/api/auth` is public (login), everything added later
|
|
* (`/admin/api/metrics`, `/admin/api/tech-steps/*`, `/admin/api/catalog/*`)
|
|
* sits behind `requireAdmin` (`middlewares/require-admin.ts`).
|
|
*
|
|
* Monitoring has **no** REST route here on purpose — it's pushed over
|
|
* WebSocket instead (`lib/monitoring-broadcast.service.ts`, wired in
|
|
* `server.ts`), not requested per-page-load. See that service's doc
|
|
* comment for the full mechanism.
|
|
*/
|
|
export const adminRouter = Router();
|
|
|
|
adminRouter.use("/auth", adminAuthRouter);
|
|
adminRouter.use("/metrics", adminMetricsRouter);
|
|
adminRouter.use("/tech-steps", adminTechStepsRouter);
|
|
adminRouter.use("/catalog", adminCatalogRouter);
|