Thème
Client de bureau
Application WPF qui encapsule l'interface web dans une fenêtre native Windows via WebView2. Elle sert le frontend depuis le disque, indépendamment du service, et dirige les appels API et le temps réel vers le backend en boucle locale.
| Technologie | WPF .NET 9 (net9.0-windows), WebView2, WinForms pour l'icône de notification |
| Point d'entrée | App.xaml.cs puis MainWindow.xaml.cs |
| Nom d'assembly | Superviseur |
| Paquets | Microsoft.Web.WebView2, System.Management |
| Référence projet | dotnet/shared — pour l'empreinte matérielle |
Structure
| Fichier ou dossier | Rôle |
|---|---|
App.xaml.cs | Mutex d'instance unique, contrôle de licence, choix de la fenêtre à ouvrir |
MainWindow.xaml / .cs | Fenêtre principale, WebView2, icône de notification, fermeture protégée |
VirtualHost.cs | Mappage de l'hôte virtuel et injection de l'adresse du backend |
LoadingWindow.xaml | Anneau de chargement, dans une fenêtre à part |
LicenseWindow.xaml | Activation du poste |
HelpWindow.xaml | Notices, servies par le même hôte virtuel |
PasswordVerifyDialog.xaml | Saisie du mot de passe administrateur à la fermeture |
LanguagePreference.cs, Loc.cs | Langue des fenêtres natives |
Configuration/AppConfig.cs | Chargement de app.config.json |
Licensing/ | LicenseChecker, LicenseMessages, SecurityVerifier |
Architecture
LoadingWindow (anneau neutre, fenêtre propre) ── estompée au signal « app-ready »
MainWindow
├── WebView2 ── hôte virtuel https://superviseur.local/ (wwwroot sur disque)
│ └── SPA Vue ──postMessage──► OnWebViewMessage → NotifyIcon / retrait de l'indicateur
│ └── API + SignalR ──► https://localhost:5000 (backend, boucle locale)
├── NotifyIcon (barre des tâches)
└── OnClosing → mot de passe administrateur (POST /api/auth/verify-admin-password)Mécanismes non évidents
Le front et le backend sont deux origines distinctes
| Élément | Valeur |
|---|---|
| Hôte virtuel | superviseur.local, mappé sur wwwroot/ à côté de l'exécutable |
| Page d'entrée | https://superviseur.local/index.html |
| Adresse du backend | Injectée dans la page comme window.__SUPERVISEUR_API_BASE__ |
Conséquence : la page de connexion s'affiche même si le service n'est pas démarré. Corollaire pour le frontend — une URL /api ou /uploads écrite en relatif viserait superviseur.local et échouerait sans message.
Toute fenêtre du client qui affiche l'interface doit appeler VirtualHost.ApplyAsync. Sans le mappage, superviseur.local n'est résolu par rien et la fenêtre affiche une erreur réseau ; sans le repli sur index.html, une route sans fichier correspondant — /notice, /login après un rafraîchissement — donne « fichier introuvable », le mappage WebView2 n'ayant pas d'index de répertoire.
Le certificat de bouclage est accepté dans WebView2, pas dans Windows
En Release, le client s'abonne à ServerCertificateErrorDetected et accepte le certificat uniquement lorsque l'URI est de bouclage. Le certificat auto-signé du service n'a donc pas à être placé dans le magasin racine de la machine.
L'indicateur de chargement est une fenêtre à part
Le WebView2 rend son HWND au-dessus de tout élément de la même fenêtre : un anneau posé dans MainWindow serait masqué. LoadingWindow est donc une fenêtre distincte, estompée au signal app-ready envoyé par le front. Aucune identité visuelle n'y est posée : l'habillage est celui que le front applique sur son propre écran de démarrage.
DEBUG et Release ne visent pas les mêmes adresses
| Compilation | Interface chargée depuis | API visée |
|---|---|---|
DEBUG | http://localhost:5173 (serveur Vite) | http://localhost:{DefaultPort} |
Release | https://superviseur.local/index.html (disque) | https://localhost:{DefaultPort} |
La variable d'environnement SUPERVISEUR_WEB_URL, si elle est définie, passe devant. Les outils de développement et le menu contextuel de WebView2 ne sont activés qu'en DEBUG.
La langue des fenêtres natives est mémorisée par utilisateur
LanguagePreference écrit %APPDATA%\{application}\ui-language : aucun droit administrateur, à la différence du répertoire de données commun du service. Lecture comme écriture sont tolérantes à l'échec — la langue ne peut jamais empêcher l'application de démarrer.
app.config.json est copié auprès de l'exécutable
Le client est installé dans {app}\client, hors de portée du app.config.json déposé auprès du backend. Sans cette copie, AppConfig.Load() retombe sur ses valeurs par défaut — version, URL et port erronés.
Le pont vers le front est unidirectionnel côté page
La SPA envoie ses messages par window.chrome.webview.postMessage() : notifications système, signal de disponibilité de l'interface. Le client les traite dans OnWebViewMessage et déclenche les NotifyIcon (infobulles) et FlashWindowEx (clignotement de la barre des tâches), ce qui rend les alertes visibles même quand la fenêtre n'a pas le focus.
Comportement de la fenêtre
| Action | Résultat |
|---|---|
| Minimiser | Autorisé |
| Redimensionner | Autorisé — ResizeMode="CanResize", taille minimale 900 × 600 |
| Fermer (×, Alt+F4) | Mot de passe administrateur requis ; autorisé sans mot de passe si le backend est injoignable |
| Double-clic sur l'icône de notification | Restaure la fenêtre au premier plan |
| Seconde instance | Refusée par un mutex nommé {AppName}-Mutex |
Développement
bash
dotnet run --project dotnet/clientLe serveur Vite doit tourner sur :5173 et le backend de développement sur :5264.
Contraintes actives
| Contrainte | Conséquence si elle est ignorée |
|---|---|
Le wwwroot du frontend doit être déposé à côté de l'exécutable | VirtualHost.IsAvailable est faux et la fenêtre n'a rien à servir |
Toute fenêtre affichant l'interface appelle VirtualHost.ApplyAsync | Erreur réseau, ou « fichier introuvable » sur une route sans fichier |
| Le runtime WebView2 doit être présent | Préinstallé sur Windows 10 1803+ et Windows 11 ; l'installateur le pose sinon |
| L'acceptation du certificat est strictement limitée au bouclage | Élargir la condition ouvrirait le client à n'importe quel certificat invalide |
Le projet cible net9.0-windows | Il ne se compile pas hors Windows |
Build de production
Piloté par deployment/build-all.ps1 : publication autonome en fichier unique, aucun runtime requis sur la machine cible. Le wwwroot du frontend est déposé à la fois auprès du backend et auprès du client.
Tests
dotnet/tests/client — voir son README.