diff --git a/dashboard/src/ui/mod.rs b/dashboard/src/ui/mod.rs index 628c55a..46ff51b 100644 --- a/dashboard/src/ui/mod.rs +++ b/dashboard/src/ui/mod.rs @@ -6,6 +6,7 @@ use ratatui::{ widgets::{Block, Paragraph}, Frame, }; +use std::collections::HashMap; use std::time::Instant; use tracing::info; @@ -17,24 +18,43 @@ use crate::metrics::{MetricStore, WidgetType}; use cm_dashboard_shared::{Metric, Status}; use theme::{Theme, Layout as ThemeLayout, Typography, Components, StatusIcons}; +/// Widget states for a specific host +#[derive(Clone)] +pub struct HostWidgets { + /// CPU widget state + pub cpu_widget: CpuWidget, + /// Memory widget state + pub memory_widget: MemoryWidget, + /// Services widget state + pub services_widget: ServicesWidget, + /// Backup widget state + pub backup_widget: BackupWidget, + /// Last update time for this host + pub last_update: Option, +} + +impl HostWidgets { + pub fn new() -> Self { + Self { + cpu_widget: CpuWidget::new(), + memory_widget: MemoryWidget::new(), + services_widget: ServicesWidget::new(), + backup_widget: BackupWidget::new(), + last_update: None, + } + } +} + /// Main TUI application pub struct TuiApp { - /// CPU widget - cpu_widget: CpuWidget, - /// Memory widget - memory_widget: MemoryWidget, - /// Services widget - services_widget: ServicesWidget, - /// Backup widget - backup_widget: BackupWidget, + /// Widget states per host (hostname -> HostWidgets) + host_widgets: HashMap, /// Current active host current_host: Option, /// Available hosts available_hosts: Vec, /// Host index for navigation host_index: usize, - /// Last update time - last_update: Option, /// Should quit application should_quit: bool, } @@ -42,45 +62,44 @@ pub struct TuiApp { impl TuiApp { pub fn new() -> Self { Self { - cpu_widget: CpuWidget::new(), - memory_widget: MemoryWidget::new(), - services_widget: ServicesWidget::new(), - backup_widget: BackupWidget::new(), + host_widgets: HashMap::new(), current_host: None, available_hosts: Vec::new(), host_index: 0, - last_update: None, should_quit: false, } } + /// Get or create host widgets for the given hostname + fn get_or_create_host_widgets(&mut self, hostname: &str) -> &mut HostWidgets { + self.host_widgets.entry(hostname.to_string()).or_insert_with(HostWidgets::new) + } + /// Update widgets with metrics from store pub fn update_metrics(&mut self, metric_store: &MetricStore) { - if let Some(ref hostname) = self.current_host { - // Update CPU widget - let cpu_metrics = metric_store.get_metrics_for_widget(hostname, WidgetType::Cpu); - self.cpu_widget.update_from_metrics(&cpu_metrics); - - // Update Memory widget - let memory_metrics = metric_store.get_metrics_for_widget(hostname, WidgetType::Memory); - self.memory_widget.update_from_metrics(&memory_metrics); - - // Update Services widget - get all metrics that start with "service_" - let all_metrics = metric_store.get_metrics_for_host(hostname); + if let Some(hostname) = self.current_host.clone() { + // Get metrics first while hostname is borrowed + let cpu_metrics = metric_store.get_metrics_for_widget(&hostname, WidgetType::Cpu); + let memory_metrics = metric_store.get_metrics_for_widget(&hostname, WidgetType::Memory); + let all_metrics = metric_store.get_metrics_for_host(&hostname); let service_metrics: Vec<&Metric> = all_metrics.iter() .filter(|m| m.name.starts_with("service_")) .copied() .collect(); - self.services_widget.update_from_metrics(&service_metrics); - - // Update Backup widget - get all metrics that start with "backup_" let all_backup_metrics: Vec<&Metric> = all_metrics.iter() .filter(|m| m.name.starts_with("backup_")) .copied() .collect(); - self.backup_widget.update_from_metrics(&all_backup_metrics); - self.last_update = Some(Instant::now()); + // Now get host widgets and update them + let host_widgets = self.get_or_create_host_widgets(&hostname); + + host_widgets.cpu_widget.update_from_metrics(&cpu_metrics); + host_widgets.memory_widget.update_from_metrics(&memory_metrics); + host_widgets.services_widget.update_from_metrics(&service_metrics); + host_widgets.backup_widget.update_from_metrics(&all_backup_metrics); + + host_widgets.last_update = Some(Instant::now()); } } @@ -184,7 +203,12 @@ impl TuiApp { // Render new panel layout self.render_system_panel(frame, left_chunks[0], metric_store); self.render_backup_panel(frame, left_chunks[1]); - self.services_widget.render(frame, content_chunks[1]); // Services takes full right side + + // Render services widget for current host + if let Some(hostname) = self.current_host.clone() { + let host_widgets = self.get_or_create_host_widgets(&hostname); + host_widgets.services_widget.render(frame, content_chunks[1]); // Services takes full right side + } } /// Render btop-style minimal title @@ -241,8 +265,12 @@ impl TuiApp { ]) .split(inner_area); - self.cpu_widget.render(frame, content_chunks[0]); - self.memory_widget.render(frame, content_chunks[1]); + // Get current host widgets, create if none exist + if let Some(hostname) = self.current_host.clone() { + let host_widgets = self.get_or_create_host_widgets(&hostname); + host_widgets.cpu_widget.render(frame, content_chunks[0]); + host_widgets.memory_widget.render(frame, content_chunks[1]); + } self.render_storage_section(frame, content_chunks[2], metric_store); } @@ -250,7 +278,12 @@ impl TuiApp { let backup_block = Components::widget_block("backup"); let inner_area = backup_block.inner(area); frame.render_widget(backup_block, area); - self.backup_widget.render(frame, inner_area); + + // Get current host widgets for backup widget + if let Some(hostname) = self.current_host.clone() { + let host_widgets = self.get_or_create_host_widgets(&hostname); + host_widgets.backup_widget.render(frame, inner_area); + } } diff --git a/dashboard/src/ui/widgets/backup.rs b/dashboard/src/ui/widgets/backup.rs index 184d885..10679cb 100644 --- a/dashboard/src/ui/widgets/backup.rs +++ b/dashboard/src/ui/widgets/backup.rs @@ -10,6 +10,7 @@ use super::Widget; use crate::ui::theme::{Theme, Typography, Components, StatusIcons}; /// Backup widget displaying backup status, services, and repository information +#[derive(Clone)] pub struct BackupWidget { /// Overall backup status overall_status: Status, diff --git a/dashboard/src/ui/widgets/cpu.rs b/dashboard/src/ui/widgets/cpu.rs index c5bf48b..e77e868 100644 --- a/dashboard/src/ui/widgets/cpu.rs +++ b/dashboard/src/ui/widgets/cpu.rs @@ -10,6 +10,7 @@ use super::Widget; use crate::ui::theme::{Theme, Typography, Components, StatusIcons}; /// CPU widget displaying load, temperature, and frequency +#[derive(Clone)] pub struct CpuWidget { /// CPU load averages (1, 5, 15 minutes) load_1min: Option, diff --git a/dashboard/src/ui/widgets/memory.rs b/dashboard/src/ui/widgets/memory.rs index bd6de4a..1030357 100644 --- a/dashboard/src/ui/widgets/memory.rs +++ b/dashboard/src/ui/widgets/memory.rs @@ -10,6 +10,7 @@ use super::Widget; use crate::ui::theme::{Theme, Typography, Components, StatusIcons}; /// Memory widget displaying usage, totals, and swap information +#[derive(Clone)] pub struct MemoryWidget { /// Memory usage percentage usage_percent: Option, diff --git a/dashboard/src/ui/widgets/services.rs b/dashboard/src/ui/widgets/services.rs index 57c87f6..636680e 100644 --- a/dashboard/src/ui/widgets/services.rs +++ b/dashboard/src/ui/widgets/services.rs @@ -12,6 +12,7 @@ use crate::ui::theme::{Theme, Typography, Components, StatusIcons}; use ratatui::style::Style; /// Services widget displaying hierarchical systemd service statuses +#[derive(Clone)] pub struct ServicesWidget { /// Parent services (nginx, docker, etc.) parent_services: HashMap,