From 2910b7d875ce4c9b2450c921daa75076314f34dd Mon Sep 17 00:00:00 2001 From: Christoffer Martinsson Date: Tue, 28 Oct 2025 13:21:56 +0100 Subject: [PATCH] Update version to 0.1.22 and fix system metric status calculation - Fix /tmp usage status to use proper thresholds instead of hardcoded Ok status - Fix wear level status to use configurable thresholds instead of hardcoded values - Add dedicated tmp_status field to SystemWidget for proper /tmp status display - Remove host-level hourglass icon during service operations - Implement immediate service status updates after start/stop/restart commands - Remove active users display and collection from NixOS section - Fix immediate host status aggregation transmission to dashboard --- Cargo.lock | 6 ++-- agent/Cargo.toml | 2 +- agent/src/agent.rs | 11 ++++--- agent/src/collectors/disk.rs | 4 +-- agent/src/collectors/memory.rs | 9 ++++-- agent/src/collectors/nixos.rs | 47 ------------------------------ agent/src/status/mod.rs | 34 +++++++++++++-------- dashboard/Cargo.toml | 2 +- dashboard/src/main.rs | 2 +- dashboard/src/ui/mod.rs | 21 ++----------- dashboard/src/ui/widgets/system.rs | 16 +++------- shared/Cargo.toml | 2 +- 12 files changed, 51 insertions(+), 105 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d5ddabf..f8b34c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -270,7 +270,7 @@ checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "cm-dashboard" -version = "0.1.20" +version = "0.1.21" dependencies = [ "anyhow", "chrono", @@ -291,7 +291,7 @@ dependencies = [ [[package]] name = "cm-dashboard-agent" -version = "0.1.20" +version = "0.1.21" dependencies = [ "anyhow", "async-trait", @@ -314,7 +314,7 @@ dependencies = [ [[package]] name = "cm-dashboard-shared" -version = "0.1.20" +version = "0.1.21" dependencies = [ "chrono", "serde", diff --git a/agent/Cargo.toml b/agent/Cargo.toml index 13678cd..6dc7c20 100644 --- a/agent/Cargo.toml +++ b/agent/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cm-dashboard-agent" -version = "0.1.21" +version = "0.1.22" edition = "2021" [dependencies] diff --git a/agent/src/agent.rs b/agent/src/agent.rs index 9235463..3826bbc 100644 --- a/agent/src/agent.rs +++ b/agent/src/agent.rs @@ -270,7 +270,7 @@ impl Agent { } /// Handle systemd service control commands - async fn handle_service_control(&self, service_name: &str, action: &ServiceAction) -> Result<()> { + async fn handle_service_control(&mut self, service_name: &str, action: &ServiceAction) -> Result<()> { let action_str = match action { ServiceAction::Start => "start", ServiceAction::Stop => "stop", @@ -300,9 +300,12 @@ impl Agent { // Force refresh metrics after service control to update service status if matches!(action, ServiceAction::Start | ServiceAction::Stop | ServiceAction::Restart) { - info!("Triggering metric refresh after service control"); - // Note: We can't call self.collect_metrics_only() here due to borrowing issues - // The next metric collection cycle will pick up the changes + info!("Triggering immediate metric refresh after service control"); + if let Err(e) = self.collect_metrics_only().await { + error!("Failed to refresh metrics after service control: {}", e); + } else { + info!("Service status refreshed immediately after {} {}", action_str, service_name); + } } Ok(()) diff --git a/agent/src/collectors/disk.rs b/agent/src/collectors/disk.rs index 8eaefbb..dc4724a 100644 --- a/agent/src/collectors/disk.rs +++ b/agent/src/collectors/disk.rs @@ -556,8 +556,8 @@ impl Collector for DiskCollector { // Drive wear level (for SSDs) if let Some(wear) = drive.wear_level { - let wear_status = if wear >= 90.0 { Status::Critical } - else if wear >= 80.0 { Status::Warning } + let wear_status = if wear >= self.config.wear_critical_percent { Status::Critical } + else if wear >= self.config.wear_warning_percent { Status::Warning } else { Status::Ok }; metrics.push(Metric { diff --git a/agent/src/collectors/memory.rs b/agent/src/collectors/memory.rs index 52acddc..f786218 100644 --- a/agent/src/collectors/memory.rs +++ b/agent/src/collectors/memory.rs @@ -187,7 +187,7 @@ impl MemoryCollector { } // Monitor tmpfs (/tmp) usage - if let Ok(tmpfs_metrics) = self.get_tmpfs_metrics() { + if let Ok(tmpfs_metrics) = self.get_tmpfs_metrics(status_tracker) { metrics.extend(tmpfs_metrics); } @@ -195,7 +195,7 @@ impl MemoryCollector { } /// Get tmpfs (/tmp) usage metrics - fn get_tmpfs_metrics(&self) -> Result, CollectorError> { + fn get_tmpfs_metrics(&self, status_tracker: &mut StatusTracker) -> Result, CollectorError> { use std::process::Command; let output = Command::new("df") @@ -249,12 +249,15 @@ impl MemoryCollector { let mut metrics = Vec::new(); let timestamp = chrono::Utc::now().timestamp() as u64; + // Calculate status using same thresholds as main memory + let tmp_status = self.calculate_usage_status("memory_tmp_usage_percent", usage_percent, status_tracker); + metrics.push(Metric { name: "memory_tmp_usage_percent".to_string(), value: MetricValue::Float(usage_percent), unit: Some("%".to_string()), description: Some("tmpfs /tmp usage percentage".to_string()), - status: Status::Ok, + status: tmp_status, timestamp, }); diff --git a/agent/src/collectors/nixos.rs b/agent/src/collectors/nixos.rs index 8ba2286..2948cd1 100644 --- a/agent/src/collectors/nixos.rs +++ b/agent/src/collectors/nixos.rs @@ -10,7 +10,6 @@ use crate::config::NixOSConfig; /// /// Collects NixOS-specific system information including: /// - NixOS version and build information -/// - Currently active/logged in users pub struct NixOSCollector { } @@ -65,27 +64,6 @@ impl NixOSCollector { Err("Could not extract hash from nix store path".into()) } - /// Get currently active users - fn get_active_users(&self) -> Result, Box> { - let output = Command::new("who").output()?; - - if !output.status.success() { - return Err("who command failed".into()); - } - - let who_output = String::from_utf8_lossy(&output.stdout); - let mut users = std::collections::HashSet::new(); - - for line in who_output.lines() { - if let Some(username) = line.split_whitespace().next() { - if !username.is_empty() { - users.insert(username.to_string()); - } - } - } - - Ok(users.into_iter().collect()) - } } #[async_trait] @@ -121,31 +99,6 @@ impl Collector for NixOSCollector { } } - // Collect active users - match self.get_active_users() { - Ok(users) => { - let users_str = users.join(", "); - metrics.push(Metric { - name: "system_active_users".to_string(), - value: MetricValue::String(users_str), - unit: None, - description: Some("Currently active users".to_string()), - status: Status::Ok, - timestamp, - }); - } - Err(e) => { - debug!("Failed to get active users: {}", e); - metrics.push(Metric { - name: "system_active_users".to_string(), - value: MetricValue::String("unknown".to_string()), - unit: None, - description: Some("Active users (failed to detect)".to_string()), - status: Status::Unknown, - timestamp, - }); - } - } // Collect config hash match self.get_config_hash() { diff --git a/agent/src/status/mod.rs b/agent/src/status/mod.rs index 20af9ba..b6aaada 100644 --- a/agent/src/status/mod.rs +++ b/agent/src/status/mod.rs @@ -160,27 +160,37 @@ impl HostStatusManager { /// Process a metric - updates status and queues for aggregated notifications if status changed pub async fn process_metric(&mut self, metric: &Metric, _notification_manager: &mut crate::notifications::NotificationManager) -> bool { - let old_status = self.service_statuses.get(&metric.name).copied(); - let new_status = metric.status; + let old_service_status = self.service_statuses.get(&metric.name).copied(); + let old_host_status = self.current_host_status; + let new_service_status = metric.status; - // Update status - self.update_service_status(metric.name.clone(), new_status); + // Update status (this recalculates host status internally) + self.update_service_status(metric.name.clone(), new_service_status); - // Check if status actually changed (ignore first-time status setting) - if let Some(old_status) = old_status { - if old_status != new_status { - debug!("Status change detected for {}: {:?} -> {:?}", metric.name, old_status, new_status); + let new_host_status = self.current_host_status; + let mut status_changed = false; + + // Check if service status actually changed (ignore first-time status setting) + if let Some(old_service_status) = old_service_status { + if old_service_status != new_service_status { + debug!("Service status change detected for {}: {:?} -> {:?}", metric.name, old_service_status, new_service_status); // Queue change for aggregated notification (not immediate) - self.queue_status_change(&metric.name, old_status, new_status); + self.queue_status_change(&metric.name, old_service_status, new_service_status); - return true; // Status changed - caller should trigger immediate transmission + status_changed = true; } } else { - debug!("Initial status set for {}: {:?}", metric.name, new_status); + debug!("Initial status set for {}: {:?}", metric.name, new_service_status); } - false // No status change (or first-time status) + // Check if host status changed (this should trigger immediate transmission) + if old_host_status != new_host_status { + debug!("Host status change detected: {:?} -> {:?}", old_host_status, new_host_status); + status_changed = true; + } + + status_changed // Return true if either service or host status changed } /// Queue status change for aggregated notification diff --git a/dashboard/Cargo.toml b/dashboard/Cargo.toml index 7acd532..8b279c8 100644 --- a/dashboard/Cargo.toml +++ b/dashboard/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cm-dashboard" -version = "0.1.21" +version = "0.1.22" edition = "2021" [dependencies] diff --git a/dashboard/src/main.rs b/dashboard/src/main.rs index a497d87..6ee4dba 100644 --- a/dashboard/src/main.rs +++ b/dashboard/src/main.rs @@ -14,7 +14,7 @@ use app::Dashboard; /// Get hardcoded version fn get_version() -> &'static str { - "v0.1.21" + "v0.1.22" } /// Check if running inside tmux session diff --git a/dashboard/src/ui/mod.rs b/dashboard/src/ui/mod.rs index 2bc1bdd..b5bad87 100644 --- a/dashboard/src/ui/mod.rs +++ b/dashboard/src/ui/mod.rs @@ -724,24 +724,9 @@ impl TuiApp { spans.push(Span::styled(" ", Typography::title())); } - // Check if this host has a command status that affects the icon - let (status_icon, status_color) = if let Some(host_widgets) = self.host_widgets.get(host) { - match &host_widgets.command_status { - Some(CommandStatus::InProgress { .. }) => { - // Show working indicator for in-progress commands - ("⏳", Theme::highlight()) - } - _ => { - // Normal status icon based on metrics - let host_status = self.calculate_host_status(host, metric_store); - (StatusIcons::get_icon(host_status), Theme::status_color(host_status)) - } - } - } else { - // No host widgets yet, use normal status - let host_status = self.calculate_host_status(host, metric_store); - (StatusIcons::get_icon(host_status), Theme::status_color(host_status)) - }; + // Always show normal status icon based on metrics (no command status at host level) + let host_status = self.calculate_host_status(host, metric_store); + let (status_icon, status_color) = (StatusIcons::get_icon(host_status), Theme::status_color(host_status)); // Add status icon spans.push(Span::styled( diff --git a/dashboard/src/ui/widgets/system.rs b/dashboard/src/ui/widgets/system.rs index c275c0d..ea852a0 100644 --- a/dashboard/src/ui/widgets/system.rs +++ b/dashboard/src/ui/widgets/system.rs @@ -15,7 +15,6 @@ pub struct SystemWidget { // NixOS information nixos_build: Option, config_hash: Option, - active_users: Option, agent_hash: Option, // CPU metrics @@ -33,6 +32,7 @@ pub struct SystemWidget { tmp_used_gb: Option, tmp_total_gb: Option, memory_status: Status, + tmp_status: Status, // Storage metrics (collected from disk metrics) storage_pools: Vec, @@ -66,7 +66,6 @@ impl SystemWidget { Self { nixos_build: None, config_hash: None, - active_users: None, agent_hash: None, cpu_load_1min: None, cpu_load_5min: None, @@ -80,6 +79,7 @@ impl SystemWidget { tmp_used_gb: None, tmp_total_gb: None, memory_status: Status::Unknown, + tmp_status: Status::Unknown, storage_pools: Vec::new(), has_data: false, } @@ -334,11 +334,6 @@ impl Widget for SystemWidget { self.config_hash = Some(hash.clone()); } } - "system_active_users" => { - if let MetricValue::String(users) = &metric.value { - self.active_users = Some(users.clone()); - } - } "agent_version" => { if let MetricValue::String(version) = &metric.value { self.agent_hash = Some(version.clone()); @@ -390,6 +385,7 @@ impl Widget for SystemWidget { "memory_tmp_usage_percent" => { if let MetricValue::Float(usage) = metric.value { self.tmp_usage_percent = Some(usage); + self.tmp_status = metric.status.clone(); } } "memory_tmp_used_gb" => { @@ -432,10 +428,6 @@ impl SystemWidget { Span::styled(format!("Agent: {}", agent_version_text), Typography::secondary()) ])); - let users_text = self.active_users.as_deref().unwrap_or("unknown"); - lines.push(Line::from(vec![ - Span::styled(format!("Active users: {}", users_text), Typography::secondary()) - ])); // CPU section lines.push(Line::from(vec![ @@ -472,7 +464,7 @@ impl SystemWidget { Span::styled(" └─ ", Typography::tree()), ]; tmp_spans.extend(StatusIcons::create_status_spans( - self.memory_status.clone(), + self.tmp_status.clone(), &format!("/tmp: {}", tmp_text) )); lines.push(Line::from(tmp_spans)); diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 67be62f..5462aec 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cm-dashboard-shared" -version = "0.1.21" +version = "0.1.22" edition = "2021" [dependencies]