use cm_dashboard_shared::{Metric, Status}; use ratatui::{ layout::{Constraint, Direction, Layout, Rect}, widgets::Paragraph, Frame, }; use tracing::debug; 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, load_5min: Option, load_15min: Option, /// CPU temperature in Celsius temperature: Option, /// CPU frequency in MHz frequency: Option, /// Aggregated status status: Status, /// Last update indicator has_data: bool, } impl CpuWidget { pub fn new() -> Self { Self { load_1min: None, load_5min: None, load_15min: None, temperature: None, frequency: None, status: Status::Unknown, has_data: false, } } /// Format load average for display fn format_load(&self) -> String { match (self.load_1min, self.load_5min, self.load_15min) { (Some(l1), Some(l5), Some(l15)) => { format!("{:.2} {:.2} {:.2}", l1, l5, l15) } _ => "— — —".to_string(), } } /// Format frequency for display fn format_frequency(&self) -> String { match self.frequency { Some(freq) => format!("{:.1} MHz", freq), None => "— MHz".to_string(), } } } impl Widget for CpuWidget { fn update_from_metrics(&mut self, metrics: &[&Metric]) { debug!("CPU widget updating with {} metrics", metrics.len()); // Reset status aggregation let mut statuses = Vec::new(); for metric in metrics { match metric.name.as_str() { "cpu_load_1min" => { if let Some(value) = metric.value.as_f32() { self.load_1min = Some(value); statuses.push(metric.status); } } "cpu_load_5min" => { if let Some(value) = metric.value.as_f32() { self.load_5min = Some(value); statuses.push(metric.status); } } "cpu_load_15min" => { if let Some(value) = metric.value.as_f32() { self.load_15min = Some(value); statuses.push(metric.status); } } "cpu_temperature_celsius" => { if let Some(value) = metric.value.as_f32() { self.temperature = Some(value); statuses.push(metric.status); } } "cpu_frequency_mhz" => { if let Some(value) = metric.value.as_f32() { self.frequency = Some(value); statuses.push(metric.status); } } _ => {} } } // Aggregate status self.status = if statuses.is_empty() { Status::Unknown } else { Status::aggregate(&statuses) }; self.has_data = !metrics.is_empty(); debug!("CPU widget updated: load={:?}, temp={:?}, freq={:?}, status={:?}", self.load_1min, self.temperature, self.frequency, self.status); } fn render(&mut self, frame: &mut Frame, area: Rect) { let content_chunks = Layout::default().direction(Direction::Vertical).constraints([Constraint::Length(1), Constraint::Length(1)]).split(area); let cpu_title = Paragraph::new("CPU:").style(Typography::widget_title()); frame.render_widget(cpu_title, content_chunks[0]); let load_freq_spans = StatusIcons::create_status_spans(self.status, &format!("Load: {} • {}", self.format_load(), self.format_frequency())); let load_freq_para = Paragraph::new(ratatui::text::Line::from(load_freq_spans)); frame.render_widget(load_freq_para, content_chunks[1]); } } impl Default for CpuWidget { fn default() -> Self { Self::new() } }