diff --git a/Cargo.lock b/Cargo.lock index 40bfb86..0aac06a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -270,7 +270,7 @@ checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "cm-dashboard" -version = "0.1.33" +version = "0.1.34" dependencies = [ "anyhow", "chrono", @@ -291,7 +291,7 @@ dependencies = [ [[package]] name = "cm-dashboard-agent" -version = "0.1.33" +version = "0.1.34" dependencies = [ "anyhow", "async-trait", @@ -314,7 +314,7 @@ dependencies = [ [[package]] name = "cm-dashboard-shared" -version = "0.1.33" +version = "0.1.34" dependencies = [ "chrono", "serde", diff --git a/agent/Cargo.toml b/agent/Cargo.toml index 3433421..eef1edc 100644 --- a/agent/Cargo.toml +++ b/agent/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cm-dashboard-agent" -version = "0.1.34" +version = "0.1.35" edition = "2021" [dependencies] diff --git a/dashboard/Cargo.toml b/dashboard/Cargo.toml index 132f4f2..9394633 100644 --- a/dashboard/Cargo.toml +++ b/dashboard/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cm-dashboard" -version = "0.1.34" +version = "0.1.35" edition = "2021" [dependencies] diff --git a/dashboard/src/ui/mod.rs b/dashboard/src/ui/mod.rs index 4a1d87f..5786de0 100644 --- a/dashboard/src/ui/mod.rs +++ b/dashboard/src/ui/mod.rs @@ -536,48 +536,71 @@ impl TuiApp { if self.available_hosts.is_empty() { let title_text = "cm-dashboard • no hosts discovered"; - let title = Paragraph::new(title_text).style(Typography::title()); + let title = Paragraph::new(title_text) + .style(Style::default().fg(Theme::background()).bg(Theme::highlight())); frame.render_widget(title, area); return; } - // Create spans for each host with status indicators - let mut spans = vec![Span::styled("cm-dashboard • ", Typography::title())]; + // Split the title bar into left and right sections + let chunks = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Min(0), Constraint::Min(0)]) + .split(area); + // Left side: "cm-dashboard" text + let left_span = Span::styled( + "cm-dashboard", + Style::default().fg(Theme::background()).bg(Theme::highlight()) + ); + let left_title = Paragraph::new(Line::from(vec![left_span])) + .style(Style::default().bg(Theme::highlight())); + frame.render_widget(left_title, chunks[0]); + + // Right side: hosts with status indicators + let mut host_spans = Vec::new(); + for (i, host) in self.available_hosts.iter().enumerate() { if i > 0 { - spans.push(Span::styled(" ", Typography::title())); + host_spans.push(Span::styled( + " ", + Style::default().fg(Theme::background()).bg(Theme::highlight()) + )); } // 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)); + let status_icon = StatusIcons::get_icon(host_status); - // Add status icon - spans.push(Span::styled( + // Add status icon with background color as foreground against blue background + host_spans.push(Span::styled( format!("{} ", status_icon), - Style::default().fg(status_color), + Style::default().fg(Theme::background()).bg(Theme::highlight()), )); if Some(host) == self.current_host.as_ref() { - // Selected host in bold bright white - spans.push(Span::styled( + // Selected host in bold background color against blue background + host_spans.push(Span::styled( host.clone(), - Typography::title().add_modifier(Modifier::BOLD), + Style::default() + .fg(Theme::background()) + .bg(Theme::highlight()) + .add_modifier(Modifier::BOLD), )); } else { - // Other hosts in normal style with status color - spans.push(Span::styled( + // Other hosts in normal background color against blue background + host_spans.push(Span::styled( host.clone(), - Style::default().fg(status_color), + Style::default().fg(Theme::background()).bg(Theme::highlight()), )); } } - let title_line = Line::from(spans); - let title = Paragraph::new(vec![title_line]); - - frame.render_widget(title, area); + let host_line = Line::from(host_spans); + let host_title = Paragraph::new(vec![host_line]) + .style(Style::default().bg(Theme::highlight())) + .alignment(ratatui::layout::Alignment::Right); + frame.render_widget(host_title, chunks[1]); } /// Calculate overall status for a host based on its metrics diff --git a/dashboard/src/ui/widgets/system.rs b/dashboard/src/ui/widgets/system.rs index ea852a0..c82c100 100644 --- a/dashboard/src/ui/widgets/system.rs +++ b/dashboard/src/ui/widgets/system.rs @@ -230,9 +230,30 @@ impl SystemWidget { /// Extract pool name from disk metric name fn extract_pool_name(&self, metric_name: &str) -> Option { - if let Some(captures) = metric_name.strip_prefix("disk_") { - if let Some(pos) = captures.find('_') { - return Some(captures[..pos].to_string()); + // Pattern: disk_{pool_name}_{drive_name}_{metric_type} + // Since pool_name can contain underscores, work backwards from known metric suffixes + if metric_name.starts_with("disk_") { + // First try drive-specific metrics that have device names + if let Some(suffix_pos) = metric_name.rfind("_temperature") + .or_else(|| metric_name.rfind("_wear_percent")) + .or_else(|| metric_name.rfind("_health")) { + // Find the second-to-last underscore to get pool name + let before_suffix = &metric_name[..suffix_pos]; + if let Some(drive_start) = before_suffix.rfind('_') { + return Some(metric_name[5..drive_start].to_string()); // Skip "disk_" + } + } + // For pool-level metrics (usage_percent, used_gb, total_gb), take everything before the metric suffix + else if let Some(suffix_pos) = metric_name.rfind("_usage_percent") + .or_else(|| metric_name.rfind("_used_gb")) + .or_else(|| metric_name.rfind("_total_gb")) { + return Some(metric_name[5..suffix_pos].to_string()); // Skip "disk_" + } + // Fallback to old behavior for unknown patterns + else if let Some(captures) = metric_name.strip_prefix("disk_") { + if let Some(pos) = captures.find('_') { + return Some(captures[..pos].to_string()); + } } } None @@ -240,10 +261,18 @@ impl SystemWidget { /// Extract drive name from disk metric name fn extract_drive_name(&self, metric_name: &str) -> Option { - // Pattern: disk_pool_drive_metric - let parts: Vec<&str> = metric_name.split('_').collect(); - if parts.len() >= 3 && parts[0] == "disk" { - return Some(parts[2].to_string()); + // Pattern: disk_{pool_name}_{drive_name}_{metric_type} + // Since pool_name can contain underscores, work backwards from known metric suffixes + if metric_name.starts_with("disk_") { + if let Some(suffix_pos) = metric_name.rfind("_temperature") + .or_else(|| metric_name.rfind("_wear_percent")) + .or_else(|| metric_name.rfind("_health")) { + // Find the second-to-last underscore to get the drive name + let before_suffix = &metric_name[..suffix_pos]; + if let Some(drive_start) = before_suffix.rfind('_') { + return Some(before_suffix[drive_start + 1..].to_string()); + } + } } None } diff --git a/shared/Cargo.toml b/shared/Cargo.toml index b3134df..baeedb6 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cm-dashboard-shared" -version = "0.1.34" +version = "0.1.35" edition = "2021" [dependencies]