docs: add design spec for Utilisateurs tab and Amadea log monitoring
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
188
docs/superpowers/specs/2026-04-02-users-tab-design.md
Normal file
188
docs/superpowers/specs/2026-04-02-users-tab-design.md
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
# Design — Onglet Utilisateurs Amadea
|
||||||
|
|
||||||
|
**Date :** 2026-04-02
|
||||||
|
**Statut :** Approuvé
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Contexte
|
||||||
|
|
||||||
|
Ajouter un onglet "Utilisateurs" à l'application de supervision, affichant en temps réel les utilisateurs connectés à Amadea Web 8 x64, leur statut d'activité, et un graphique d'utilisation horaire. Le chemin des logs Amadea et les seuils de statut sont rendus configurables dans l'onglet Configuration existant.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture générale
|
||||||
|
|
||||||
|
### Nouveaux fichiers
|
||||||
|
|
||||||
|
- **`user_monitor.py`** — classe `UserMonitor` : thread de fond, parsing des logs, cache thread-safe. Miroir exact de `SystemMonitor`.
|
||||||
|
|
||||||
|
### Fichiers modifiés
|
||||||
|
|
||||||
|
| Fichier | Modification |
|
||||||
|
|---|---|
|
||||||
|
| `config_manager.py` | Ajout des clés `amadea_log_path` et `user_status_thresholds` dans la config par défaut |
|
||||||
|
| `app.py` | Instanciation de `UserMonitor`, routes `/users` et `/api/users` |
|
||||||
|
| `templates/base.html` | Lien "Utilisateurs" dans la navbar |
|
||||||
|
| `templates/settings.html` | 2 nouveaux blocs de configuration |
|
||||||
|
| `templates/users.html` | Nouvelle page (tableau + graphique CSS) |
|
||||||
|
|
||||||
|
### Flux de données
|
||||||
|
|
||||||
|
```
|
||||||
|
UserMonitor (background thread)
|
||||||
|
├─ parse awevents_YY-MM-DD_*.log → activité utilisateur + déconnexions explicites
|
||||||
|
└─ parse isoft_YY-MM-DD_*.log → événements de session (login/timeout)
|
||||||
|
↓ cache thread-safe (dict par login)
|
||||||
|
/api/users → JSON
|
||||||
|
/users → rendu Jinja2 initial + auto-refresh JS (30s, même pattern que dashboard)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Parsing et modèle de données
|
||||||
|
|
||||||
|
### Sélection des fichiers du jour
|
||||||
|
|
||||||
|
Les fichiers de logs suivent le pattern `PREFIX_YY-MM-DD_N.ext` (ex: `awevents_26-04-02_1.log`).
|
||||||
|
|
||||||
|
- Date du jour formatée en `%y-%m-%d` (ex: `26-04-02` pour 2026-04-02)
|
||||||
|
- Tous les fichiers du jour sont lus, triés par index `N` croissant
|
||||||
|
- Si plusieurs fichiers du même jour existent (index incrémental), ils sont tous parsés dans l'ordre
|
||||||
|
|
||||||
|
### Format `awevents_YY-MM-DD_N.log` (source principale)
|
||||||
|
|
||||||
|
```
|
||||||
|
2026-03-30 10:34:24.034;INFO ;;;;"login=JENKINS,action=SelectionChange,Label=BAO_Main/..."
|
||||||
|
```
|
||||||
|
|
||||||
|
Regex d'extraction :
|
||||||
|
```python
|
||||||
|
r'^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}).*login=([^,]+),action=([^,]+),Label=(.+)"?$'
|
||||||
|
```
|
||||||
|
|
||||||
|
- `timestamp` → groupe 1
|
||||||
|
- `login` → groupe 2 (identifiant utilisateur)
|
||||||
|
- `action` → groupe 3 (SelectionChange, Action, Click, ValueChange…)
|
||||||
|
- `label` → groupe 4 (contexte UI)
|
||||||
|
|
||||||
|
**Déconnexion explicite :** ligne dont le label contient `se deconnecter`
|
||||||
|
|
||||||
|
### Format `isoft_YY-MM-DD_N.log` (complément sessions)
|
||||||
|
|
||||||
|
```
|
||||||
|
2026-03-30 10:33:05.830;INFO ;"ISExecutingThread...";...;"method=OpenUserSession,...,login=JENKINS"
|
||||||
|
```
|
||||||
|
|
||||||
|
Regex pour login réussi :
|
||||||
|
```python
|
||||||
|
r'^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}).*method=OpenUserSession.*login=([A-Za-z0-9_]+)'
|
||||||
|
```
|
||||||
|
|
||||||
|
Regex pour timeout de session :
|
||||||
|
```python
|
||||||
|
r'^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}).*method=closeSession'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Modèle par utilisateur (cache)
|
||||||
|
|
||||||
|
```python
|
||||||
|
{
|
||||||
|
"login": str, # identifiant (ex: "JENKINS")
|
||||||
|
"last_action_time": datetime, # horodatage de la dernière action
|
||||||
|
"last_action_label": str, # label de la dernière action (tronqué à 60 chars)
|
||||||
|
"action_count_24h": int, # nombre d'actions dans les dernières 24h
|
||||||
|
"status": str, # "actif" | "inactif" | "deconnecte"
|
||||||
|
"explicit_logout": bool, # True si déconnexion explicite détectée
|
||||||
|
"connected_since": datetime | None, # heure de la première action du jour (premier awevents du jour pour cet utilisateur)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Règles de statut
|
||||||
|
|
||||||
|
Seuils configurables (clé `user_status_thresholds`, défauts : `active_minutes=5`, `inactive_minutes=30`) :
|
||||||
|
|
||||||
|
| Condition | Statut |
|
||||||
|
|---|---|
|
||||||
|
| Déconnexion explicite détectée | DÉCONNECTÉ |
|
||||||
|
| Dernière action > `inactive_minutes` | DÉCONNECTÉ |
|
||||||
|
| Dernière action entre `active_minutes` et `inactive_minutes` | INACTIF |
|
||||||
|
| Dernière action < `active_minutes` | ACTIF |
|
||||||
|
|
||||||
|
### Graphique d'activité horaire
|
||||||
|
|
||||||
|
Comptage du nombre d'utilisateurs distincts ayant au moins une action par tranche horaire (H:00 → H:59). Données issues uniquement des fichiers du jour (`awevents_*.log`).
|
||||||
|
|
||||||
|
Pour le sélecteur "7 derniers jours" : si les fichiers zippés ne sont pas disponibles (cas nominal en prod), afficher un `alert-info` : *"Les données historiques des jours précédents ne sont pas disponibles (fichiers archivés)."*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Nouvelles clés dans `config.json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"amadea_log_path": "C:\\ProgramData\\ISoft\\Amadea Web 8 x64\\data\\logs",
|
||||||
|
"user_status_thresholds": {
|
||||||
|
"active_minutes": 5,
|
||||||
|
"inactive_minutes": 30
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Nouveaux blocs dans `settings.html`
|
||||||
|
|
||||||
|
**Bloc 1 — Chemin des logs Amadea** (même style `card` + `form-control` + `btn btn-primary`) :
|
||||||
|
- Champ texte pré-rempli avec la valeur actuelle
|
||||||
|
- Bouton "Enregistrer"
|
||||||
|
- Route POST : `/settings/amadea-log-path`
|
||||||
|
|
||||||
|
**Bloc 2 — Seuils statut utilisateurs** (même style que le bloc "Seuils d'alerte") :
|
||||||
|
- Champ numérique "Actif si dernière action < N min" (défaut: 5)
|
||||||
|
- Champ numérique "Inactif si dernière action < N min" (défaut: 30)
|
||||||
|
- Bouton "Enregistrer"
|
||||||
|
- Route POST : `/settings/user-thresholds`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Interface utilisateurs (`users.html`)
|
||||||
|
|
||||||
|
### Tableau
|
||||||
|
|
||||||
|
Colonnes : **Utilisateur | Statut | Dernière action | Actions (24h) | Depuis**
|
||||||
|
|
||||||
|
Tri par défaut : Actifs → Inactifs → Déconnectés (ordre de priorité statut).
|
||||||
|
|
||||||
|
Badges Bootstrap cohérents avec l'existant :
|
||||||
|
- ACTIF → `<span class="badge bg-success">ACTIF</span>`
|
||||||
|
- INACTIF → `<span class="badge bg-warning text-dark">INACTIF</span>`
|
||||||
|
- DÉCONNECTÉ → `<span class="badge bg-secondary">DÉCONNECTÉ</span>`
|
||||||
|
|
||||||
|
### Graphique d'activité
|
||||||
|
|
||||||
|
Barres CSS Bootstrap (divs avec hauteur proportionnelle), aucune librairie externe.
|
||||||
|
Une barre par heure de la journée (00h–23h), largeur fixe, hauteur = `(valeur / max) * 100%`.
|
||||||
|
Couleur : `bg-primary`. Tooltip au survol (attribut `title`).
|
||||||
|
|
||||||
|
### Auto-refresh
|
||||||
|
|
||||||
|
`setInterval` toutes les 30 secondes, appel `fetch('/api/users')`, même pattern que `refreshMetrics()` dans `dashboard.html`.
|
||||||
|
|
||||||
|
### Gestion d'erreurs
|
||||||
|
|
||||||
|
| Cas | Affichage |
|
||||||
|
|---|---|
|
||||||
|
| Dossier de logs introuvable | `alert alert-warning` avec le chemin configuré |
|
||||||
|
| Aucun fichier du jour trouvé | `alert alert-info "Aucun log disponible pour aujourd'hui"` |
|
||||||
|
| Fichier verrouillé/illisible | Ignoré silencieusement, parsing continue |
|
||||||
|
| Aucun utilisateur détecté | Message `text-muted` dans le tableau |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Choix techniques
|
||||||
|
|
||||||
|
- **Librairie graphique** : barres CSS Bootstrap pures (aucune dépendance externe)
|
||||||
|
- **Thread** : daemon thread (comme `SystemMonitor`), s'arrête avec l'application
|
||||||
|
- **Encodage fichiers** : `utf-8` avec `errors='ignore'` pour tolérer les caractères invalides
|
||||||
|
- **Performance** : les fichiers `isoft_*.log` peuvent être très volumineux (index > 80). Le parsing lit ligne par ligne sans charger le fichier en mémoire (`for line in f`)
|
||||||
Reference in New Issue
Block a user