Compare commits

...

6 Commits

Author SHA1 Message Date
833010e270 Bump version to v0.1.192
All checks were successful
Build and Release / build-and-release (push) Successful in 1m8s
2025-11-27 18:34:53 +01:00
549d9d1c72 Replace whale emoji with ASCII 'D' for performance
Emoji rendering in terminals can be very slow, especially when rendered in the hot path (every frame for every docker image). The whale emoji 🐋 was causing significant rendering delays.

Temporary change to ASCII 'D' to test if emoji was the performance issue.
2025-11-27 18:34:27 +01:00
9b84b70581 Bump version to v0.1.191
All checks were successful
Build and Release / build-and-release (push) Successful in 1m8s
2025-11-27 18:16:49 +01:00
92c3ee3f2a Add Docker whale icon for docker images
Docker images now display with distinctive 🐋 whale icon in blue (highlight color) instead of status icons. This provides clear visual identification that these are docker images while not implying operational status.
2025-11-27 18:16:33 +01:00
1be55f765d Bump version to v0.1.190
All checks were successful
Build and Release / build-and-release (push) Successful in 1m9s
2025-11-27 18:09:49 +01:00
2f94a4b853 Add service_type field to separate data from presentation
Changes:
- Add service_type field to SubServiceData: 'nginx_site', 'container', 'image'
- Agent sends pure data without display formatting
- Dashboard checks service_type to decide presentation
- Docker images now display without status icon (service_type='image')
- Remove unused image_size_str from docker images tuple

Clean separation: agent provides data, dashboard handles display logic.
2025-11-27 18:09:20 +01:00
7 changed files with 80 additions and 40 deletions

6
Cargo.lock generated
View File

@@ -279,7 +279,7 @@ checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
[[package]] [[package]]
name = "cm-dashboard" name = "cm-dashboard"
version = "0.1.188" version = "0.1.191"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
@@ -301,7 +301,7 @@ dependencies = [
[[package]] [[package]]
name = "cm-dashboard-agent" name = "cm-dashboard-agent"
version = "0.1.188" version = "0.1.191"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@@ -324,7 +324,7 @@ dependencies = [
[[package]] [[package]]
name = "cm-dashboard-shared" name = "cm-dashboard-shared"
version = "0.1.188" version = "0.1.191"
dependencies = [ dependencies = [
"chrono", "chrono",
"serde", "serde",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "cm-dashboard-agent" name = "cm-dashboard-agent"
version = "0.1.189" version = "0.1.192"
edition = "2021" edition = "2021"
[dependencies] [dependencies]

View File

@@ -113,6 +113,7 @@ impl SystemdCollector {
name: site_name.clone(), name: site_name.clone(),
service_status: self.calculate_service_status(&site_name, &site_status), service_status: self.calculate_service_status(&site_name, &site_status),
metrics, metrics,
service_type: "nginx_site".to_string(),
}); });
} }
} }
@@ -128,12 +129,13 @@ impl SystemdCollector {
name: container_name.clone(), name: container_name.clone(),
service_status: self.calculate_service_status(&container_name, &container_status), service_status: self.calculate_service_status(&container_name, &container_status),
metrics, metrics,
service_type: "container".to_string(),
}); });
} }
// Add Docker images // Add Docker images
let docker_images = self.get_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(); let mut metrics = Vec::new();
metrics.push(SubServiceMetric { metrics.push(SubServiceMetric {
label: "size".to_string(), label: "size".to_string(),
@@ -142,9 +144,10 @@ impl SystemdCollector {
}); });
sub_services.push(SubServiceData { 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), service_status: self.calculate_service_status(&image_name, &image_status),
metrics, metrics,
service_type: "image".to_string(),
}); });
} }
} }
@@ -824,7 +827,7 @@ impl SystemdCollector {
} }
/// Get docker images as sub-services /// 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(); let mut images = Vec::new();
// Check if docker is available (cm-agent user is in docker group) with 3 second timeout // Check if docker is available (cm-agent user is in docker group) with 3 second timeout
let output = Command::new("timeout") let output = Command::new("timeout")
@@ -865,9 +868,8 @@ impl SystemdCollector {
let size_mb = self.parse_docker_size(size_str); let size_mb = self.parse_docker_size(size_str);
images.push(( images.push((
format!("I {}", image_name), image_name.to_string(),
"inactive".to_string(), // Images are informational - use inactive for neutral display "inactive".to_string(), // Images are informational - use inactive for neutral display
size_str.to_string(),
size_mb size_mb
)); ));
} }

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "cm-dashboard" name = "cm-dashboard"
version = "0.1.189" version = "0.1.192"
edition = "2021" edition = "2021"
[dependencies] [dependencies]

View File

