Remove unused code and eliminate compiler warnings

- Remove unused fields from CommandStatus variants
- Clean up unused methods and unused collector fields
- Fix lifetime syntax warning in SystemWidget
- Delete unused cache module completely
- Remove redundant render methods from widgets

All agent and dashboard warnings eliminated while preserving
panel switching and scrolling functionality.
This commit is contained in:
2025-10-25 14:15:52 +02:00
parent 8dd943e8f1
commit 4b54a59e35
23 changed files with 38 additions and 1410 deletions

View File

@@ -15,7 +15,7 @@ pub mod widgets;
use crate::metrics::MetricStore;
use cm_dashboard_shared::{Metric, Status};
use theme::{Components, Layout as ThemeLayout, StatusIcons, Theme, Typography};
use theme::{Components, Layout as ThemeLayout, Theme, Typography};
use widgets::{BackupWidget, ServicesWidget, SystemWidget, Widget};
/// Commands that can be triggered from the UI
@@ -34,9 +34,7 @@ pub enum CommandStatus {
/// Command is executing
InProgress { command_type: CommandType, target: String, start_time: std::time::Instant },
/// Command completed successfully
Success { command_type: CommandType, target: String, duration: std::time::Duration, completed_at: std::time::Instant },
/// Command failed
Failed { command_type: CommandType, target: String, error: String, failed_at: std::time::Instant },
Success { command_type: CommandType, completed_at: std::time::Instant },
}
/// Types of commands for status tracking
@@ -58,28 +56,6 @@ pub enum PanelType {
}
impl PanelType {
/// Get all panel types in order
pub fn all() -> [PanelType; 3] {
[PanelType::System, PanelType::Services, PanelType::Backup]
}
/// Get the next panel in cycle (System → Services → Backup → System)
pub fn next(self) -> PanelType {
match self {
PanelType::System => PanelType::Services,
PanelType::Services => PanelType::Backup,
PanelType::Backup => PanelType::System,
}
}
/// Get the previous panel in cycle (System ← Services ← Backup ← System)
pub fn previous(self) -> PanelType {
match self {
PanelType::System => PanelType::Backup,
PanelType::Services => PanelType::System,
PanelType::Backup => PanelType::Services,
}
}
}
/// Widget states for a specific host
@@ -423,34 +399,7 @@ impl TuiApp {
info!("Switched to panel: {:?}", self.focused_panel);
}
/// Switch to previous panel (Shift+Tab in reverse) - only cycles through visible panels
pub fn previous_panel(&mut self) {
let visible_panels = self.get_visible_panels();
if visible_panels.len() <= 1 {
return; // Can't switch if only one or no panels visible
}
// Find current panel index in visible panels
if let Some(current_index) = visible_panels.iter().position(|&p| p == self.focused_panel) {
// Move to previous visible panel
let prev_index = if current_index == 0 {
visible_panels.len() - 1
} else {
current_index - 1
};
self.focused_panel = visible_panels[prev_index];
} else {
// Current panel not visible, switch to last visible panel
self.focused_panel = visible_panels[visible_panels.len() - 1];
}
info!("Switched to panel: {:?}", self.focused_panel);
}
/// Get the currently focused panel
pub fn get_focused_panel(&self) -> PanelType {
self.focused_panel
}
/// Get the currently selected service name from the services widget
fn get_selected_service(&self) -> Option<String> {
@@ -462,15 +411,6 @@ impl TuiApp {
None
}
/// Get command status for current host
pub fn get_command_status(&self) -> Option<&CommandStatus> {
if let Some(hostname) = &self.current_host {
if let Some(host_widgets) = self.host_widgets.get(hostname) {
return host_widgets.command_status.as_ref();
}
}
None
}
/// Should quit application
pub fn should_quit(&self) -> bool {
@@ -491,31 +431,15 @@ impl TuiApp {
/// Mark command as completed successfully
pub fn complete_command(&mut self, hostname: &str) {
if let Some(host_widgets) = self.host_widgets.get_mut(hostname) {
if let Some(CommandStatus::InProgress { command_type, target, start_time }) = &host_widgets.command_status {
let duration = start_time.elapsed();
if let Some(CommandStatus::InProgress { command_type, .. }) = &host_widgets.command_status {
host_widgets.command_status = Some(CommandStatus::Success {
command_type: command_type.clone(),
target: target.clone(),
duration,
completed_at: Instant::now(),
});
}
}
}
/// Mark command as failed
pub fn fail_command(&mut self, hostname: &str, error: String) {
if let Some(host_widgets) = self.host_widgets.get_mut(hostname) {
if let Some(CommandStatus::InProgress { command_type, target, .. }) = &host_widgets.command_status {
host_widgets.command_status = Some(CommandStatus::Failed {
command_type: command_type.clone(),
target: target.clone(),
error,
failed_at: Instant::now(),
});
}
}
}
/// Check for command timeouts and automatically clear them
pub fn check_command_timeouts(&mut self) {
@@ -539,11 +463,6 @@ impl TuiApp {
hosts_to_clear.push(hostname.clone());
}
}
else if let Some(CommandStatus::Failed { failed_at, .. }) = &host_widgets.command_status {
if now.duration_since(*failed_at) > Duration::from_secs(5) {
hosts_to_clear.push(hostname.clone());
}
}
}
// Clear timed out commands
@@ -620,14 +539,6 @@ impl TuiApp {
}
}
/// Get total count of services for bounds checking
fn get_total_services_count(&self, hostname: &str) -> usize {
if let Some(host_widgets) = self.host_widgets.get(hostname) {
host_widgets.services_widget.get_total_services_count()
} else {
0
}
}
/// Get list of currently visible panels
fn get_visible_panels(&self) -> Vec<PanelType> {
@@ -759,10 +670,6 @@ impl TuiApp {
// Show green checkmark for successful rebuild
("", Theme::success())
}
Some(CommandStatus::Failed { command_type: CommandType::SystemRebuild, .. }) => {
// Show red X for failed rebuild
("", Theme::error())
}
_ => {
// Normal status icon based on metrics
let host_status = self.calculate_host_status(host, metric_store);
@@ -928,297 +835,4 @@ impl TuiApp {
}
}
fn render_storage_section(&self, frame: &mut Frame, area: Rect, metric_store: &MetricStore) {
if area.height < 2 {
return;
}
if let Some(ref hostname) = self.current_host {
// Discover storage pools from metrics (look for disk_{pool}_usage_percent patterns)
let mut storage_pools: std::collections::HashMap<String, Vec<String>> =
std::collections::HashMap::new();
let all_metrics = metric_store.get_metrics_for_host(hostname);
// Find storage pools by looking for usage metrics
for metric in &all_metrics {
if metric.name.starts_with("disk_") && metric.name.ends_with("_usage_percent") {
let pool_name = metric.name
.strip_prefix("disk_")
.and_then(|s| s.strip_suffix("_usage_percent"))
.unwrap_or_default()
.to_string();
if !pool_name.is_empty() && pool_name != "tmp" {
storage_pools.entry(pool_name.clone()).or_insert_with(Vec::new);
}
}
}
// Find individual drives for each pool
for metric in &all_metrics {
if metric.name.starts_with("disk_") && metric.name.contains("_") && metric.name.ends_with("_health") {
// Parse disk_{pool}_{drive}_health format
let parts: Vec<&str> = metric.name.split('_').collect();
if parts.len() >= 4 && parts[0] == "disk" && parts[parts.len()-1] == "health" {
// Extract pool name (everything between "disk_" and "_{drive}_health")
let drive_name = parts[parts.len()-2].to_string();
let pool_part_end = parts.len() - 2;
let pool_name = parts[1..pool_part_end].join("_");
if let Some(drives) = storage_pools.get_mut(&pool_name) {
if !drives.contains(&drive_name) {
drives.push(drive_name);
}
}
}
}
}
// Check if we found any storage pools
if storage_pools.is_empty() {
// No storage pools found - show error/waiting message
let content_chunks = ratatui::layout::Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Length(1), Constraint::Min(0)])
.split(area);
let storage_title = Paragraph::new("Storage:").style(Typography::widget_title());
frame.render_widget(storage_title, content_chunks[0]);
let no_storage_spans =
StatusIcons::create_status_spans(Status::Unknown, "No storage pools detected");
let no_storage_para = Paragraph::new(ratatui::text::Line::from(no_storage_spans));
frame.render_widget(no_storage_para, content_chunks[1]);
return;
}
let available_lines = area.height as usize;
let mut constraints = Vec::new();
let mut pools_to_show = Vec::new();
let mut current_line = 0;
// Sort storage pools by name for consistent ordering
let mut sorted_pools: Vec<_> = storage_pools.iter().collect();
sorted_pools.sort_by_key(|(pool_name, _)| pool_name.as_str());
// Add section title if we have pools
let mut title_added = false;
for (pool_name, drives) in sorted_pools {
// Calculate lines needed: pool header + drives + usage line (+ section title if first)
let section_title_lines = if !title_added { 1 } else { 0 };
let lines_for_this_pool = section_title_lines + 1 + drives.len() + 1;
if current_line + lines_for_this_pool <= available_lines {
pools_to_show.push((pool_name.clone(), drives.clone()));
// Add section title constraint if this is the first pool
if !title_added {
constraints.push(Constraint::Length(1)); // "Storage:" section title
title_added = true;
}
// Add constraints for this pool
constraints.push(Constraint::Length(1)); // Pool header with status
for _ in 0..drives.len() {
constraints.push(Constraint::Length(1)); // Drive line with tree symbol
}
constraints.push(Constraint::Length(1)); // Usage line with end tree symbol
current_line += lines_for_this_pool;
} else {
break; // Can't fit more pools
}
}
// Add remaining space if any
if constraints.len() < available_lines {
constraints.push(Constraint::Min(0));
}
let content_chunks = ratatui::layout::Layout::default()
.direction(Direction::Vertical)
.constraints(constraints)
.split(area);
let mut chunk_index = 0;
// Render "Storage:" section title if we have pools
if !pools_to_show.is_empty() {
let storage_title = Paragraph::new("Storage:").style(Typography::widget_title());
frame.render_widget(storage_title, content_chunks[chunk_index]);
chunk_index += 1;
}
// Display each storage pool with tree structure
for (pool_name, drives) in &pools_to_show {
// Pool header with status icon and type
let pool_display_name = if pool_name == "root" {
"root".to_string()
} else {
pool_name.clone()
};
let pool_type = if drives.len() > 1 { "multi-drive" } else { "Single" };
// Get pool status from usage metric
let pool_status = metric_store
.get_metric(hostname, &format!("disk_{}_usage_percent", pool_name))
.map(|m| m.status)
.unwrap_or(Status::Unknown);
// Create pool header with status icon
let pool_status_icon = StatusIcons::get_icon(pool_status);
let pool_status_color = Theme::status_color(pool_status);
let pool_header_text = format!("{} ({}):", pool_display_name, pool_type);
let pool_header_spans = vec![
ratatui::text::Span::styled(
format!("{} ", pool_status_icon),
Style::default().fg(pool_status_color),
),
ratatui::text::Span::styled(
pool_header_text,
Style::default().fg(Theme::primary_text()),
),
];
let pool_header_para = Paragraph::new(ratatui::text::Line::from(pool_header_spans));
frame.render_widget(pool_header_para, content_chunks[chunk_index]);
chunk_index += 1;
// Individual drive lines with tree symbols
let mut sorted_drives = drives.clone();
sorted_drives.sort();
for (_drive_idx, drive_name) in sorted_drives.iter().enumerate() {
// Get drive health status
let drive_health_metric = metric_store
.get_metric(hostname, &format!("disk_{}_{}_health", pool_name, drive_name));
let drive_status = drive_health_metric
.map(|m| m.status)
.unwrap_or(Status::Unknown);
// Get drive temperature
let temp_text = metric_store
.get_metric(hostname, &format!("disk_{}_{}_temperature", pool_name, drive_name))
.and_then(|m| m.value.as_f32())
.map(|temp| format!(" T:{:.0}°C", temp))
.unwrap_or_default();
// Get drive wear level (SSDs)
let wear_text = metric_store
.get_metric(hostname, &format!("disk_{}_{}_wear_percent", pool_name, drive_name))
.and_then(|m| m.value.as_f32())
.map(|wear| format!(" W:{:.0}%", wear))
.unwrap_or_default();
// Build drive line with tree symbol
let tree_symbol = "├─";
let drive_status_icon = StatusIcons::get_icon(drive_status);
let drive_status_color = Theme::status_color(drive_status);
let drive_text = format!("{}{}{}", drive_name, temp_text, wear_text);
let drive_spans = vec![
ratatui::text::Span::styled(" ", Style::default()), // 2-space indentation
ratatui::text::Span::styled(
format!("{} ", tree_symbol),
Style::default().fg(Theme::muted_text()),
),
ratatui::text::Span::styled(
format!("{} ", drive_status_icon),
Style::default().fg(drive_status_color),
),
ratatui::text::Span::styled(
drive_text,
Style::default().fg(Theme::primary_text()),
),
];
let drive_para = Paragraph::new(ratatui::text::Line::from(drive_spans));
frame.render_widget(drive_para, content_chunks[chunk_index]);
chunk_index += 1;
}
// Usage line with end tree symbol and status icon
let usage_percent = metric_store
.get_metric(hostname, &format!("disk_{}_usage_percent", pool_name))
.and_then(|m| m.value.as_f32())
.unwrap_or(0.0);
let used_gb = metric_store
.get_metric(hostname, &format!("disk_{}_used_gb", pool_name))
.and_then(|m| m.value.as_f32())
.unwrap_or(0.0);
let total_gb = metric_store
.get_metric(hostname, &format!("disk_{}_total_gb", pool_name))
.and_then(|m| m.value.as_f32())
.unwrap_or(0.0);
let usage_status = metric_store
.get_metric(hostname, &format!("disk_{}_usage_percent", pool_name))
.map(|m| m.status)
.unwrap_or(Status::Unknown);
// Format usage with proper units
let (used_display, total_display, unit) = if total_gb < 1.0 {
(used_gb * 1024.0, total_gb * 1024.0, "MB")
} else {
(used_gb, total_gb, "GB")
};
let end_tree_symbol = "└─";
let usage_status_icon = StatusIcons::get_icon(usage_status);
let usage_status_color = Theme::status_color(usage_status);
let usage_text = format!("{:.1}% {:.1}{}/{:.1}{}",
usage_percent, used_display, unit, total_display, unit);
let usage_spans = vec![
ratatui::text::Span::styled(" ", Style::default()), // 2-space indentation
ratatui::text::Span::styled(
format!("{} ", end_tree_symbol),
Style::default().fg(Theme::muted_text()),
),
ratatui::text::Span::styled(
format!("{} ", usage_status_icon),
Style::default().fg(usage_status_color),
),
ratatui::text::Span::styled(
usage_text,
Style::default().fg(Theme::primary_text()),
),
];
let usage_para = Paragraph::new(ratatui::text::Line::from(usage_spans));
frame.render_widget(usage_para, content_chunks[chunk_index]);
chunk_index += 1;
}
// Show truncation indicator if we couldn't display all pools
if pools_to_show.len() < storage_pools.len() {
if let Some(last_chunk) = content_chunks.last() {
let truncated_count = storage_pools.len() - pools_to_show.len();
let truncated_text = format!(
"... and {} more pool{}",
truncated_count,
if truncated_count == 1 { "" } else { "s" }
);
let truncated_para = Paragraph::new(truncated_text).style(Typography::muted());
frame.render_widget(truncated_para, *last_chunk);
}
}
} else {
// No host connected
let content_chunks = ratatui::layout::Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Length(1), Constraint::Min(0)])
.split(area);
let storage_title = Paragraph::new("Storage:").style(Typography::widget_title());
frame.render_widget(storage_title, content_chunks[0]);
let no_host_spans =
StatusIcons::create_status_spans(Status::Unknown, "No host connected");
let no_host_para = Paragraph::new(ratatui::text::Line::from(no_host_spans));
frame.render_widget(no_host_para, content_chunks[1]);
}
}
}