1 Commits

Author SHA1 Message Date
oussi
ebd5482070 V1.0.3 2026-04-27 16:41:16 +02:00
269 changed files with 155 additions and 20 deletions

View File

@@ -7,7 +7,7 @@ Outil de supervision système avec interface web, écrit en Rust. Surveille CPU,
- **Dashboard temps réel** — CPU, RAM, disques, uptime, statut par code couleur (ok / warning / critical) - **Dashboard temps réel** — CPU, RAM, disques, uptime, statut par code couleur (ok / warning / critical)
- **Surveillance de processus** — détection par pattern, alerte si processus arrêté, seuil mémoire configurable - **Surveillance de processus** — détection par pattern, alerte si processus arrêté, seuil mémoire configurable
- **Alertes email (SMTP)** — envoi automatique avec cooldown configurable pour éviter le spam - **Alertes email (SMTP)** — envoi automatique avec cooldown configurable pour éviter le spam
- **Suivi utilisateurs Amadea** — analyse des logs `awevents` et `isoft`, statuts actif/inactif/absent/déconnecté, temps de présence et temps actif, graphe d'activité horaire, hebdomadaire et mensuel, historique par utilisateur - **Suivi utilisateurs Amadea** — analyse des logs `awevents` et `isoft`, statuts actif/inactif/absent/déconnecté, temps de présence et temps actif, compteur d'erreurs par utilisateur (logs `isoft` niveau ERROR), graphe d'activité horaire, hebdomadaire et mensuel, historique par utilisateur
- **Interface de configuration** — seuils, SMTP, processus, port, mot de passe admin, tout modifiable via l'UI - **Interface de configuration** — seuils, SMTP, processus, port, mot de passe admin, tout modifiable via l'UI
- **Service Windows** — installation en tant que service système avec démarrage automatique - **Service Windows** — installation en tant que service système avec démarrage automatique
@@ -139,15 +139,25 @@ Pour chaque utilisateur SuperVision calcule :
- **Présence** — durée entre la première et la dernière action du jour - **Présence** — durée entre la première et la dernière action du jour
- **Temps actif** — présence moins les pauses dépassant le seuil configuré - **Temps actif** — présence moins les pauses dépassant le seuil configuré
### Erreurs isoft
SuperVision analyse les fichiers `isoft_*` pour compter les lignes de niveau `ERROR`. Chaque erreur est rattachée à un utilisateur via le champ `ISI=<session_id>` présent dans le nom du thread, et la correspondance session → login est établie grâce aux événements `OpenUserSession` et `CloseUserSession`.
Le nombre d'erreurs est affiché :
- Dans le **tableau temps réel** et le **tableau jour historique** (colonne « Erreurs », badge rouge si > 0)
- Dans le **panneau historique utilisateur** (colonne « Erreurs » + tooltip sur les barres)
- Dans les **tooltips des graphiques 7/30 jours** (total d'erreurs du jour)
### Tableau temps réel (aujourd'hui) ### Tableau temps réel (aujourd'hui)
- Colonnes : Utilisateur, Statut, Dernière action, Actions (24h), Connecté depuis, Présence, Temps actif, Sessions - Colonnes : Utilisateur, Statut, Dernière action, Actions (24h), Erreurs, Présence / Actif, Depuis
- Tri : actif → inactif → absent → déconnecté, puis dernière action la plus récente en premier - Tri : actif → inactif → absent → déconnecté, puis dernière action la plus récente en premier
### Graphiques d'activité ### Graphiques d'activité
- **7 jours** et **30 jours** — pic d'utilisateurs simultanés par jour - **7 jours** et **30 jours** — pic d'utilisateurs simultanés par jour
- **Cliquer sur une barre** charge le tableau des utilisateurs de ce jour : login, première/dernière action, nombre d'actions, présence, temps actif, nombre de sessions - **Cliquer sur une barre** charge le tableau des utilisateurs de ce jour : login, première/dernière action, nombre d'actions, erreurs, présence, temps actif, nombre de sessions
- **Tooltip sur les barres** affiche le nombre d'utilisateurs et le total d'erreurs du jour
- **Cliquer sur un utilisateur** (tableau du jour ou tableau temps réel) affiche son historique individuel sur 7 ou 30 jours - **Cliquer sur un utilisateur** (tableau du jour ou tableau temps réel) affiche son historique individuel sur 7 ou 30 jours
### Détection des fichiers de logs ### Détection des fichiers de logs

View File

@@ -58,6 +58,7 @@ pub async fn api_users(
"session_count": u.session_count, "session_count": u.session_count,
"presence_str": u.presence_str, "presence_str": u.presence_str,
"active_time_str": u.active_time_str, "active_time_str": u.active_time_str,
"error_count": u.error_count,
}) })
}) })
.collect(); .collect();

View File

