Architecture frontend (Angular)¶
Application Angular moderne : composants standalone, Signals, Signal Forms, et zoneless (pas de zone.js).
frontend/src/app/
├── app.component.ts, app.config.ts, app.routes.ts
├── core/
│ ├── applets/ # champs de formulaire réutilisables
│ ├── guards/ # auth.guard.ts, admin.guard.ts
│ ├── interceptors/ # auth.interceptor.ts, error.interceptor.ts
│ ├── models/ # api.models.ts (miroir des schémas backend)
│ └── services/ # api.service.ts, auth.service.ts, theme.service.ts, ...
├── features/ # un dossier par écran/domaine fonctionnel
│ ├── about/, audit/, auth/login/, auth/oidc-callback/,
│ │ clients/, oidc/, profile/, server/, settings/,
│ │ smtp/, status/, users/
└── shared/components/
├── healthly/ # indicateur de santé backend
├── layout/ # shell applicatif (navigation, header)
└── session-expired-modal/
Configuration applicative — app.config.ts¶
export const appConfig: ApplicationConfig = {
providers: [
provideZonelessChangeDetection(),
provideHttpClient(withFetch(), withInterceptors([authInterceptor, errorInterceptor])),
provideRouter(routes),
provideSignalFormsConfig({ ... }),
],
};
provideZonelessChangeDetection(): la détection de changement ne repose pas surzone.js, mais sur les Signals — meilleures performances, code plus explicite.withFetch():HttpClientutilise l'APIfetchnative.- Intercepteurs :
authInterceptor(gestion des en-têtes CSRF/authentification) eterrorInterceptor(normalisation des erreurs API, déclenchement de la modale de session expirée sur 401). provideSignalFormsConfig: configure les classes CSS (is-invalid/is-valid) appliquées automatiquement par les Signal Forms selon l'état de validation des champs.
Routing — app.routes.ts¶
flowchart TD
Root["/"] -->|"non protégé"| Login["/login"]
Root -->|"non protégé"| Callback["/auth/callback (OIDC)"]
Root -->|"authGuard"| Layout["Layout (shell)"]
Layout --> Status["/status (tous les utilisateurs)"]
Layout --> Profile["/profile"]
Layout --> About["/about"]
Layout -->|"adminGuard"| Clients["/clients"]
Layout -->|"adminGuard"| Server["/server"]
Layout -->|"adminGuard"| Settings["/settings"]
Layout -->|"adminGuard"| Users["/users"]
Layout -->|"adminGuard"| Audit["/audit"]
Layout -->|"adminGuard"| Oidc["/oidc"]
Layout -->|"adminGuard"| Smtp["/smtp"]
Toutes les pages sont chargées en lazy-loading (loadComponent). Les gardes authGuard/adminGuard reproduisent côté client les mêmes restrictions que le backend (get_current_user/get_current_admin) — mais l'autorité reste toujours le backend, qui revalide chaque requête API.
Couche core/¶
Services (core/services/)¶
| Fichier | Rôle |
|---|---|
api.service.ts |
ApiService central : une méthode par endpoint backend, puis des façades par domaine (ClientsService, ServerService, SettingsService, SmtpService, UsersService, OidcService, AuditService, PatService) qui encapsulent ApiService pour un usage sémantique dans les composants. |
auth.service.ts |
État réactif (Signals) de l'utilisateur courant : isAuthenticated, isAdmin, authSource, isOidc. Le JWT lui-même reste dans un cookie HttpOnly (jamais accessible en JS) ; seul un résumé non sensible est mis en cache côté client. Expose login()/logout(). |
health.service.ts |
Interroge GET /api/health pour piloter l'indicateur de santé (shared/components/healthly). |
session-expired.service.ts |
Déclenche la modale de session expirée lorsqu'une réponse 401 est interceptée. |
theme.service.ts |
Gestion du thème clair/sombre de l'interface. |
Modèles (core/models/api.models.ts)¶
Interfaces TypeScript miroir des schémas Pydantic backend : User, Role, WireGuardClient/ClientCreate/ClientUpdate, WireGuardServer/ServerCreate, GlobalSettings/SettingsUpdate, SmtpSettings, OidcAdminSettings/OidcPublicConfig, WireGuardStatus/PeerStatus, AuditLogEntry/AuditLogPage, Pat/PatCreate/PatCreateResponse, AppConfigResponse.
Intercepteurs (core/interceptors/)¶
auth.interceptor.ts: ajoute le header CSRF (X-CSRF-Token) sur les requêtes non sûres, en s'appuyant sur le cookiecsrf_tokenlisible en JS.error.interceptor.ts: normalise les erreurs HTTP renvoyées par le backend (format{code, message, details}) et déclenche la déconnexion/modale de session expirée sur une 401.
Gardes (core/guards/)¶
auth.guard.ts: bloque l'accès aux routes protégées si l'utilisateur n'est pas authentifié → redirection vers/login.admin.guard.ts: bloque l'accès aux pages d'administration si le rôle courant n'est pasadmin.
Features (features/)¶
Un dossier par écran, chacun avec son composant standalone, son template et ses styles. Correspondance directe avec les routers backend :
| Feature | Router backend correspondant | Accès |
|---|---|---|
status/ |
status.py |
Tous les utilisateurs connectés |
clients/ |
clients.py |
Admin |
server/ |
server.py |
Admin |
settings/ |
settings.py |
Admin |
users/ |
users.py |
Admin |
audit/ |
audit.py |
Admin |
oidc/ |
oidc.py |
Admin (config) / public (login) |
smtp/ |
smtp.py |
Admin |
profile/ |
auth.py, pat.py |
Utilisateur connecté |
auth/login/, auth/oidc-callback/ |
auth.py, oidc.py |
Public |
about/ |
status.py (version/latest-release) |
Utilisateur connecté |
Build et intégration avec le backend¶
npm run build # ng build --configuration production
# → sortie dans frontend/dist/wireguard-ui/browser
En production, le Dockerfile copie ce dossier browser vers /app/frontend, et c'est FastAPI (route catch-all serve_spa dans main.py) qui le sert directement — un seul port (8000) suffit alors, backend et frontend étant servis par le même processus.
En développement, les deux applications tournent séparément : Angular sur :4200 (avec proxy.conf.json qui relaie /api/* vers :8000), FastAPI sur :8000. Voir Installation locale.