Route, paramètres ?site= et ?alert=, jointure côté client et sa raison (pas de site_id sur une recommandation, aucun filtre sur GET /recommendations, /alerts non paginé), génération réservée aux admins, points d'entrée.
9.2 KiB
Frontend
Application Angular 22, 100 % standalone, testée avec Vitest. Source dans apps/frontend.
État actuel
Statut : En cours. L'application sert une première page métier, le tableau de bord, alimentée
par des fixtures : les endpoints qu'elle appelle n'existent pas encore côté API.
Ce qui est en place :
- Bootstrap par
bootstrapApplication(App, appConfig), aucunNgModuledans le dépôt. app.config.tsfournitprovideBrowserGlobalErrorListeners(),provideRouter(routes)etprovideHttpClient(withInterceptors([mockApiInterceptor])).- Une route
/dashboarden composant différé, et une redirection depuis la racine. core/servicesporteStatsService,AlertsService,PredictionsService,SitesServiceetAuthService,core/interceptorsl'intercepteur de fixtures et l'intercepteur d'authentification (jeton porteur, rafraîchissement sur 401),core/guardsla garde de routeauthGuard,features/dashboardla page principale,shared/componentsla jauge de consommation et le graphique de charge par site, tous deux construits sur Chart.js.- Une authentification complète côté interface : connexion, mot de passe oublié/réinitialisation,
changement de mot de passe, garde de route sur
/dashboardet/sites. Détail : 31-contrat-authentification.md. - Un système de design partagé (
shared/components/ui/:ev-button,ev-card,ev-alert,ev-badge,ev-brand, tokens CSS dansstyles/_tokens.scss) que toute nouvelle page doit réutiliser plutôt que redéfinir ses propres styles. Détail : 32-design-systeme-frontend.md. - L'état vit dans des signaux, sans bibliothèque dédiée.
- Vitest via le builder
@angular/build:unit-test, couverture activée. - Prettier configuré, parser
angularpour les gabarits HTML.
Ce qui n'existe pas encore :
stats/alertsrestent sur fixtures.GET /api/v1/stats/summaryetGET /api/v1/alertssont servis par l'intercepteur de fixtures ; l'API expose bien ces routes désormais, mais rien ne basculeuseMockFixturesàfalseen développement pour les consommer réellement.GET /api/v1/predictionsfait exception : jamais mocké, branché sur l'API réelle depuis cette PR (voir plus bas).- Aucun état de chargement : tant que la première réponse n'est pas arrivée, la page reste vide.
- Aucun lint : ESLint n'est pas installé.
Arborescence
Statut : Fait. Elle suit ce que TESTING.md prescrit dans ses
gabarits de tests.
flowchart TB
subgraph src["src/app"]
core["core/<br/>services, guards, interceptors"]
features["features/<br/>un dossier par domaine"]
shared["shared/<br/>composants réutilisables"]
end
features -.-> core
features -.-> shared
core -.-> env["environments/<br/>apiUrl"]
Un service HTTP par domaine dans core/services, les composants de page dans features, et rien
d'autre que du réutilisable dans shared. Les composants n'appellent jamais HttpClient
directement : ils passent par un service, ce qui rend le double de test trivial.
Flux HTTP
Statut : En cours. Le chemin complet est câblé, mais un intercepteur se place devant et répond
lui-même tant que les endpoints n'existent pas.
sequenceDiagram
participant C as Composant
participant S as Service Angular
participant I as mockApiInterceptor
participant P as ng serve, proxy
participant A as FastAPI
C->>S: appel de méthode
S->>I: GET /api/v1/...
alt useMockFixtures actif et route connue
I-->>S: fixture locale
else
I->>P: la requête poursuit
P->>A: http://localhost:8000/api/v1/...
A-->>S: JSON
end
S-->>C: modèle typé
mockApiInterceptor n'intercepte que /stats/summary et /alerts, et seulement si
environment.useMockFixtures est vrai. Le drapeau est à true en développement, à false en
production : toute autre requête, et toutes les requêtes en production, suivent le chemin réel.
/predictions est volontairement exclu de cette liste (contrairement à stats/alerts) : il
suit toujours le chemin réel, comme /auth/* - en développement, ça veut dire qu'un jeton valide
et un backend joignable sont nécessaires pour que la section prévisions du dashboard s'affiche.
En développement, proxy.conf.json redirige tout /api vers http://localhost:8000. C'est ce
qui évite le CORS sur le poste, et c'est pourquoi environment.development.ts se contente d'un
apiUrl relatif, /api/v1.
En production, environment.ts porte lui aussi un apiUrl relatif (/api/v1) plutôt qu'une URL
absolue : la dette qui pointait en dur sur http://localhost:8000/api/v1 a été corrigée. Un build
de production sert donc l'appel /api/v1/... sur son propre origin, et c'est le reverse proxy
qui route /api vers le backend : location /api/ dans infra/proxy/conf.d/enervision.conf, voir
10-infra.md et l'ADR 0007.
Exécution
| Commande | Effet |
|---|---|
npm ci |
Installe les dépendances. node_modules/ n'est pas présent par défaut |
npm start |
ng serve sur le port 4200, proxy actif |
npm run build |
Build de production |
npm run test |
Vitest en mode observateur |
npm run test:ci |
Vitest en une passe |
Version de Node. L'Angular CLI refuse de démarrer en dessous de 22.22.3, 24.15.0 ou 26.0.0, et le message d'erreur arrive avant toute compilation. Un poste en 22.21 ou en 24.12 ne peut donc ni tester ni construire le frontend.
Le frontend a ses cibles dans le Makefile racine (install-frontend, dev-frontend,
englobées par install et dev). En développement il tourne directement via npm, depuis
apps/frontend : le port 4200 n'apparaît dans le compose que comme valeur par défaut
d'APP_CORS_ORIGINS, côté backend.
Le service frontend du docker-compose.yml sert le build statique par le nginx de
apps/frontend/Dockerfile, multi-étage, qui écoute sur 3000. En déploiement il n'est plus
publié du tout : le reverse proxy est seul à sortir sur le réseau, et l'atteint par le réseau
Compose.
Sécurité
- Le frontend ne détient aucun secret :
environment.tsne porte qu'une URL. - L'authentification existe des deux côtés désormais :
authGuardprotège/dashboardet/sites,authInterceptorpose le jeton porteur sur les requêtes sortantes et déclenche le rafraîchissement sur 401. Détail complet dans 31-contrat-authentification.md. - La CSP posée par le reverse proxy contraint le build.
script-src 'self'interdit les gestionnaires d'événements en ligne ; l'inlining du CSS critique en produisait un (<link media="print" onload="this.media='all'">), ce qui aurait laissé l'application sans style derrière le proxy. D'oùoptimization.styles.inlineCritical: falsedans la configuration de production d'angular.json. La contrepartie est un rendu non stylé très bref au premier affichage.style-srcconserve'unsafe-inline': Angular injecte les styles de composants à l'exécution, et s'en passer demanderait unngCspNonceque le SPA statique ne peut pas produire.
Tests
Conventions et gabarits : apps/frontend/TESTING.md.
Recommandations
Statut : Fait. La vue /recommendations (features/recommendations, derrière authGuard, tous
rôles) présente les recommandations du moteur de règles groupées par alerte, du plus récent au plus
ancien, avec le contexte de l'alerte (sévérité, type, site, horodatage, message) puis chaque action,
son explication et la règle qui l'a produite.
- Jointure côté client. Une recommandation ne porte que
alert_id, jamaissite_id, etGET /recommendationsn'a aucun filtre.app-recommendation-list(shared/components/) charge donc en parallèleGET /alerts(filtré parsite_idquand un site est fixé) etGET /recommendations, puis les joint paralert_id(joinByAlert, fonction pure testée à part). Les recommandations dont l'alerte n'est pas dans le jeu chargé sont ignorées : c'est ainsi que le filtre site s'applique./alertsn'étant pas paginé, un seul appel suffit. - Paramètres d'URL.
?site=<site_id>présélectionne le filtre site ;?alert=<alert_id>réduit la vue à une alerte et la met en évidence (entier strictement positif, sinon ignoré). - Génération. Le bouton « Générer les recommandations » n'apparaît que pour le rôle
admin(POST /recommendations/generate?site_id=, réservé admin côté API) et affiche le bilan renvoyé (créées, déjà présentes, alertes examinées) avant de recharger la liste. La voie normale reste le DAG Airflowalertes(ADR 0008). - Entrées. Lien « Recommandations » dans l'en-tête du tableau de bord ; section « Recommandations » sur la vue détail d'un site (liste restreinte au site, lien vers la vue complète préfiltrée).
Questions ouvertes
- Gestion d'état : les signaux suffisent aujourd'hui, la question se reposera quand plusieurs pages partageront le même état.
- Comment
apiUrlest injecté en production : build par environnement, ou configuration lue au démarrage.