@@ -27,6 +27,7 @@ pub struct UserEntry {
pub session_count: u32, pub session_count: u32,
pub presence_str: Option<String>, pub presence_str: Option<String>,
pub active_time_str: Option<String>, pub active_time_str: Option<String>,
pub error_count: u32,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@@ -176,6 +177,66 @@ fn format_duration(minutes: i64) -> String {
} }
} }
// ---------------------------------------------------------------------------
// Erreurs isoft : mapping session → login, comptage par utilisateur
// ---------------------------------------------------------------------------
/// Parcourt les fichiers isoft et retourne login → nombre d'erreurs (lignes ERROR).
/// Utilise `OpenUserSession` (ISI dans le thread) et `CloseUserSession` (ID dans le message)
/// pour construire la table session_id → login.
fn parse_isoft_errors(files: &[std::path::PathBuf]) -> HashMap<String, u32> {
let re_isi = Regex::new(r"ISI=([A-Za-z0-9_]+)").unwrap();
let re_close = Regex::new(r"CloseUserSession.*,ID=([A-Za-z0-9_]+)").unwrap();
let re_login = Regex::new(r"login=([A-Za-z0-9_]+)").unwrap();
let mut session_to_login: HashMap<String, String> = HashMap::new();
let mut error_sessions: Vec<String> = Vec::new();
for file in files {
let Some(content) = read_log_file(file) else { continue };
for line in content.lines() {
if line.contains("OpenUserSession") {
if let (Some(isi), Some(lg)) = (re_isi.captures(line), re_login.captures(line)) {
session_to_login.entry(isi[1].to_string()).or_insert_with(|| lg[1].to_string());
}
}
if line.contains("CloseUserSession") {
if let (Some(id), Some(lg)) = (re_close.captures(line), re_login.captures(line)) {
session_to_login.entry(id[1].to_string()).or_insert_with(|| lg[1].to_string());
}
}
if line.contains(";ERROR;") {
if let Some(isi) = re_isi.captures(line) {
error_sessions.push(isi[1].to_string());
}
}
}
}
let mut result: HashMap<String, u32> = HashMap::new();
for sid in &error_sessions {
if let Some(login) = session_to_login.get(sid) {
*result.entry(login.clone()).or_default() += 1;
}
}
result
}
/// Compte le total de lignes ERROR dans les fichiers isoft (toutes sessions confondues).
fn count_daily_errors(files: &[std::path::PathBuf]) -> u32 {
let mut count = 0u32;
for file in files {
if let Some(content) = read_log_file(file) {
for line in content.lines() {
if line.contains(";ERROR;") {
count += 1;
}
}
}
}
count
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Parsing ligne par ligne // Parsing ligne par ligne
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -223,6 +284,7 @@ pub fn parse_awevents_line(
session_count: 1, session_count: 1,
presence_str: None, presence_str: None,
active_time_str: None, active_time_str: None,
error_count: 0,
}); });
entry.timestamps.push(ts); entry.timestamps.push(ts);
@@ -348,8 +410,9 @@ impl UserMonitor {
r"^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}).*method=OpenUserSession.*login=([A-Za-z0-9_]+)", r"^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}).*method=OpenUserSession.*login=([A-Za-z0-9_]+)",
) )
.unwrap(); .unwrap();
for file in log_files_for_date(log_dir, "isoft", &date_str) { let isoft_files: Vec<_> = log_files_for_date(log_dir, "isoft", &date_str);
if let Some(content) = read_log_file(&file) { for file in &isoft_files {
if let Some(content) = read_log_file(file) {
for line in content.lines() { for line in content.lines() {
if let Some(m) = re_isoft.captures(line) { if let Some(m) = re_isoft.captures(line) {
let login = m[2].to_string(); let login = m[2].to_string();
@@ -366,6 +429,12 @@ impl UserMonitor {
} }
} }
} }
let error_counts = parse_isoft_errors(&isoft_files);
for (login, count) in &error_counts {
if let Some(u) = users.get_mut(login) {
u.error_count = *count;
}
}
// Calcul présence et temps actif, puis libération des timestamps // Calcul présence et temps actif, puis libération des timestamps
for user in users.values_mut() { for user in users.values_mut() {
@@ -463,7 +532,13 @@ impl UserMonitor {
} }
} }
let max_concurrent = hourly.values().map(|s| s.len()).max().unwrap_or(0); let max_concurrent = hourly.values().map(|s| s.len()).max().unwrap_or(0);
result.push(serde_json::json!({ "date": day.to_string(), "count": max_concurrent })); let isoft_files = log_files_for_date(log_dir, "isoft", &date_str);
let total_errors = count_daily_errors(&isoft_files);
result.push(serde_json::json!({
"date": day.to_string(),
"count": max_concurrent,
"errors": total_errors,
}));
} }
result result
} }
@@ -500,6 +575,9 @@ impl UserMonitor {
let mut result: Vec<_> = users.into_values().collect(); let mut result: Vec<_> = users.into_values().collect();
result.sort_by(|a, b| b.action_count_24h.cmp(&a.action_count_24h)); result.sort_by(|a, b| b.action_count_24h.cmp(&a.action_count_24h));
let isoft_files = log_files_for_date(log_dir, "isoft", &date_str);
let error_counts = parse_isoft_errors(&isoft_files);
result result
.iter() .iter()
.map(|u| { .map(|u| {
@@ -516,6 +594,7 @@ impl UserMonitor {
"presence": if presence_mins > 0 { Some(format_duration(presence_mins)) } else { None }, "presence": if presence_mins > 0 { Some(format_duration(presence_mins)) } else { None },
"active_time": if active_mins > 0 { Some(format_duration(active_mins)) } else { None }, "active_time": if active_mins > 0 { Some(format_duration(active_mins)) } else { None },
"sessions": u.session_count, "sessions": u.session_count,
"error_count": error_counts.get(&u.login).copied().unwrap_or(0),
}) })
}) })
.collect() .collect()
@@ -605,6 +684,10 @@ impl UserMonitor {
let (presence_mins, active_mins) = let (presence_mins, active_mins) =
compute_active_time(&timestamps, pause_threshold_minutes); compute_active_time(&timestamps, pause_threshold_minutes);
let isoft_files = log_files_for_date(log_dir, "isoft", &date_str);
let error_counts = parse_isoft_errors(&isoft_files);
let user_errors = error_counts.get(login).copied().unwrap_or(0);
result.push(serde_json::json!({ result.push(serde_json::json!({
"date": day.to_string(), "date": day.to_string(),
"action_count": action_count, "action_count": action_count,
@@ -613,6 +696,7 @@ impl UserMonitor {
"presence": if presence_mins > 0 { Some(format_duration(presence_mins)) } else { None }, "presence": if presence_mins > 0 { Some(format_duration(presence_mins)) } else { None },
"active_time": if active_mins > 0 { Some(format_duration(active_mins)) } else { None }, "active_time": if active_mins > 0 { Some(format_duration(active_mins)) } else { None },
"sessions": session_count, "sessions": session_count,
"error_count": user_errors,
})); }));
} }
result result
@@ -660,6 +744,7 @@ mod tests {
session_count: 1, session_count: 1,
presence_str: None, presence_str: None,
active_time_str: None, active_time_str: None,
error_count: 0,
} }
} }

