Skip to content

Frontend — interface de supervision ​

Application monopage Vue 3 servant l'ensemble de l'interface : tableau de bord, carte du site, détail des centrales, historique, statistiques, rapports, réglages et outils de diagnostic. Elle consomme l'API REST du backend et reçoit l'état des centrales en temps réel par SignalR.

TechnologieVue 3 (Composition API, <script setup>), TypeScript, Vite 6, Tailwind CSS 4 + DaisyUI 5, Pinia, Vue Router, vue-i18n
Point d'entréesrc/main.ts puis src/App.vue
Temps réel@microsoft/signalr sur le hub /modbusHub
HTTPAxios, encapsulé par src/api/httpClient.ts
Node requis`^20.19.0
Sortie de builddist/, recopiée dans le wwwroot du backend et dans celui du client de bureau
LanguesFrançais et anglais, src/i18n/locales/

Commandes ​

bash
npm install
npm run dev          # serveur de développement sur :5173, proxy vers le backend :5264
npm run build        # vérification de types puis bundle dans dist/
npm run test:run     # Vitest
npm run test:e2e     # Playwright
npm run lint         # eslint --fix
npm run lint:ci      # eslint --max-warnings 0, sans correction
npm run docs         # TypeDoc vers docs/site/api/

Le serveur de développement proxifie /api, /modbusHub (WebSocket comprise), /uploads et /modbus-scanner.html vers http://127.0.0.1:5264.

Structure ​

DossierContenu
src/features/Un dossier par domaine : auth, dashboard, help, history, inis-network, map, notes, panel, reports, settings, statistics, status, tools. Chacun porte sa vue, ses composables use*, ses sous-composants, son store et ses types
src/api/Accès HTTP. apiService.ts agrège ; les méthodes sont réparties par domaine dans services/ (auth, users, logs, panels, inis, modbus-profiles, reports, notes, system), les types dans apiTypes.ts, le client SignalR dans signalr.service.ts
src/components/ui/Composants partagés : DataTable, BaseModal, SearchableSelect, BaseIcon, ConfirmDialog, FilterBar, NotificationStack…
src/composables/Composables transverses : points de rupture, navigation, notifications, raccourcis clavier, échelle automatique, habillage des incidents
src/stores/Stores Pinia globaux : configuration, connexion, profils d'incident, console Modbus, synchronisation de ressources, barre latérale, modifications non enregistrées
src/services/Services applicatifs non HTTP : confirmation, notifications, et deux services audio distincts
src/layouts/MainLayout.vue et son chrome (barre latérale, en-tête, pied, navigation basse)
src/router/16 routes et garde de navigation (requiresAuth, requiresSuperAdmin)
src/config/Constantes, échelle de sévérité, recettes de sons d'interface, et generated/enums.ts
src/i18n/Initialisation et dictionnaires fr.json / en.json
src/types/, src/utils/Types transverses et utilitaires (décodeur Modbus, greffon de zoom des graphiques)
src/test/, src/test-helpers/Amorce Vitest et constructeurs de scénarios Modbus
e2e/Suite Playwright : specs/, pages/, support/

137 fichiers de spécification Vitest sont co-localisés avec les sources (*.spec.ts ou *.test.ts à côté du composant éprouvé) et s'exécutent sous jsdom.

Architecture ​

Organisation par feature : chaque domaine fonctionnel regroupe sa vue, sa logique et ses sous-composants. La logique des gros composants est isolée dans des composables use* co-localisés — usePanelConfigForm, useStatistics, useDashboard, ou les composables de carte dans features/map/composables/. Le composant garde un <script setup> mince : defineProps/Emits/Expose, imports de sous-composants et le câblage const { … } = useXxx(props, emit) ; le gabarit ne dépend que des valeurs rendues par le composable.

Navigation : MainLayout héberge la vue routée dans un <router-view> enveloppé d'une <transition> et d'un <keep-alive>, la clé étant $route.path.

Mécanismes non évidents ​

L'URL du backend n'est jamais relative ​

src/api/apiBase.ts est la source unique. Elle résout l'URL de base dans cet ordre :

RangSourceCas
1VITE_API_BASE_URLDéfini à la compilation ; en développement, pointe le backend :5264
2window.__SUPERVISEUR_API_BASE__Injecté par le client de bureau, qui charge le front depuis le disque et indique où joindre le service
3location.originLe front est servi par le backend lui-même

assetUrl() applique la même base à tout téléversement affiché — logo, sons, plans. Une URL /api ou /uploads écrite en relatif fonctionne en navigateur et échoue silencieusement sur poste installé, où le front est servi par un hôte virtuel distinct du backend. La spécification relative-api-calls.spec.ts verrouille cette règle.

Corollaire : aucune ressource ne vient d'un CDN. Toute dépendance chargée à l'exécution passe par un import ?url.

Les énumérations partagées sont générées, pas écrites ​

src/config/generated/enums.ts est produit par TsEnumGenerator depuis les enums C# du backend. Le fichier est versionné, et l'intégration continue échoue s'il diffère de ce que le générateur produirait. La valeur de chaque membre est son nom, forme sérialisée par le backend.

L'application entière est mise à l'échelle par zoom sur <body> ​

useAutoZoom pose une propriété CSS zoom en style en ligne sur <body>.

PalierÉchelle
BureauProportionnelle à la fenêtre, référence 1680 × 945, plafonnée à 0,9 et plancher à 0,5
Tablette0,9 fixe
Mobile0,85 fixe
Routes /notice et /loginTaille réelle

Aucun écran n'est donc à l'échelle 1. Tout code qui mêle des coordonnées de pointeur — clientX, getBoundingClientRect, en pixels visuels, échelle comprise — à des longueurs CSS — interprétées avant mise à l'échelle — doit diviser par bodyScale(), sans quoi la géométrie dérive de tout l'écart d'échelle.

Deux services audio, deux natures ​

ServiceNatureSource
sound.service.tsSons d'alarmeFichiers de public/sounds/, remplaçables par site
ui-sound.service.tsRetours d'interfaceSynthétisés en WebAudio d'après les recettes de config/ui-sounds.ts — aucun fichier audio

Un geste de l'opérateur se réaligne sur le serveur ​

Le backend ne notifie que sur changement : un poste qui manquerait la notification suivant son propre geste resterait indéfiniment sur une estimation locale. refreshPanelFromServer (panel.store.ts) relit donc la centrale par GET /api/Panels/{id} et applique la réponse par le chemin d'une mise à jour temps réel, dont elle partage source et projection. Un échec est sans conséquence visible : le prochain cycle de supervision rétablira la vérité dès que la centrale bougera.

Le keep-alive du layout ne déclare ni include ni exclude ​

Les vues sont conservées telles quelles ; le rafraîchissement d'un écran réactivé passe par onActivated — useDashboard, useMapEditor, UnifiedMap — et par des stores idempotents.

Tests de bout en bout ​

La suite pilote un vrai navigateur contre le backend réel. Trois serveurs sont démarrés automatiquement, ou réutilisés s'ils tournent déjà : le simulateur sans interface — en tête, pour que le backend trouve ses centrales dès sa première tentative —, le backend :5264 avec le jeu de démonstration, et Vite :5173. Prérequis identiques au développement : PostgreSQL local et dotnet/backend/appsettings.Crypto.dev.json.

bash
npm run test:e2e        # sans interface
npm run test:e2e:ui     # interface interactive

Deux centrales de démonstration sont réellement servies en Modbus — une INIS-E16 en TCP, une AVISS en RTU sur tube nommé — ce qui permet d'exercer la chaîne complète : état « connecté », alarme posée sur la centrale et remontée à l'écran, commande d'exploitation qui atteint la centrale. Les autres centrales [DEMO] restent déconnectées, et leurs incidents ne sont pas acquittables.

DossierContenu
e2e/specs/15 fichiers de scénarios : auth, roles, navigation, status, history, statistics, reports, users, settings, notes, supervision, incidents, custom-profile, panel-profiles, sound-upload
e2e/pages/Page objects : chaque écran expose ses locators (data-testid) et ses actions métier ; les specs ne manipulent jamais de sélecteur directement. select.control.ts pilote n'importe quel SearchableSelect, dont le menu est téléporté dans body
e2e/support/fixtures.ts (test de base, forPage pour une seconde session simultanée), accounts.ts, auth.setup.ts, simulator.ts, api.ts

Comptes du jeu de démonstration : demo.admin01, demo.user01, demo.super01, mot de passe Demo1234!. auth.setup.ts ouvre chaque session une fois par exécution via l'API et la sauvegarde en storageState — les points d'authentification sont limités à 10 requêtes par minute.

Rendre la flotte au parcours suivant ​

Un état qui retombe sans acquittement reste exposé à l'opérateur : remettre les bits à zéro côté simulateur ne suffit pas à nettoyer. settlePanel attend le délai de confirmation du backend, réinitialise les zones et les points, puis acquitte les synthèses rétablies — que la réinitialisation globale ne couvre pas. resetSimulatedFleet fait de même avant une série, car une exécution précédente ou interrompue laisse sinon des états qu'un parcours exigeant une centrale nominale ne pardonne pas.

Accroches génériques ​

BaseModal (prop dataTestid et bouton modal-close), DataTable (table-row), FilterBar (filter-search), ConfirmDialog (confirm-accept / confirm-cancel).

Ce que la suite n'affirme pas, faute d'historique semé : les volumes d'événements. Les écrans concernés sont vérifiés sur leurs mécaniques — filtres, bascules, liens directs — et sur le CRUD réel. Le décodage des adresses Modbus et les règles de groupement des incidents relèvent des tests d'intégration backend et des spécifications Vitest, plus rapides et plus précis.

Contraintes actives ​

ContrainteConséquence si elle est ignorée
Aucune URL /api ou /uploads relativeFonctionne en navigateur, échoue sans message sur poste installé
Aucun CDN ; toute dépendance d'exécution en import ?urlMême symptôme, sur un poste hors ligne
src/config/generated/enums.ts ne s'édite pas à la mainL'édition est perdue, et l'intégration continue la signale
lint:ci et non lint en intégration continuelint corrige (--fix) et ferait passer au vert du code non conforme, le correctif étant jeté avec l'agent
Aucun zoom ni transform sur <html>zoom sur <html> casse le caret des zones de texte ; transform produit un débordement défilable
Les surcouches placées sous un backdrop-blur passent par Teleport vers bodyElles seraient rendues derrière le flou
Un gabarit de vue routée n'a qu'un seul nœud racineUn commentaire HTML en compte comme un : la <transition> du layout devient inopérante et la page reste vide, sans erreur

Documentation Technique