@@ -32,6 +32,7 @@ struct ServiceInfo {
disk_gb: Option<f32>, disk_gb: Option<f32>,
metrics: Vec<(String, f32, Option<String>)>, // (label, value, unit) metrics: Vec<(String, f32, Option<String>)>, // (label, value, unit)
widget_status: Status, widget_status: Status,
service_type: String, // "nginx_site", "container", "image", or empty for parent services
} }
impl ServicesWidget { impl ServicesWidget {
@@ -169,7 +170,7 @@ impl ServicesWidget {
// Convert Status enum to display text for sub-services // Convert Status enum to display text for sub-services
match info.widget_status { match info.widget_status {
Status::Ok => "active", Status::Ok => "active",
Status::Inactive => "inactive", Status::Inactive => "inactive",
Status::Critical => "failed", Status::Critical => "failed",
Status::Pending => "pending", Status::Pending => "pending",
Status::Warning => "warning", Status::Warning => "warning",
@@ -179,32 +180,62 @@ impl ServicesWidget {
}; };
let tree_symbol = if is_last { "└─" } else { "├─" }; let tree_symbol = if is_last { "└─" } else { "├─" };
vec![ // Docker images use docker whale icon
// Indentation and tree prefix if info.service_type == "image" {
ratatui::text::Span::styled( vec![
format!(" {} ", tree_symbol), // Indentation and tree prefix
Typography::tree(), ratatui::text::Span::styled(
), format!(" {} ", tree_symbol),
// Status icon Typography::tree(),
ratatui::text::Span::styled( ),
format!("{} ", icon), // Docker icon (simple character for performance)
Style::default().fg(status_color).bg(Theme::background()), ratatui::text::Span::styled(
), "D ".to_string(),
// Service name Style::default().fg(Theme::highlight()).bg(Theme::background()),
ratatui::text::Span::styled( ),
format!("{:<18} ", short_name), // Service name
Style::default() ratatui::text::Span::styled(
.fg(Theme::secondary_text()) format!("{:<18} ", short_name),
.bg(Theme::background()), Style::default()
), .fg(Theme::secondary_text())
// Status/latency text .bg(Theme::background()),
ratatui::text::Span::styled( ),
status_str, // Status/metrics text
Style::default() ratatui::text::Span::styled(
.fg(Theme::secondary_text()) status_str,
.bg(Theme::background()), 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 /// Move selection up
@@ -282,9 +313,10 @@ impl Widget for ServicesWidget {
disk_gb: Some(service.disk_gb), disk_gb: Some(service.disk_gb),
metrics: Vec::new(), // Parent services don't have custom metrics metrics: Vec::new(), // Parent services don't have custom metrics
widget_status: service.service_status, widget_status: service.service_status,
service_type: String::new(), // Parent services have no type
}; };
self.parent_services.insert(service.name.clone(), parent_info); self.parent_services.insert(service.name.clone(), parent_info);
// Process sub-services if any // Process sub-services if any
if !service.sub_services.is_empty() { if !service.sub_services.is_empty() {
let mut sub_list = Vec::new(); let mut sub_list = Vec::new();
@@ -293,12 +325,13 @@ impl Widget for ServicesWidget {
let metrics: Vec<(String, f32, Option<String>)> = sub_service.metrics.iter() let metrics: Vec<(String, f32, Option<String>)> = sub_service.metrics.iter()
.map(|m| (m.label.clone(), m.value, m.unit.clone())) .map(|m| (m.label.clone(), m.value, m.unit.clone()))
.collect(); .collect();
let sub_info = ServiceInfo { let sub_info = ServiceInfo {
memory_mb: None, // Not used for sub-services memory_mb: None, // Not used for sub-services
disk_gb: None, // Not used for sub-services disk_gb: None, // Not used for sub-services
metrics, metrics,
widget_status: sub_service.service_status, widget_status: sub_service.service_status,
service_type: sub_service.service_type.clone(),
}; };
sub_list.push((sub_service.name.clone(), sub_info)); sub_list.push((sub_service.name.clone(), sub_info));
} }
@@ -342,6 +375,7 @@ impl ServicesWidget {
disk_gb: None, disk_gb: None,
metrics: Vec::new(), metrics: Vec::new(),
widget_status: Status::Unknown, widget_status: Status::Unknown,
service_type: String::new(),
}); });
if metric.name.ends_with("_status") { if metric.name.ends_with("_status") {
@@ -377,6 +411,7 @@ impl ServicesWidget {
disk_gb: None, disk_gb: None,
metrics: Vec::new(), metrics: Vec::new(),
widget_status: Status::Unknown, widget_status: Status::Unknown,
service_type: String::new(), // Unknown type in legacy path
}, },
)); ));
&mut sub_service_list.last_mut().unwrap().1 &mut sub_service_list.last_mut().unwrap().1

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "cm-dashboard-shared" name = "cm-dashboard-shared"
version = "0.1.189" version = "0.1.192"
edition = "2021" edition = "2021"
[dependencies] [dependencies]

View File

@@ -149,6 +149,9 @@ pub struct SubServiceData {
pub name: String, pub name: String,
pub service_status: Status, pub service_status: Status,
pub metrics: Vec<SubServiceMetric>, pub metrics: Vec<SubServiceMetric>,
/// Type of sub-service: "nginx_site", "container", "image"
#[serde(default)]
pub service_type: String,
} }
/// Individual metric for a sub-service /// Individual metric for a sub-service