From 9f95547aba46e2ce64c45331366e4554a2960165 Mon Sep 17 00:00:00 2001 From: oussi Date: Tue, 7 Apr 2026 15:37:57 +0200 Subject: [PATCH] Code en Rust pour windows --- .DS_Store | Bin 0 -> 6148 bytes .gitignore | 18 +++ Cargo.toml | 1 - src/main.rs | 269 +++++++++++++++++++++++++++++++- src/user_monitor.rs | 2 +- static/style.css | 48 ++++++ templates/alerts.html | 60 ++++++++ templates/base.html | 80 ++++++++++ templates/dashboard.html | 243 +++++++++++++++++++++++++++++ templates/login.html | 63 ++++++++ templates/settings.html | 320 +++++++++++++++++++++++++++++++++++++++ templates/users.html | 300 ++++++++++++++++++++++++++++++++++++ 12 files changed, 1400 insertions(+), 4 deletions(-) create mode 100644 .DS_Store create mode 100644 .gitignore create mode 100644 static/style.css create mode 100644 templates/alerts.html create mode 100644 templates/base.html create mode 100644 templates/dashboard.html create mode 100644 templates/login.html create mode 100644 templates/settings.html create mode 100644 templates/users.html diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..c1cf69c38f06d6e3775ac7ca193020554ce9a9c5 GIT binary patch literal 6148 zcmeHKF=_)r43uIM3~5}Z+%Mz@i*a6%4NH_0^8l`{~ zm@2Ty<(l>X5&lR2KP71;1*E`LDd5ZP({_U=Rc)O;j = std::env::args().collect(); + + #[cfg(windows)] + { + if args.get(1).map(|s| s.as_str()) == Some("install") { + service::install_service(); + return; + } + if args.get(1).map(|s| s.as_str()) == Some("uninstall") { + service::uninstall_service(); + return; + } + if service::is_running_as_service() { + service::run_service(); + return; + } + } + + #[cfg(not(windows))] + let _ = args; + + run_server().await; +} + +#[cfg(windows)] +mod service { + use std::ffi::OsString; + use std::time::Duration; + use windows_service::{ + define_windows_service, + service::{ + ServiceAccess, ServiceControl, ServiceControlAccept, ServiceErrorControl, + ServiceExitCode, ServiceInfo, ServiceStartType, ServiceState, ServiceStatus, + ServiceType, + }, + service_control_handler::{self, ServiceControlHandlerResult}, + service_dispatcher, + service_manager::{ServiceManager, ServiceManagerAccess}, + }; + + const SERVICE_NAME: &str = "Supervision"; + const SERVICE_DISPLAY: &str = "Supervision - Monitoring Système"; + const SERVICE_DESCRIPTION: &str = + "Surveille CPU, RAM, disques et processus. Interface web sur http://localhost:5000"; + + pub fn install_service() { + let manager = ServiceManager::local_computer( + None::<&str>, + ServiceManagerAccess::CREATE_SERVICE, + ) + .expect("Impossible d'ouvrir le Service Manager (lancer en administrateur)"); + + let exe_path = std::env::current_exe().unwrap(); + + let service_info = ServiceInfo { + name: OsString::from(SERVICE_NAME), + display_name: OsString::from(SERVICE_DISPLAY), + service_type: ServiceType::OWN_PROCESS, + start_type: ServiceStartType::AutoStart, + error_control: ServiceErrorControl::Normal, + executable_path: exe_path, + launch_arguments: vec![], + dependencies: vec![], + account_name: None, + account_password: None, + }; + + let service = manager + .create_service(&service_info, ServiceAccess::CHANGE_CONFIG) + .expect("Impossible de créer le service"); + + service + .set_description(SERVICE_DESCRIPTION) + .expect("Impossible de définir la description"); + + println!("Service '{}' installé avec succès.", SERVICE_NAME); + println!("Démarrer avec: sc start {}", SERVICE_NAME); + } + + pub fn uninstall_service() { + let manager = ServiceManager::local_computer( + None::<&str>, + ServiceManagerAccess::CONNECT, + ) + .expect("Impossible d'ouvrir le Service Manager"); + + let service = manager + .open_service(SERVICE_NAME, ServiceAccess::DELETE) + .expect("Service introuvable"); + + service.delete().expect("Impossible de supprimer le service"); + println!("Service '{}' désinstallé.", SERVICE_NAME); + } + + pub fn is_running_as_service() -> bool { + // Heuristique : variable SESSIONNAME absente = pas de session interactive + std::env::var("SESSIONNAME").is_err() + } + + define_windows_service!(ffi_service_main, service_main); + + fn service_main(_arguments: Vec) { + let rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(async { + let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel::<()>(); + + let mut shutdown_tx = Some(shutdown_tx); + let status_handle = service_control_handler::register( + SERVICE_NAME, + move |control| match control { + ServiceControl::Stop => { + if let Some(tx) = shutdown_tx.take() { + let _ = tx.send(()); + } + ServiceControlHandlerResult::NoError + } + ServiceControl::Interrogate => ServiceControlHandlerResult::NoError, + _ => ServiceControlHandlerResult::NotImplemented, + }, + ) + .unwrap(); + + status_handle + .set_service_status(ServiceStatus { + service_type: ServiceType::OWN_PROCESS, + current_state: ServiceState::Running, + controls_accepted: ServiceControlAccept::STOP, + exit_code: ServiceExitCode::Win32(0), + checkpoint: 0, + wait_hint: Duration::default(), + process_id: None, + }) + .unwrap(); + + tokio::select! { + _ = crate::run_server() => {}, + _ = shutdown_rx => {}, + } + + status_handle + .set_service_status(ServiceStatus { + service_type: ServiceType::OWN_PROCESS, + current_state: ServiceState::Stopped, + controls_accepted: ServiceControlAccept::empty(), + exit_code: ServiceExitCode::Win32(0), + checkpoint: 0, + wait_hint: Duration::default(), + process_id: None, + }) + .unwrap(); + }); + } + + pub fn run_service() { + service_dispatcher::start(SERVICE_NAME, ffi_service_main) + .expect("Impossible de démarrer le service dispatcher"); + } } diff --git a/src/user_monitor.rs b/src/user_monitor.rs index 100b588..ee1f9c9 100644 --- a/src/user_monitor.rs +++ b/src/user_monitor.rs @@ -1,4 +1,4 @@ -use chrono::{Duration, Local, NaiveDateTime}; +use chrono::{Duration, Local, NaiveDateTime, Timelike}; use regex::Regex; use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..78104a9 --- /dev/null +++ b/static/style.css @@ -0,0 +1,48 @@ +/* Supervision — Style */ + +body { + background-color: #f4f6f9; + font-size: 0.9rem; +} + +.metric-card { + transition: border-color 0.3s; +} + +.metric-card .metric-value { + font-size: 2.2rem; + font-weight: 700; + line-height: 1.1; +} + +.card { + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); +} + +.badge { + font-size: 0.75rem; + text-transform: uppercase; +} + +.table th { + font-size: 0.8rem; + text-transform: uppercase; + color: #6c757d; + border-bottom-width: 1px; +} + +.navbar-brand i { + color: #4fc3f7; +} + +/* Statut couleurs */ +.border-success { border-left: 4px solid #198754 !important; } +.border-warning { border-left: 4px solid #ffc107 !important; } +.border-danger { border-left: 4px solid #dc3545 !important; } + +/* Responsive */ +@media (max-width: 768px) { + .metric-card .metric-value { + font-size: 1.8rem; + } +} diff --git a/templates/alerts.html b/templates/alerts.html new file mode 100644 index 0000000..2325caf --- /dev/null +++ b/templates/alerts.html @@ -0,0 +1,60 @@ +{% extends "base.html" %} +{% block title %}Supervision - Alertes{% endblock %} + +{% block content %} +
+

Historique des alertes

+ {% if alerts %} +
+ +
+ {% endif %} +
+ +{% if not alerts %} +
+ Aucune alerte enregistrée. +
+{% else %} +
+
+ + + + + + + + + + + + {% for alert in alerts %} + + + + + + + + {% endfor %} + +
DateTypeMessageValeurSeuil
+ {{ alert.timestamp_display }} + + {% if alert.type == "process_down" %} + Processus + {% else %} + Seuil + {% endif %} + {{ alert.message }}{{ alert.value }}{{ alert.threshold }}
+
+
+
+ {{ alerts | length }} alerte(s) — les 500 dernières sont conservées. +
+{% endif %} +{% endblock %} diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..59d2082 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,80 @@ + + + + + + {% block title %}Supervision{% endblock %} + + + + + + {% if is_authenticated %} + + {% endif %} + +
+ {% if flash_messages %} + {% for item in flash_messages %} + + {% endfor %} + {% endif %} + + {% if default_pw is defined and default_pw %} +
+ + Sécurité : Le mot de passe par défaut est encore actif. + Changez-le maintenant. +
+ {% endif %} + + {% block content %}{% endblock %} +
+ + + {% block scripts %}{% endblock %} + + diff --git a/templates/dashboard.html b/templates/dashboard.html new file mode 100644 index 0000000..95b12d5 --- /dev/null +++ b/templates/dashboard.html @@ -0,0 +1,243 @@ +{% extends "base.html" %} +{% block title %}Supervision - Tableau de bord{% endblock %} + +{% block content %} +
+

+ Tableau de bord + {% if metrics and metrics.hostname %} + — {{ metrics.hostname }} + {% endif %} +

+
+ +
+ {% if metrics and metrics.monitoring_active %} + + {% else %} + + {% endif %} +
+
+
+ +{% if not metrics %} +
+ Collecte des métriques en cours... +
+{% else %} + + +
+
+
+
+ {{ metrics.hostname }} + {{ metrics.os }} + Uptime: {{ metrics.uptime }} + {{ metrics.cpu.cores }} cœurs + {{ metrics.ram.total_gb }} Go RAM +
+
+
+
+ + +
+ +
+
+
+
+
CPU
+ {{ metrics.cpu.status }} +
+
{{ metrics.cpu.percent }}%
+
+
+
+ Seuil: {{ metrics.cpu.threshold }}% +
+
+
+ +
+
+
+
+
RAM
+ {{ metrics.ram.status }} +
+
{{ metrics.ram.percent }}%
+
+
+
+ + {{ metrics.ram.used_gb }} / + {{ metrics.ram.total_gb }} Go + — Seuil: {{ metrics.ram.threshold }}% + +
+
+
+ + {% for disk in metrics.disks %} +
+
+
+
+
{{ disk.drive }}
+ {{ disk.status }} +
+
{{ disk.percent }}%
+
+
+
+ + {{ disk.used_gb }} / {{ disk.total_gb }} Go + ({{ disk.free_gb }} Go libres) + — Seuil: {{ disk.threshold }}% + +
+
+
+ {% endfor %} +
+ + +
+
+
+
+
Processus surveillés
+
+
+ + + + + + + + + + + + + {% for proc in metrics.processes %} + + + + + + + + + {% endfor %} + +
ProcessusStatutInstancesMémoireCPUPID(s)
+ {{ proc.name }} +
pattern: {{ proc.pattern }} +
+ {% if not proc.enabled %} + Désactivé + {% elif proc.running %} + Actif + {% else %} + Arrêté + {% endif %} + {{ proc.instance_count }} + {{ proc.total_memory_mb }} Mo + {% if proc.memory_threshold_mb > 0 %} +
seuil: {{ proc.memory_threshold_mb }} Mo + {% endif %} +
{{ proc.total_cpu_percent }}%{{ proc.pids | join(sep=", ") }}
+
+
+
+
+ + +
+
+
+
+
Alertes récentes
+ Voir tout +
+
+ + + + +
+
+ Aucune alerte récente. +
+
+
+
+
+ +{% endif %} +{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..7b84476 --- /dev/null +++ b/templates/login.html @@ -0,0 +1,63 @@ + + + + + + Supervision - Connexion + + + + + +
+ +
+ + diff --git a/templates/settings.html b/templates/settings.html new file mode 100644 index 0000000..c446d89 --- /dev/null +++ b/templates/settings.html @@ -0,0 +1,320 @@ +{% extends "base.html" %} +{% block title %}Supervision - Configuration{% endblock %} + +{% block content %} +

Configuration

+ +
+
+
+
Seuils d'alerte (%)
+
+
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+
+ +
+
+
Fréquence et alertes
+
+
+
+ + +
+
+ + +
Délai minimum entre deux alertes du même type.
+
+ +
+
+
+ +
+
Port de l'application
+
+
+
+ + +
+ +
+
+
+
+
+ + +
+
+
+
Configuration SMTP
+
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+
+
+
+ + +
+
+ + +
Laissez vide pour conserver le mot de passe actuel.
+
+
+
+
+ + +
+
+ + +
+
+ +
+
+
+ +
+
+
+
+
+ + +
+
+
+
Processus surveillés
+
+
+ + + + + + + + + {% for proc in config.processes %} + + + + + + + + + {% endfor %} + +
NomPatternSeuil mémoire (Mo)ActifAlerte si arrêté
+ +
0 = pas de seuil
+
+
+ +
+
+
+ +
+
+ +
+
+ + +
+
+
+
+
+
+ + +
+
+
+
Mot de passe administrateur
+
+
+
+ + +
+
+ + +
Minimum 8 caractères.
+
+
+ + +
+ +
+
+
+
+
+ + +
+
+
+
Chemin des logs Amadea
+
+
+
+ + +
Dossier contenant les fichiers isoft_*.txt et awevents_*.txt.
+
+ +
+
+
+
+
+
+
Seuils statut utilisateurs
+
+
+
+ + +
+
+ + +
Au-delà du seuil inactif, le statut passe à Déconnecté.
+
+ +
+
+
+
+
+{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/templates/users.html b/templates/users.html new file mode 100644 index 0000000..4378ed3 --- /dev/null +++ b/templates/users.html @@ -0,0 +1,300 @@ +{% extends "base.html" %} +{% block title %}Supervision - Utilisateurs{% endblock %} + +{% block content %} +
+

Utilisateurs Amadea

+ +
+ +
+ + +
+
+
Utilisateurs actifs par heure
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ + Les donnees historiques des jours precedents ne sont pas disponibles (fichiers archives). +
+
+
+
+ + +
+
+
Utilisateurs connectes aujourd'hui
+
+
+ + + + + + + + + + + +
UtilisateurStatutDerniere actionActions (24h)Depuis
+
+
+{% endblock %} + +{% block scripts %} + +{% endblock %}