View File

@@ -1,6 +1,6 @@
/Users/oussi/Downloads/correctionMonitor/rustAvant/supervision/target/debug/deps/supervision-9eaa7328542abfd9.d: src/main.rs src/config.rs src/monitor.rs src/alerter.rs src/user_monitor.rs src/routes/mod.rs src/routes/auth.rs src/routes/dashboard.rs src/routes/settings.rs src/routes/alerts.rs src/routes/users.rs src/routes/../../templates/base.html src/routes/../../templates/login.html src/routes/../../templates/dashboard.html src/routes/../../templates/settings.html src/routes/../../templates/alerts.html src/routes/../../templates/users.html /Users/oussi/Documents/Documents - MacBook Pro de oussi/EttaSante/monitoring/supervisionRust/target/debug/deps/supervision-9eaa7328542abfd9.d: src/main.rs src/config.rs src/monitor.rs src/alerter.rs src/user_monitor.rs src/routes/mod.rs src/routes/auth.rs src/routes/dashboard.rs src/routes/settings.rs src/routes/alerts.rs src/routes/users.rs src/routes/../../templates/base.html src/routes/../../templates/login.html src/routes/../../templates/dashboard.html src/routes/../../templates/settings.html src/routes/../../templates/alerts.html src/routes/../../templates/users.html
/Users/oussi/Downloads/correctionMonitor/rustAvant/supervision/target/debug/deps/supervision-9eaa7328542abfd9: src/main.rs src/config.rs src/monitor.rs src/alerter.rs src/user_monitor.rs src/routes/mod.rs src/routes/auth.rs src/routes/dashboard.rs src/routes/settings.rs src/routes/alerts.rs src/routes/users.rs src/routes/../../templates/base.html src/routes/../../templates/login.html src/routes/../../templates/dashboard.html src/routes/../../templates/settings.html src/routes/../../templates/alerts.html src/routes/../../templates/users.html /Users/oussi/Documents/Documents - MacBook Pro de oussi/EttaSante/monitoring/supervisionRust/target/debug/deps/supervision-9eaa7328542abfd9: src/main.rs src/config.rs src/monitor.rs src/alerter.rs src/user_monitor.rs src/routes/mod.rs src/routes/auth.rs src/routes/dashboard.rs src/routes/settings.rs src/routes/alerts.rs src/routes/users.rs src/routes/../../templates/base.html src/routes/../../templates/login.html src/routes/../../templates/dashboard.html src/routes/../../templates/settings.html src/routes/../../templates/alerts.html src/routes/../../templates/users.html
src/main.rs: src/main.rs:
src/config.rs: src/config.rs:

Some files were not shown because too many files have changed in this diff Show More