Implement per-host widget cache for instant host switching
Resolves widget data persistence issue where switching hosts left stale data from the previous host displayed in widgets. Key improvements: - Add Clone derives to all widget structs (CpuWidget, MemoryWidget, ServicesWidget, BackupWidget) - Create HostWidgets struct to cache widget states per hostname - Update TuiApp with HashMap<String, HostWidgets> for per-host storage - Fix borrowing issues by cloning hostname before mutable self borrow - Implement instant widget state restoration when switching hosts Tab key host switching now displays cached widget data for each host without stale information persistence between switches.
This commit is contained in:
parent
46cc813a68
commit
4b7d08153c
@ -6,6 +6,7 @@ use ratatui::{
|
|||||||
widgets::{Block, Paragraph},
|
widgets::{Block, Paragraph},
|
||||||
Frame,
|
Frame,
|
||||||
};
|
};
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
@ -17,24 +18,43 @@ use crate::metrics::{MetricStore, WidgetType};
|
|||||||
use cm_dashboard_shared::{Metric, Status};
|
use cm_dashboard_shared::{Metric, Status};
|
||||||
use theme::{Theme, Layout as ThemeLayout, Typography, Components, StatusIcons};
|
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<Instant>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
/// Main TUI application
|
||||||
pub struct TuiApp {
|
pub struct TuiApp {
|
||||||
/// CPU widget
|
/// Widget states per host (hostname -> HostWidgets)
|
||||||
cpu_widget: CpuWidget,
|
host_widgets: HashMap<String, HostWidgets>,
|
||||||
/// Memory widget
|
|
||||||
memory_widget: MemoryWidget,
|
|
||||||
/// Services widget
|
|
||||||
services_widget: ServicesWidget,
|
|
||||||
/// Backup widget
|
|
||||||
backup_widget: BackupWidget,
|
|
||||||
/// Current active host
|
/// Current active host
|
||||||
current_host: Option<String>,
|
current_host: Option<String>,
|
||||||
/// Available hosts
|
/// Available hosts
|
||||||
available_hosts: Vec<String>,
|
available_hosts: Vec<String>,
|
||||||
/// Host index for navigation
|
/// Host index for navigation
|
||||||
host_index: usize,
|
host_index: usize,
|
||||||
/// Last update time
|
|
||||||
last_update: Option<Instant>,
|
|
||||||
/// Should quit application
|
/// Should quit application
|
||||||
should_quit: bool,
|
should_quit: bool,
|
||||||
}
|
}
|
||||||
@ -42,45 +62,44 @@ pub struct TuiApp {
|
|||||||
impl TuiApp {
|
impl TuiApp {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
cpu_widget: CpuWidget::new(),
|
host_widgets: HashMap::new(),
|
||||||
memory_widget: MemoryWidget::new(),
|
|
||||||
services_widget: ServicesWidget::new(),
|
|
||||||
backup_widget: BackupWidget::new(),
|
|
||||||
current_host: None,
|
current_host: None,
|
||||||
available_hosts: Vec::new(),
|
available_hosts: Vec::new(),
|
||||||
host_index: 0,
|
host_index: 0,
|
||||||
last_update: None,
|
|
||||||
should_quit: false,
|
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
|
/// Update widgets with metrics from store
|
||||||
pub fn update_metrics(&mut self, metric_store: &MetricStore) {
|
pub fn update_metrics(&mut self, metric_store: &MetricStore) {
|
||||||
if let Some(ref hostname) = self.current_host {
|
if let Some(hostname) = self.current_host.clone() {
|
||||||
// Update CPU widget
|
// Get metrics first while hostname is borrowed
|
||||||
let cpu_metrics = metric_store.get_metrics_for_widget(hostname, WidgetType::Cpu);
|
let cpu_metrics = metric_store.get_metrics_for_widget(&hostname, WidgetType::Cpu);
|
||||||
self.cpu_widget.update_from_metrics(&cpu_metrics);
|
let memory_metrics = metric_store.get_metrics_for_widget(&hostname, WidgetType::Memory);
|
||||||
|
let all_metrics = metric_store.get_metrics_for_host(&hostname);
|
||||||
// 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);
|
|
||||||
let service_metrics: Vec<&Metric> = all_metrics.iter()
|
let service_metrics: Vec<&Metric> = all_metrics.iter()
|
||||||
.filter(|m| m.name.starts_with("service_"))
|
.filter(|m| m.name.starts_with("service_"))
|
||||||
.copied()
|
.copied()
|
||||||
.collect();
|
.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()
|
let all_backup_metrics: Vec<&Metric> = all_metrics.iter()
|
||||||
.filter(|m| m.name.starts_with("backup_"))
|
.filter(|m| m.name.starts_with("backup_"))
|
||||||
.copied()
|
.copied()
|
||||||
.collect();
|
.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
|
// Render new panel layout
|
||||||
self.render_system_panel(frame, left_chunks[0], metric_store);
|
self.render_system_panel(frame, left_chunks[0], metric_store);
|
||||||
self.render_backup_panel(frame, left_chunks[1]);
|
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
|
/// Render btop-style minimal title
|
||||||
@ -241,8 +265,12 @@ impl TuiApp {
|
|||||||
])
|
])
|
||||||
.split(inner_area);
|
.split(inner_area);
|
||||||
|
|
||||||
self.cpu_widget.render(frame, content_chunks[0]);
|
// Get current host widgets, create if none exist
|
||||||
self.memory_widget.render(frame, content_chunks[1]);
|
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);
|
self.render_storage_section(frame, content_chunks[2], metric_store);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,7 +278,12 @@ impl TuiApp {
|
|||||||
let backup_block = Components::widget_block("backup");
|
let backup_block = Components::widget_block("backup");
|
||||||
let inner_area = backup_block.inner(area);
|
let inner_area = backup_block.inner(area);
|
||||||
frame.render_widget(backup_block, 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,7 @@ use super::Widget;
|
|||||||
use crate::ui::theme::{Theme, Typography, Components, StatusIcons};
|
use crate::ui::theme::{Theme, Typography, Components, StatusIcons};
|
||||||
|
|
||||||
/// Backup widget displaying backup status, services, and repository information
|
/// Backup widget displaying backup status, services, and repository information
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct BackupWidget {
|
pub struct BackupWidget {
|
||||||
/// Overall backup status
|
/// Overall backup status
|
||||||
overall_status: Status,
|
overall_status: Status,
|
||||||
|
|||||||
@ -10,6 +10,7 @@ use super::Widget;
|
|||||||
use crate::ui::theme::{Theme, Typography, Components, StatusIcons};
|
use crate::ui::theme::{Theme, Typography, Components, StatusIcons};
|
||||||
|
|
||||||
/// CPU widget displaying load, temperature, and frequency
|
/// CPU widget displaying load, temperature, and frequency
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct CpuWidget {
|
pub struct CpuWidget {
|
||||||
/// CPU load averages (1, 5, 15 minutes)
|
/// CPU load averages (1, 5, 15 minutes)
|
||||||
load_1min: Option<f32>,
|
load_1min: Option<f32>,
|
||||||
|
|||||||
@ -10,6 +10,7 @@ use super::Widget;
|
|||||||
use crate::ui::theme::{Theme, Typography, Components, StatusIcons};
|
use crate::ui::theme::{Theme, Typography, Components, StatusIcons};
|
||||||
|
|
||||||
/// Memory widget displaying usage, totals, and swap information
|
/// Memory widget displaying usage, totals, and swap information
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct MemoryWidget {
|
pub struct MemoryWidget {
|
||||||
/// Memory usage percentage
|
/// Memory usage percentage
|
||||||
usage_percent: Option<f32>,
|
usage_percent: Option<f32>,
|
||||||
|
|||||||
@ -12,6 +12,7 @@ use crate::ui::theme::{Theme, Typography, Components, StatusIcons};
|
|||||||
use ratatui::style::Style;
|
use ratatui::style::Style;
|
||||||
|
|
||||||
/// Services widget displaying hierarchical systemd service statuses
|
/// Services widget displaying hierarchical systemd service statuses
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct ServicesWidget {
|
pub struct ServicesWidget {
|
||||||
/// Parent services (nginx, docker, etc.)
|
/// Parent services (nginx, docker, etc.)
|
||||||
parent_services: HashMap<String, ServiceInfo>,
|
parent_services: HashMap<String, ServiceInfo>,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user