From 5b12c122285733d7455663601658de042067e385 Mon Sep 17 00:00:00 2001 From: Christoffer Martinsson Date: Tue, 28 Oct 2025 14:38:33 +0100 Subject: [PATCH] Fix transitional icons by always storing pending transitions for visual feedback - Store pending transitions even for redundant commands (start active service) - Add 3-second timeout for redundant command visual feedback - Include timestamp in pending transitions to enable timeout clearing - Show directional arrows immediately regardless of command validation result - Fix core issue where state validation prevented visual feedback storage Now pressing s/S/r always shows immediate directional arrows, even for redundant operations, providing consistent visual feedback to users. --- Cargo.lock | 6 +++--- agent/Cargo.toml | 2 +- dashboard/Cargo.toml | 2 +- dashboard/src/main.rs | 2 +- dashboard/src/ui/mod.rs | 29 ++++++++++++++++------------ dashboard/src/ui/widgets/services.rs | 10 +++++----- shared/Cargo.toml | 2 +- 7 files changed, 29 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3c1606b..69eeb99 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -270,7 +270,7 @@ checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "cm-dashboard" -version = "0.1.24" +version = "0.1.25" dependencies = [ "anyhow", "chrono", @@ -291,7 +291,7 @@ dependencies = [ [[package]] name = "cm-dashboard-agent" -version = "0.1.24" +version = "0.1.25" dependencies = [ "anyhow", "async-trait", @@ -314,7 +314,7 @@ dependencies = [ [[package]] name = "cm-dashboard-shared" -version = "0.1.24" +version = "0.1.25" dependencies = [ "chrono", "serde", diff --git a/agent/Cargo.toml b/agent/Cargo.toml index 186fdf2..21787e4 100644 --- a/agent/Cargo.toml +++ b/agent/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cm-dashboard-agent" -version = "0.1.25" +version = "0.1.26" edition = "2021" [dependencies] diff --git a/dashboard/Cargo.toml b/dashboard/Cargo.toml index a1416d7..ff3d3db 100644 --- a/dashboard/Cargo.toml +++ b/dashboard/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cm-dashboard" -version = "0.1.25" +version = "0.1.26" edition = "2021" [dependencies] diff --git a/dashboard/src/main.rs b/dashboard/src/main.rs index 527f4f0..4a31d04 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.25" + "v0.1.26" } /// Check if running inside tmux session diff --git a/dashboard/src/ui/mod.rs b/dashboard/src/ui/mod.rs index a258525..178aafd 100644 --- a/dashboard/src/ui/mod.rs +++ b/dashboard/src/ui/mod.rs @@ -65,7 +65,7 @@ pub struct HostWidgets { /// Last update time for this host pub last_update: Option, /// Pending service transitions for immediate visual feedback - pub pending_service_transitions: HashMap, // service_name -> (command_type, original_status) + pub pending_service_transitions: HashMap, // service_name -> (command_type, original_status, start_time) } impl HostWidgets { @@ -447,26 +447,31 @@ impl TuiApp { _ => true, // Default: allow other combinations }; - if should_execute { - if let Some(host_widgets) = self.host_widgets.get_mut(hostname) { - // Store the pending transition for immediate visual feedback - host_widgets.pending_service_transitions.insert( - target.clone(), - (command_type, current_status.unwrap_or_else(|| "unknown".to_string())) - ); - } + // ALWAYS store the pending transition for immediate visual feedback, even if we don't execute + if let Some(host_widgets) = self.host_widgets.get_mut(hostname) { + host_widgets.pending_service_transitions.insert( + target.clone(), + (command_type, current_status.unwrap_or_else(|| "unknown".to_string()), Instant::now()) + ); } should_execute } - /// Clear pending transitions when real status updates arrive + /// Clear pending transitions when real status updates arrive or timeout fn clear_completed_transitions(&mut self, hostname: &str, service_metrics: &[&Metric]) { if let Some(host_widgets) = self.host_widgets.get_mut(hostname) { let mut completed_services = Vec::new(); + let now = Instant::now(); - // Check each pending transition to see if real status has changed - for (service_name, (command_type, original_status)) in &host_widgets.pending_service_transitions { + // Check each pending transition to see if real status has changed or timed out + for (service_name, (command_type, original_status, start_time)) in &host_widgets.pending_service_transitions { + // Clear if too much time has passed (3 seconds for redundant commands) + if now.duration_since(*start_time).as_secs() > 3 { + completed_services.push(service_name.clone()); + continue; + } + // Look for status metric for this service for metric in service_metrics { if metric.name == format!("service_{}_status", service_name) { diff --git a/dashboard/src/ui/widgets/services.rs b/dashboard/src/ui/widgets/services.rs index f76ed7b..1779288 100644 --- a/dashboard/src/ui/widgets/services.rs +++ b/dashboard/src/ui/widgets/services.rs @@ -129,9 +129,9 @@ impl ServicesWidget { } /// Get status icon for service, considering pending transitions for visual feedback - fn get_service_icon_and_status(&self, service_name: &str, info: &ServiceInfo, pending_transitions: &HashMap) -> (String, String, ratatui::prelude::Color) { + fn get_service_icon_and_status(&self, service_name: &str, info: &ServiceInfo, pending_transitions: &HashMap) -> (String, String, ratatui::prelude::Color) { // Check if this service has a pending transition - if let Some((command_type, _original_status)) = pending_transitions.get(service_name) { + if let Some((command_type, _original_status, _start_time)) = pending_transitions.get(service_name) { // Show transitional icons for pending commands let (icon, status_text) = match command_type { CommandType::ServiceRestart => ("↻", "restarting"), @@ -162,7 +162,7 @@ impl ServicesWidget { name: &str, info: &ServiceInfo, is_last: bool, - pending_transitions: &HashMap, + pending_transitions: &HashMap, ) -> Vec> { // Truncate long sub-service names to fit layout (accounting for indentation) let short_name = if name.len() > 18 { @@ -440,7 +440,7 @@ impl Widget for ServicesWidget { impl ServicesWidget { /// Render with focus, scroll, and pending transitions for visual feedback - pub fn render_with_transitions(&mut self, frame: &mut Frame, area: Rect, is_focused: bool, scroll_offset: usize, pending_transitions: &HashMap) { + pub fn render_with_transitions(&mut self, frame: &mut Frame, area: Rect, is_focused: bool, scroll_offset: usize, pending_transitions: &HashMap) { let services_block = if is_focused { Components::focused_widget_block("services") } else { @@ -474,7 +474,7 @@ impl ServicesWidget { } /// Render services list with pending transitions awareness - fn render_services_with_transitions(&mut self, frame: &mut Frame, area: Rect, is_focused: bool, scroll_offset: usize, pending_transitions: &HashMap) { + fn render_services_with_transitions(&mut self, frame: &mut Frame, area: Rect, is_focused: bool, scroll_offset: usize, pending_transitions: &HashMap) { // Build hierarchical service list for display (same as existing logic) let mut display_lines: Vec<(String, Status, bool, Option<(ServiceInfo, bool)>)> = Vec::new(); diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 138b9fb..e07e99f 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cm-dashboard-shared" -version = "0.1.25" +version = "0.1.26" edition = "2021" [dependencies]