diff --git a/Cargo.lock b/Cargo.lock index 727ac01..7f534f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -279,7 +279,7 @@ checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "cm-dashboard" -version = "0.1.188" +version = "0.1.189" dependencies = [ "anyhow", "chrono", @@ -301,7 +301,7 @@ dependencies = [ [[package]] name = "cm-dashboard-agent" -version = "0.1.188" +version = "0.1.189" dependencies = [ "anyhow", "async-trait", @@ -324,7 +324,7 @@ dependencies = [ [[package]] name = "cm-dashboard-shared" -version = "0.1.188" +version = "0.1.189" dependencies = [ "chrono", "serde", diff --git a/agent/src/collectors/systemd.rs b/agent/src/collectors/systemd.rs index 25019e6..bcaa6be 100644 --- a/agent/src/collectors/systemd.rs +++ b/agent/src/collectors/systemd.rs @@ -113,6 +113,7 @@ impl SystemdCollector { name: site_name.clone(), service_status: self.calculate_service_status(&site_name, &site_status), metrics, + service_type: "nginx_site".to_string(), }); } } @@ -128,12 +129,13 @@ impl SystemdCollector { name: container_name.clone(), service_status: self.calculate_service_status(&container_name, &container_status), metrics, + service_type: "container".to_string(), }); } // Add Docker images let docker_images = self.get_docker_images(); - for (image_name, image_status, image_size_str, image_size_mb) in docker_images { + for (image_name, image_status, image_size_mb) in docker_images { let mut metrics = Vec::new(); metrics.push(SubServiceMetric { label: "size".to_string(), @@ -142,9 +144,10 @@ impl SystemdCollector { }); sub_services.push(SubServiceData { - name: format!("{} ({})", image_name, image_size_str), + name: image_name.to_string(), service_status: self.calculate_service_status(&image_name, &image_status), metrics, + service_type: "image".to_string(), }); } } @@ -824,7 +827,7 @@ impl SystemdCollector { } /// Get docker images as sub-services - fn get_docker_images(&self) -> Vec<(String, String, String, f32)> { + fn get_docker_images(&self) -> Vec<(String, String, f32)> { let mut images = Vec::new(); // Check if docker is available (cm-agent user is in docker group) with 3 second timeout let output = Command::new("timeout") @@ -865,9 +868,8 @@ impl SystemdCollector { let size_mb = self.parse_docker_size(size_str); images.push(( - format!("I {}", image_name), + image_name.to_string(), "inactive".to_string(), // Images are informational - use inactive for neutral display - size_str.to_string(), size_mb )); } diff --git a/dashboard/src/ui/widgets/services.rs b/dashboard/src/ui/widgets/services.rs index 7f77ccd..a21a6a1 100644 --- a/dashboard/src/ui/widgets/services.rs +++ b/dashboard/src/ui/widgets/services.rs @@ -32,6 +32,7 @@ struct ServiceInfo { disk_gb: Option, metrics: Vec<(String, f32, Option)>, // (label, value, unit) widget_status: Status, + service_type: String, // "nginx_site", "container", "image", or empty for parent services } impl ServicesWidget { @@ -169,7 +170,7 @@ impl ServicesWidget { // Convert Status enum to display text for sub-services match info.widget_status { Status::Ok => "active", - Status::Inactive => "inactive", + Status::Inactive => "inactive", Status::Critical => "failed", Status::Pending => "pending", Status::Warning => "warning", @@ -179,32 +180,57 @@ impl ServicesWidget { }; let tree_symbol = if is_last { "└─" } else { "├─" }; - vec![ - // Indentation and tree prefix - ratatui::text::Span::styled( - format!(" {} ", tree_symbol), - Typography::tree(), - ), - // Status icon - ratatui::text::Span::styled( - format!("{} ", icon), - Style::default().fg(status_color).bg(Theme::background()), - ), - // Service name - ratatui::text::Span::styled( - format!("{:<18} ", short_name), - Style::default() - .fg(Theme::secondary_text()) - .bg(Theme::background()), - ), - // Status/latency text - ratatui::text::Span::styled( - status_str, - Style::default() - .fg(Theme::secondary_text()) - .bg(Theme::background()), - ), - ] + // Docker images don't have status icons + if info.service_type == "image" { + vec![ + // Indentation and tree prefix + ratatui::text::Span::styled( + format!(" {} ", tree_symbol), + Typography::tree(), + ), + // Service name (no icon for images) + ratatui::text::Span::styled( + format!("{:<18} ", short_name), + Style::default() + .fg(Theme::secondary_text()) + .bg(Theme::background()), + ), + // Status/metrics text + ratatui::text::Span::styled( + status_str, + Style::default() + .fg(Theme::secondary_text()) + .bg(Theme::background()), + ), + ] + } else { + vec![ + // Indentation and tree prefix + ratatui::text::Span::styled( + format!(" {} ", tree_symbol), + Typography::tree(), + ), + // Status icon + ratatui::text::Span::styled( + format!("{} ", icon), + Style::default().fg(status_color).bg(Theme::background()), + ), + // Service name + ratatui::text::Span::styled( + format!("{:<18} ", short_name), + Style::default() + .fg(Theme::secondary_text()) + .bg(Theme::background()), + ), + // Status/latency text + ratatui::text::Span::styled( + status_str, + Style::default() + .fg(Theme::secondary_text()) + .bg(Theme::background()), + ), + ] + } } /// Move selection up @@ -282,9 +308,10 @@ impl Widget for ServicesWidget { disk_gb: Some(service.disk_gb), metrics: Vec::new(), // Parent services don't have custom metrics widget_status: service.service_status, + service_type: String::new(), // Parent services have no type }; self.parent_services.insert(service.name.clone(), parent_info); - + // Process sub-services if any if !service.sub_services.is_empty() { let mut sub_list = Vec::new(); @@ -293,12 +320,13 @@ impl Widget for ServicesWidget { let metrics: Vec<(String, f32, Option)> = sub_service.metrics.iter() .map(|m| (m.label.clone(), m.value, m.unit.clone())) .collect(); - + let sub_info = ServiceInfo { memory_mb: None, // Not used for sub-services disk_gb: None, // Not used for sub-services metrics, widget_status: sub_service.service_status, + service_type: sub_service.service_type.clone(), }; sub_list.push((sub_service.name.clone(), sub_info)); } @@ -342,6 +370,7 @@ impl ServicesWidget { disk_gb: None, metrics: Vec::new(), widget_status: Status::Unknown, + service_type: String::new(), }); if metric.name.ends_with("_status") { @@ -377,6 +406,7 @@ impl ServicesWidget { disk_gb: None, metrics: Vec::new(), widget_status: Status::Unknown, + service_type: String::new(), // Unknown type in legacy path }, )); &mut sub_service_list.last_mut().unwrap().1 diff --git a/shared/src/agent_data.rs b/shared/src/agent_data.rs index 0d76e28..97e7e42 100644 --- a/shared/src/agent_data.rs +++ b/shared/src/agent_data.rs @@ -149,6 +149,9 @@ pub struct SubServiceData { pub name: String, pub service_status: Status, pub metrics: Vec, + /// Type of sub-service: "nginx_site", "container", "image" + #[serde(default)] + pub service_type: String, } /// Individual metric for a sub-service