Thème
Architecture & guide de prise en main — Superviseur
Superviseur est le logiciel de supervision d'alarmes incendie Desautel. Il interroge des centrales incendie via Modbus (TCP ou RTU), centralise leur état et l'expose en temps réel dans une interface web. Ce document décrit la structure du dépôt, le rôle des composants et les procédures techniques courantes.
Sommaire
- Vue d'ensemble & solution
- Démarrage rapide (dev)
- Flux de données de bout en bout
- Backend
- Frontend
- Configuration & fichiers clés
- How-to : ajouter un type de centrale + profil Modbus
- Autres procédures courantes
- Tests, qualité, branches
1. Vue d'ensemble & solution
La solution regroupe plusieurs projets :
| Projet | Techno | Rôle |
|---|---|---|
dotnet/backend/ | C# .NET 9, ASP.NET Core, EF Core, PostgreSQL, SignalR, JWT | Service de supervision : polling Modbus, API REST, temps réel, rapports. Sert aussi le frontend buildé. |
vue/ | Vue 3, TypeScript, Vite, Tailwind + DaisyUI, Pinia | SPA web (l'interface opérateur). Se build dans dotnet/backend/wwwroot/. |
dotnet/client/ | WPF .NET 9, WebView2 | Coquille bureau qui affiche le frontend (pointe :5000 en prod, :5173 en dev). |
dotnet/manager/ | WinForms .NET 9 | Icône barre des tâches (gestion du service). |
dotnet/simulator.core/ | C# .NET 9 (bibliothèque) | Cœur du simulateur : serveurs Modbus TCP/RTU, centrales, profils, scénarios. Sans dépendance graphique, donc testable et réutilisable. |
dotnet/simulator/ | WPF .NET 9 | Interface du simulateur. Consomme simulator.core et lui fournit son dispatcher. |
dotnet/simulator.headless/ | C# .NET 9 (console) | Simulateur sans interface : sert les centrales de demo-fixtures.json et expose un canal de pilotage HTTP local. Démarre automatiquement sous les tests de bout en bout. |
dotnet/shared/ | C# .NET 9 | Modèles partagés entre projets .NET. |
dotnet/license-generator/ | C# .NET 9 (WPF) | Outil interne Desautel : émission et signature RSA des licences, registre des émissions. Hors installateur. |
dotnet/tools/ | C# .NET 9 (console) | TsEnumGenerator — génère les enums TypeScript depuis les sources C#. Hors solution : consommé par dotnet/tests/tools. |
dotnet/tests/ | xUnit (backend, client, manager, simulator, tools) + Vitest (co-localisé dans vue/src/) + Playwright (vue/e2e/) | Tests automatisés. |
Le backend est le cœur : il tourne comme service Windows, interroge les centrales en continu, et pousse les changements au frontend via SignalR.
2. Démarrage rapide (dev)
powershell
# Backend (API) — écoute :5264 en dev (:5000 en prod)
dotnet run --project dotnet/backend
# Frontend (Vite HMR sur :5173, proxy /api et /modbusHub vers :5264)
cd vue && npm run dev
# Simulateur de centrale (pour avoir des données sans matériel réel)
dotnet run --project dotnet/simulator
# Tests
dotnet test # tous les projets .NET
cd vue && npm run test:run # VitestLe dev a besoin d'un PostgreSQL local et du fichier dotnet/backend/appsettings.Crypto.dev.json (non versionné, à copier depuis appsettings.Crypto.dev.exemple.json) contenant le secret applicatif — voir §6.
3. Flux de données de bout en bout
Centrale incendie (INIS-E16 / AVISS …)
│ Modbus TCP/RTU (registres/coils)
▼
SupervisionWorker (boucle de polling, 1 tâche/centrale)
│ via PanelAdapterFactory → IFirePanelAdapter (par type) → IModbusClient
▼
PanelStateService / PanelStateManager (état en mémoire, verrou par centrale)
│ écrit aussi SystemLog (événements) + métriques Modbus
▼
IPanelStateNotifier → SignalRPanelStateNotifier (Hub /modbusHub)
│ event "ReceivePanelUpdate" (PanelSnapshot)
▼
Frontend : signalr.service.ts → panel.store.ts (liveStates[serial])
│ systemStatus (computed, par priorité) → chrome (trait, compteur, menus), vues, sons
▼
OpérateurCôté requêtes ponctuelles (config, commandes, rapports), le frontend appelle l'API REST (apiService.ts → contrôleurs Presentation/Controllers/).
4. Backend
4.1 Démarrage
dotnet/backend/Program.cs— point d'entrée. ChargeAppConfig, crée le dossier de données applicatif, construit le logger Serilog, configure le host en service Windows, charge la configuration crypto de l'environnement viaCryptoConfigProtector, puis délègue l'assemblage àStartupExtensions. Gère le crash de démarrage proprement (et repropageHostAbortedExceptionpour les tests d'intégration).dotnet/backend/StartupExtensions.cs— assemblage du host, extrait deProgram.cspour réduire la complexité :BuildLogger(Serilog : console + fichiers journalierslogs/log-*.txt, rétention 90 j).ConfigureKestrel— HTTPS sur le port 5000 (prod), HTTP sur 5264 (dev). Certificat approvisionné (Https:CertificatePath) ou auto-signé de repli.BuildConnectionString— injecte le mot de passe PostgreSQL dérivé du secret unique (SecurityDefaults.Resolve).- Clé JWT via
JwtKeyProvider(persistée dans{AppData}/jwt.key, chiffrée au repos via DPAPI en production Windows ; un redémarrage du backend n'invalide pas les jetons d'un client encore ouvert). AddApplicationServices— enregistre EF Core, contrôleurs, SignalR, auth JWT, CORS, rate limiting, et tous les services métier (DI).- Migrations EF +
DbInitializer.Initialize(seed). - Pipeline middleware.
4.2 Découpage en couches
Le backend suit Domain → Application → Infrastructure → Presentation (+ Configuration).
Domain (dotnet/backend/Domain/)
Modèle métier pur, sans dépendance technique.
Models/FirePanel.cs— la centrale (état runtime + config Modbus). Champs clés :Type(PanelType),ProfileId(string),Profile(ModbusProfile),ConnectionType, IP/Port ou série,SlaveId,Zones.Models/SystemFlagState.cs— état lu d'un état système d'une centrale, indexé par clé.Entities/— entités EF :User,SystemLog,Zone,GlobalConfiguration,GeneratedReport,ReportTemplate,ModbusMetricSnapshot,InisNetwork,InisNetworkMember,PanelZoneConfig,PanelPointConfig.ValueObjects/— immuables :IpAddress,Port,ModbusConnectionSettings,ModbusProfile(mappage des registres Modbus — central pour le how-to §7),IncidentDefinitionetSystemFlagDefinition(description des incidents zonés et des états système du profil).Enums/—PanelType(InisE16,Aviss),ConnectionType(Tcp,Rtu),ZoneStatus,EventType,LogSeverity,AckStatus,PanelConnectionState. Les clés d'états système font exception : ce sont des constantes de chaîne dansdotnet/shared/Profiles/SystemFlagKeys.cs, une même clé étant servie à des bits différents selon la centrale.Interfaces/Repositories/— contrats de repository (IFirePanelRepository,ISystemLogRepository, …).
Application (dotnet/backend/Application/)
Orchestration métier, DTO, interfaces de service.
Services/PanelStateService.cs— orchestrateur principal des états de centrales (lecture, mise à jour, commandes, acquittements). S'appuie sur des services spécialisésZoneManagementService,PointManagementService,SystemManagementService.Services/PanelStateManager.cs— cache mémoire desFirePanelavec verrou par centrale (ExecuteWithPanelLockAsync).Services/AuthService.cs— login/refresh/revoke JWT (BCrypt).Services/StatisticsService.cs(+StatisticsCalculator,StatisticsCacheService) — agrégats de supervision.Services/ReportService.cs+Reports/— génération de rapports modulaires (voir §4.7).Services/Backup*Service.cs— export/import de configuration.Interfaces/—IPanelStateService,IPanelStateNotifier(push SignalR),IPanelStateManager,IStatisticsService, etc.DTOs/— objets de transfert API.
Infrastructure (dotnet/backend/Infrastructure/)
Détails techniques : données, Modbus, jobs, télémétrie.
Persistence/FireSupervisorDbContext.cs— DbContext EF (Users, FirePanels, Zones, SystemLogs, ModbusProfiles, ModbusMetricSnapshots, GeneratedReports, ReportTemplates, InisNetworks…).Persistence/DbInitializer.cs— seed au boot (SuperAdmindesautel, config globale, profils Modbus, réseau INIS par défaut) + réalignement (RepairPanelProfiles).Migrations/(racine du projet backend) — migrations EF (PostgreSQL).Repositories/— implémentations (Repository<>générique + spécialisés).BackgroundJobs/—SupervisionWorker,ReportSchedulerService,ModbusMetricsFlushService(voir §4.4).Communication/— transport Modbus :IModbusClient,ModbusTcpClient,ModbusRtuClient,ModbusConnectionManager(cache de clients + partage RTU multi-drop), adaptateurs série / pipe nommé (simulateur).Adapters/— stratégies de lecture par type de centrale :IFirePanelAdapter,ModbusFirePanelAdapterBase,InisAdapter,AvissAdapter,GenericModbusAdapter,GlobalCoilReader(lecture de la table de paramètres généraux tolérant l'absence des bobines d'extension),Strategies/(voir §4.5).Factory/PanelAdapterFactory.cs— fabrique l'adaptateur selon lePanelType+ applique les contraintes de timing par type.Telemetry/— collecte des métriques Modbus (IModbusMetricsCollector).Crypto/InisConfigCrypto.cs— déchiffrement des configs INIS (format propriétaire).
Presentation (dotnet/backend/Presentation/)
Controllers/— ~18 contrôleurs[Authorize]:AuthController,PanelsController(CRUD centrales, commandes, diagnostic),ReportsController,StatisticsController,SystemLogsController,UsersController,GlobalParametersController,InisNetworkController,ReportTemplatesController, etc. Règle : pas de logique métier dans les contrôleurs → déléguer aux services.Hubs/ModbusHub.cs— Hub SignalR (/modbusHub), groupeSuperAdminConsole.Services/SignalRPanelStateNotifier.cs— implémenteIPanelStateNotifier, pousse les events au frontend.
Configuration (dotnet/backend/Configuration/)
AppConfig.cs— config applicative singleton (identité app/service, ports, chemins ; chargée depuisapp.config.json).SupervisionOptions.cs— paramètres de supervision typés, liés àappsettings.jsonet validés au démarrage.SecurityDefaults.cs— résout le secret applicatif unique (Security:SuperAdminPassword) ; exception fatale si absent (aucun défaut codé en dur).JwtKeyProvider.cs— résolution/persistance de la clé de signature JWT.
4.3 Persistance & migrations
- DbContext :
Infrastructure/Persistence/FireSupervisorDbContext.cs. Les profils Modbus sont seedés viaHasData(ModbusProfile.CreateInisE16(), ModbusProfile.CreateAviss()). Les incidents, commandes et définitions d'états système du profil y sont sérialisés en colonnes JSON : toute modification de leur description en code demande une migration de reseed, faute de quoi le modèle EF diverge du snapshot. Les bases en service sont, elles, réalignées au démarrage parDbInitializer.ApplyProfileMigration. - Créer une migration :powershell
dotnet ef migrations add <Nom> --project dotnet/backend/backend.csproj dotnet ef database update --project dotnet/backend/backend.csproj DbInitializerrejoue au démarrage : crée le SuperAdmin (desautel, hashé BCrypt à partir du secret), la config globale, sème et valide les profils standard depuisPanelProfileRegistry, et le réseau INIS par défaut.
Service en cours =
bin/verrouillé. Quand le service Windows tourne, il verrouille les binaires : arrêterSuperviseur-Service.exeavantdotnet test/migration, ou compiler vers un dossier de sortie alternatif.
4.4 Jobs de fond (Infrastructure/BackgroundJobs/)
SupervisionWorker— le cœur. Au démarrage, charge les centrales, et lance une tâche async par centrale :- crée l'adaptateur via
PanelAdapterFactory.CreateAdapterAsync; - boucle de polling (intervalle
SupervisionSettings:PollingIntervalSeconds, défaut 2 s) →adapter.ReadStatusAsync(); - met à jour l'état (
PanelStateManager) et notifie (IPanelStateNotifier) ; - collecte la télémétrie Modbus ; gère reconnexion auto.
- expose
PausePanelSupervisionAsync/ResumePanelSupervisionAsync(bascule de supervision par centrale).
- crée l'adaptateur via
ReportSchedulerService— toutes les heures, vérifie la planification (GlobalConfiguration.SystemParamsJson→ReportSchedule) et génère + archive un rapport si une fenêtre est due.ModbusMetricsFlushService— toutes les ~5 min (MetricsFlushIntervalMinutes), vide les compteurs Modbus en base (ModbusMetricSnapshot, 1 ligne/centrale/fenêtre) et purge au-delà de la rétention (MetricsRetentionDays, défaut 90 j).
4.5 Supervision Modbus (le chemin centrale → état)
FirePanel.Type (PanelType) + FirePanel.Profile (ModbusProfile)
│
PanelAdapterFactory.CreateAdapterAsync()
• sélectionne une PanelTypePolicy dans le dictionnaire _policies (clé = PanelType)
• résout le profil, applique les minima de timing du type
• crée le IModbusClient (TCP/RTU) via ModbusConnectionManager
• policy.BuildAdapter(ctx) → adaptateur concret
│
IFirePanelAdapter (InisAdapter / AvissAdapter / GenericModbusAdapter)
• ReadStatusAsync() : lit l'état global + points + zones selon les adresses du ModbusProfile,
applique AddressOffset, décode les bits → ZoneStatus selon le PriorityOrder des définitions
d'incidents du profil (émission, arrêt d'urgence et évacuation en tête, états d'exploitation
sous le dérangement ; à rang égal, le premier drapeau apparu garde la main)
• renvoie un PanelSnapshot
│
IModbusClient.ReadCoilsAsync / ReadRegisters / Write…Points importants :
ModbusProfile(Domain/ValueObjects/ModbusProfile.cs) porte toutes les adresses de registres (par point :PointFireAddress,PointFaultAddress,PointDisableAddress; par zone :ZoneFireAddress,ZoneFaultAddress,Evacuation/Emission/DM/ZoneTest/ZoneDisable, et sur INIS-E16Neutralization/ManualOnly/EmergencyStop/Inhibition; globaux :GlobalFireAddress,GlobalFaultAddress), leZoneCount, l'AddressOffset(décalage appliqué à toutes les adresses), les codes de commande (CmdReset,CmdSilence, …) et la collectionSystemFlags— les définitions d'états système, dont le décalage de bit provient de la carteGlobalBitsde la centrale.PanelAdapterFactory._policies(dictionnairePanelType → PanelTypePolicy) encapsule, pour chaque type : comment résoudre le profil, les minima de timing I/O, et comment construire l'adaptateur.ConnectionTypegère TCP (IP:port) et RTU (port série, baud…). Le simulateur expose une centrale via TCP / pipe nommé.
4.6 Temps réel (SignalR)
- Hub :
Presentation/Hubs/ModbusHub.cs(/modbusHub), avec le groupeSuperAdminConsole. - Contrat serveur → client — la liste ci-dessous est exhaustive ; tout événement écouté par le frontend et absent d'ici n'est jamais émis :
| Événement | Émis par | Charge utile |
|---|---|---|
ReceivePanelUpdate | SignalRPanelStateNotifier | Le PanelDto complet, à chaque changement d'état |
ReceiveReconnection | SignalRPanelStateNotifier | Identifiant et nom de la centrale reconnectée |
SystemError | SignalRPanelStateNotifier | Source, message, horodatage |
ExportProgress | Service d'export | Identifiant de tâche et pourcentage |
ResourceChanged | SignalRResourceChangeNotifier | Le seul nom d'une famille de ResourceKind |
ReceiveModbusLog | HubModbusLogger | Trame Modbus — groupe SuperAdminConsole uniquement |
- Ajouter un événement : l'émettre depuis un service de
Presentation/Services/, l'ajouter à ce tableau, puis l'écouter dansvue/src/api/signalr.service.ts. Les deux côtés n'ayant aucun contrat partagé compilé, seule cette table les tient alignés.
Données partagées : ResourceChanged
Une donnée modifiée sur un poste s'affiche sur les autres sans rechargement de page. Le mécanisme tient en trois pièces :
[NotifiesResource(ResourceKind.Maps)]sur le contrôleur — ou sur une action, qui l'emporte alors.ResourceChangeFilterémet après toute réponse réussie à unPOST/PUT/PATCH/DELETEportant l'attribut : une seule notification par requête, quel que soit le nombre d'entités touchées.- Le poste incrémente un compteur dans
resource-sync.store.ts;useResourceSync(famille, recharger)observe ce compteur et recharge en silence.
L'événement ne transporte jamais de donnée. Chaque poste recharge par l'API, donc avec ses propres droits — le filtrage par rôle reste celui de l'API, en un seul endroit.
Familles (Domain/Enums/ResourceKind.cs) : Panels, Config, IncidentProfiles, Maps, InisNetworks, Notes, Users, ReportTemplates. Une famille correspond à ce qu'un poste recharge d'un seul appel, non à une table — sons et paramètres généraux relèvent tous deux de Config.
ResourceChangeCoverageTests échoue si une action écrivant une donnée partagée n'est ni annotée ni inscrite dans la liste motivée des contrôleurs exclus (session, sauvegarde, rapports, commandes Modbus, outillage, journal, jeu de démonstration).
4.7 Rapports modulaires (Application/Reports/)
Architecture par sections cochables : chaque section implémente IReportSectionComposer (méthodes LoadAsync, ComposePdf, AppendCsv, propriété Order). ReportService collecte les composeurs injectés, filtre selon les sections demandées (ReportRequestDto.Sections), et génère PDF (PDFsharp/MigraDoc) et CSV. Composeurs actuels : CentralesSectionComposer, EvenementsSectionComposer, StatsModbusSectionComposer, UtilisateursSectionComposer (dans Reports/Sections/). Ajouter une section = §8.
4.8 Sécurité
- Secret applicatif unique
Security:SuperAdminPassword(appsettings.Crypto.dev.jsonen développement,appsettings.Crypto.prod.jsonen production ; non versionnés).SecurityDefaults.Resolvele résout ; le service refuse de démarrer s'il est absent (aucune valeur par défaut). En production Windows, le fichier est scellé au premier démarrage enappsettings.Crypto.prod.protected(DPAPI, portée machine) puis supprimé. Il sert à la fois au compte SuperAdmindesautel(BCrypt) et au mot de passe du rôle PostgreSQL applicatif. - JWT :
AuthServiceémet l'access token (claimRole) + un refresh token, tous deux en cookies de session (HttpOnly,Secure, sans expiration persistante). La clé de signature est persistée (JwtKeyProvider) : un redémarrage du backend n'invalide pas les jetons d'un client encore ouvert. - Session liée au processus : les cookies de session vivent en mémoire du client tant que l'application tourne — la veille du poste ne déconnecte pas — et sont effacés à l'arrêt du processus (fermeture de la fenêtre, redémarrage du poste, désinstallation/mise à jour), imposant alors une nouvelle authentification. Côté frontend, l'utilisateur courant est conservé en
sessionStorage(même cycle de vie) et l'intercepteur 401 (vue/src/api/httpClient.ts) rafraîchit le token de façon transparente ; le retour à l'écran de connexion n'intervient qu'après plusieurs refus d'authentification consécutifs du serveur, jamais sur une simple coupure réseau (poste 24/7).Auth:RefreshTokenDays(défaut 30 j) borne la validité serveur du refresh token, distincte de la durée de session côté poste. - Rôles :
SuperAdmin>Admin>User.
4.9 Accès réseau local
L'API (et l'interface pour un accès navigateur depuis le LAN) est servie par le backend en HTTPS sur le port 5000 (Kestrel + UseHttps, écoute sur toutes les interfaces), avec redirection HTTP→HTTPS, HSTS et en-tête CSP. Le client de bureau, lui, charge l'interface depuis le disque (hôte virtuel WebView2 superviseur.local) et n'appelle le backend que pour l'API et le temps réel. Accès depuis tout appareil du LAN via https://<ip-du-poste>:5000. Le filtrage d'hôtes reste ouvert (AllowedHosts: "*") car l'accès par IP arbitraire du poste est une fonctionnalité ; la surface est bornée par la règle de pare-feu (port 5000, profil réseau privé) et la politique CORS AddLanCors (localhost + plages RFC 1918). L'endpoint GET /api/config/network-info (dotnet/backend/Presentation/Controllers/ConfigController.cs) retourne les IPv4 de la machine + le port ; le frontend les affiche dans Paramètres → Profil (carte « Accès réseau local », visible par un utilisateur simple). Déploiement en réseau local fermé, sans Internet. Le certificat auto-signé (CN=localhost) est installé dans le magasin racine du poste à l'installation (pour un accès navigateur local) ; le client de bureau lui accorde en outre sa confiance directement pour la boucle locale dans WebView2, sans dépendre du magasin racine.
5. Frontend
SPA Vue 3 organisée par domaine (features/), avec couches transverses (components/, composables/, services/, stores/, api/, layouts/, router/, i18n/, config/).
5.1 Structure vue/src/
| Dossier | Rôle |
|---|---|
features/<domaine>/ | Un domaine = sa vue racine + composants + store/types co-localisés. Domaines : dashboard (accueil/KPI), status (état détaillé zones/points), history (journal), statistics (KPI/graphes, consultation ouverte à tous), reports (génération, consultation ouverte à tous ; suppression d'archive et export réservés Admin+), settings (params, sections filtrées par rôle), inis-network (matrice INIS, édition Admin, lecture User), map (plans), panel (store central, pas de vue), tools (scanner Modbus, SuperAdmin), auth (login). |
components/ui/ | Primitives réutilisables : BaseIcon, KpiCard, StatusBadge, SearchableSelect, SystemKpiGrid, NotificationStack, ConfirmDialog, EmptyState… |
composables/ | Logique réutilisable : useAutoZoom (mise à l'échelle dynamique), useAudioAlerts, useIncidentStyling, useStatusLabel (point de passage de tout libellé de statut : acquittement, puis profil d'incident, puis repli i18n), useNotify. |
stores/ | Stores Pinia transverses : global-config, sidebar, connection, modbus-console. (Les stores panel et auth sont co-localisés dans leurs features.) |
api/ | apiService.ts (toutes les requêtes REST + DTO), httpClient.ts (Axios + intercepteur 401 + file d'attente de refresh), signalr.service.ts (client temps réel), map.service.ts, modbus.service.ts. |
services/ | Services applicatifs non-HTTP : notification.service.ts (toasts, dédup), confirm.service.ts, et les deux services audio — sound.service.ts diffuse les sons d'alarme (fichiers de public/sounds/, remplaçables par site), ui-sound.service.ts synthétise les retours d'interface en WebAudio (aucun fichier audio). |
layouts/ | MainLayout.vue (shell) + TheSidebar, TheHeader, TheFooter, BottomNav. |
router/index.ts | Routes + guards (requiresAuth, requiresAdmin, requiresSuperAdmin). |
i18n/locales/{fr,en}.json | Traductions (vue-i18n). |
config/constants.ts | Énums & mappings : ZONE_STATUSES, SYSTEM_STATUSES, PANEL_TYPES, API_ENDPOINTS, utilitaires couleurs. |
config/ui-sounds.ts | Recettes des retours sonores d'interface : hauteur, forme d'onde, durée et gain de chaque ton, jouées par ui-sound.service.ts. |
assets/main.css | Tailwind v4 (@theme), @font-face IBM Plex Sans (embarquée, hors-ligne), tailles/poids de police, tokens de marque (--color-brand) et palette de l'écran d'authentification (--color-auth-*), point de rupture split (900 px). |
5.2 App shell
main.ts— crée l'app, enregistre Pinia/router/i18n, initialise l'auth, monte sur#app.App.vue— monteNotificationStack/ConfirmDialog/ModbusConsole, initialise SignalR +useAutoZoom.Signalisation d'incident dans le chrome — pilotée par
config/app-status-line.ts, qui donne les couleurs de l'échelle de sévérité : rouge pour émission, alarme et évacuation, ambre pour dérangement, arrêt d'urgence et neutralisation, violet pour un déclenchement manuel, ciel pour inhibition et mode manuel seul, ardoise pour une liaison perdue. Quatre porteurs, tous permanents et sans clignotement :layouts/components/AppStatusLine.vue— trait de 3 px sous l'en-tête, traversant la barre latérale et le contenu, posé dansMainLayout. Il ne coûte aucune hauteur utile et se distingue des traits de séparation du chrome, qui s'arrêtent à leur panneau.components/ui/IncidentJumpButton.vue— compteur d'incidents de l'en-tête, visible dès qu'un incident est actif ; un clic ouvre le tableau de bord, où le flux en direct les détaille.TheSidebar/BottomNav— l'élément de menu sélectionné prend la couleur de l'état.TheHeader— le nom du site prend la couleur de l'état.
Aucun de ces porteurs ne considère l'acquittement : acquitter dit « j'ai vu », pas « c'est fini ». Ils ne retombent qu'avec l'état.
MainLayout.vue— sidebar repliable + header sticky +<router-view>(transitions GSAP) + footer.features/auth/LoginView.vue— écran de connexion hors shell : split-panel (panneau de marque + parcours en deux étapes), choix du profil (liste triée par dernière connexion locale, filtrable, repli « Autre compte » vers saisie manuelle) puis saisie du mot de passe, sélecteur de langue FR/EN. Colonne unique sous le point de rupturesplit(900 px). Exclu deuseAutoZoom(échelle propre, en pixels).components/ui/SplashScreen.vue— voile plein écran aux couleurs de la marque, affiché pendant l'authentification (splash.store).
5.3 État & temps réel
features/panel/services/alert-processor.service.ts— consolide l'état des centrales en incidents : une entrée par nature d'événement et par centrale, réunissant la synthèse générale (states) et les cibles touchées (targets: zones et leurs détecteurs). Les trois échelons sont signalés ensemble : l'opérateur acquitte ce qu'il voit.features/panel/panel.store.ts— le cœur :configurations(config statique des centrales),profiles(profils Modbus),liveStates[serial](état temps réel par numéro de série),apiConnected/modbusConnected. Les états système des snapshots y sont ingérés par clé, sans liste codée en dur. Computeds clés :systemStatus(texte + couleur, par priorité : API down > centrale déconnectée > évacuation > émission > DM > alarme feu > dérangement > exploitation dégradée > normal),activeAlerts,systemIssues.- La cascade d'exploitation dégradée (
DEGRADED_OPERATION_STATUSES) passe sous le dérangement, du plus grave au moins grave : arrêt d'urgence, neutralisation, inhibition, mode manuel seul. Chaque état se lit sur le statut de zone, sur les drapeaux de zone (deux états peuvent se cumuler sur une même zone) et sur la synthèse générale, qui peut être active sans zone porteuse visible.
- La cascade d'exploitation dégradée (
api/signalr.service.ts— connexion au hub/modbusHub, reconnexion auto, listeners :ReceivePanelUpdate→panelStore.updatePanelState(snapshot),ResourceChanged→resourceSyncStore.markChanged(famille),SystemError,ExportProgress.
5.4 Build
npm run dev(Vite :5173, proxy/apiet/modbusHub→:5264),npm run build(sort dansdotnet/backend/wwwroot/pour être embarqué dans le service),npm run type-check,npm run lint,npm run test:run.vite.config.ts— alias@→src,__APP_VERSION__injecté, proxies dev, chunks (vendor,signalr).
6. Configuration & fichiers clés
| Fichier | Rôle |
|---|---|
app.config.json (racine) | Identité de l'app/service, ports, chemins, config installateur. |
dotnet/backend/appsettings.json | SupervisionSettings (polling, timeouts, métriques), Jwt, Auth (RefreshTokenDays), Https (CertificatePath/Password), ConnectionStrings. |
dotnet/backend/appsettings.Crypto.dev.json | Non versionné. Secret Security:SuperAdminPassword + clés revendeur, en clair, pour le développement et hors Windows. Gabarit : appsettings.Crypto.dev.exemple.json. |
dotnet/backend/appsettings.Crypto.prod.json | Non versionné. Même contenu pour la production : scellé au premier démarrage en appsettings.Crypto.prod.protected (DPAPI, portée machine) puis supprimé. Gabarit : appsettings.Crypto.prod.exemple.json. |
dotnet/backend/appsettings.Crypto.json | Ancien nom unique, encore lu en repli. Sous l'environnement Testing, c'est le seul fichier chargé : le secret factice versionné dans dotnet/tests/backend/ ne doit pas être écrasé par un fichier de développement présent dans la sortie de build. |
CHANGELOG.md | Source de vérité du CD (format ## [X.Y.Z] - DD-MM-YYYY). |
deployment/build-all.ps1 | Build complet + installateur Inno Setup. |
deployment/setup.iss | Auto-généré, non versionné (porte le secret PostgreSQL). |
Ne jamais afficher ni modifier les fichiers
appsettings.Crypto.*.jsondans le code.
7. How-to : ajouter un type de centrale / profil Modbus
Le support d'une centrale repose sur deux éléments découplés :
- un
ModbusProfile(Domain/ValueObjects/ModbusProfile.cs) — le mappage des registres : adresses des états par point et par zone, états globaux, décalage d'adresse, nombre de zones, codes de commande ; - un adaptateur (
Infrastructure/Adapters/) — la logique de lecture/décodage qui transforme ces registres enPanelSnapshot.
Selon le protocole de la centrale, deux cas se présentent :
- Cas 1 — Modbus proche d'un type déjà supporté : un nouveau profil suffit. L'adaptateur générique (
GenericModbusAdapter) lit en s'appuyant uniquement sur les adresses du profil ; aucun code de lecture spécifique n'est requis. - Cas 2 — logique de lecture spécifique (longueurs/plages de lecture différentes, regroupement des requêtes, décodage particulier des bits, commandes propres) : un adaptateur dédié est nécessaire, en plus du profil.
Points d'extension
Chaque objet s'ajoute depuis un seul endroit ; le typage et la validation au démarrage font le reste.
| Ajouter… | Où |
|---|---|
| Type de centrale (enum) | Domain/Enums/PanelType.cs |
| Adresses & codes de commande d'un profil standard | dotnet/shared/Profiles/StandardPanelProfiles.cs (source unique partagée backend + simulateur) |
| Profil standard | Domain/Profiles/PanelProfileRegistry.cs (fabriques ModbusProfile.Create*), validé au démarrage |
| Comportement / timing d'un type | Infrastructure/Factory/PanelAdapterFactory.cs (_policies) + un adaptateur héritant de ModbusFirePanelAdapterBase si le décodage diffère |
| Transport / protocole | implémentation d'IModbusClient (Infrastructure/Communication/) |
| Incident / commande / son d'alarme | l'enum de clé (Domain/Enums/) + l'entrée dans les BuildStandard* + une migration de reseed (voir ci-dessous) |
| Retour sonore d'interface | une entrée d'UiSoundType et sa recette dans vue/src/config/ui-sounds.ts — purement frontend, sans base ni migration |
| Réglage global de présentation | une clé de SystemParamsJson (GlobalConfiguration), lue par global-config.store — persistée en base, donc valable pour tous les postes |
| État système | la clé dans dotnet/shared/Profiles/SystemFlagKeys.cs, le bit dans la carte GlobalBits des centrales qui le servent, la description dans ModbusProfile.BuildStandardSystemFlags, puis une migration de reseed (voir États système et états d'exploitation) |
| Rôle | Domain/Enums/UserRole.cs |
| Famille de données partagées à tenir à jour sur tous les postes | la valeur dans Domain/Enums/ResourceKind.cs, l'attribut [NotifiesResource] sur le contrôleur, le type dans vue/src/stores/resource-sync.store.ts, puis useResourceSync(famille, recharger) chez le consommateur (voir §4.6) |
| Exposer un enum au frontend | liste exported de dotnet/tools/TsEnumGenerator, puis scripts/generate-ts-enums.ps1 |
| Simuler un type de centrale (optionnel) | dotnet/simulator.core/ (ProfileLoader dérive de StandardPanelProfiles) |
Toute modification d'un
BuildStandard*demande une migration de reseed. Les profils standard sont semés parHasData(CreateInisE16(), CreateAviss()): incidents, commandes et états système y sont sérialisés en colonnes JSON. Sans migration, le modèle EF diverge du snapshot. Les bases déjà en service sont, elles, réalignées au démarrage parDbInitializer.ApplyProfileMigration.
Détail et exemples : « Profils Modbus & objets métier ». Le backend (ModbusProfile.Create*) et le cœur du simulateur (ProfileLoader, dans dotnet/simulator.core/) dérivent leurs adresses de la même cartographie StandardPanelProfiles : une seule édition met à jour les deux côtés, et les trois consommateurs du simulateur (interface graphique, hôte sans interface, tests) héritent du même mappage.
Squelettes
Fabrique de profil (dans ModbusProfile.cs, sur le modèle des fabriques existantes — les adresses, l'offset et le nombre de zones proviennent de la documentation Modbus de la centrale) :
csharp
public static ModbusProfile CreateXxx() => new()
{
Id = "xxx",
Name = "...",
TargetSystem = PanelType.Xxx,
IsReadOnly = true,
AddressOffset = …, // décalage appliqué à toutes les adresses
PointFireAddress = …, PointFaultAddress = …, PointDisableAddress = …,
ZoneCount = …,
ZoneFireAddress = …, ZoneFaultAddress = …, // … autres états par zone
GlobalFireAddress = …, GlobalFaultAddress = …,
// codes de commande (CmdReset, CmdSilence, …) si la centrale accepte des écritures
};Politique de type (dans PanelAdapterFactory._policies) :
csharp
[PanelType.Xxx] = new(
ResolveProfile: panel => panel.Profile ?? ModbusProfile.CreateXxx(),
MinIoTimeoutMs: …, // minima de timing imposés par la centrale
MinInterRequestDelayMs: …,
BuildAdapter: c => new XxxAdapter(/* client, profil, nb zones/points, délais … */)),Pour le cas 1, BuildAdapter construit un GenericModbusAdapter. Pour le cas 2, une classe d'adaptateur dédiée porte la logique de lecture particulière (implémentation de IFirePanelAdapter via ModbusFirePanelAdapterBase).
8. Autres procédures courantes
Ajouter une section de rapport : créer une classe XxxSectionComposer : IReportSectionComposer dans dotnet/backend/Application/Reports/Sections/, l'enregistrer en DI dans StartupExtensions.AddApplicationServices (AddScoped<IReportSectionComposer, XxxSectionComposer>()), et ajouter la clé correspondante dans l'enum ReportSectionKey (Application/DTOs/ReportRequestDto.cs) + l'UI (vue/src/features/reports/).
Ajouter une page frontend : créer features/<domaine>/XxxView.vue, déclarer la route dans router/index.ts (avec le bon guard), ajouter l'entrée de navigation dans layouts/components/TheSidebar.vue, et les clés i18n.
Ajouter un endpoint API : créer/étendre un contrôleur dans Presentation/Controllers/ (logique déléguée à un service Application/Services/), exposer la méthode côté front dans api/apiService.ts.
Migration EF : dotnet ef migrations add <Nom> --project dotnet/backend/backend.csproj puis dotnet ef database update. Arrêter le service Windows si bin/ est verrouillé.
9. Tests, qualité, branches
- Tests :
dotnet test(xUnit) ;cd vue && npm run test:run(Vitest) ;cd vue && npm run test:e2e(Playwright, navigateur réel). Avant commit front :npm run lint+npm run format. - Tests de bout en bout : joués en local uniquement (l'intégration continue n'a ni PostgreSQL ni
appsettings.Crypto.dev.json). La suite démarre elle-même le simulateur sans interface, le backend et Vite. Deux centrales sont réellement servies en Modbus :vue/e2e/specs/supervision.spec.tsexerce une centrale en ligne, une alarme jusqu'à l'écran et une commande qui atteint la centrale ;incidents.spec.tsexerce le cycle de vie d'un incident groupé à l'écran, acquittement compris. - Rendre la flotte entre deux parcours : un état retombé sans acquittement reste exposé, et la réinitialisation globale ne couvre ni les synthèses de centrale. Les helpers
settlePaneletresetSimulatedFleet(vue/e2e/support/api.ts) attendent le délai de confirmation du backend puis effacent les états rétablis, avant comme après chaque série. - Qualité : SonarQube via
scripts/sonar-scan.ps1(un projetsuperviseurcouvrant back + front). VoirCLAUDE.md. - Branches :
main= dev (CI : tests + build à chaque push) ;prod= production (PR uniquement, déclenche CD + GitHub Release). Avant PR versprod: monter la version dansapp.config.json+CHANGELOG.md. - Commits : signés par l'humain uniquement, sans mention d'IA. Commentaires de code en état absolu (décrire ce qui est, pas justifier un changement).
Pour les détails de conventions (namespaces, couches, secrets, docs VitePress/DocFX), voir CLAUDE.md à la racine.