V1.0.3
This commit is contained in:
16
README.md
16
README.md
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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(×tamps, pause_threshold_minutes);
|
compute_active_time(×tamps, 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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -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:
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user