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

@@ -81,38 +81,7 @@ impl BackupWidget {
/// Format timestamp for display
fn format_last_run(&self) -> String {
match self.last_run_timestamp {
Some(timestamp) => {
let duration = chrono::Utc::now().timestamp() - timestamp;
if duration < 3600 {
format!("{}m ago", duration / 60)
} else if duration < 86400 {
format!("{}h ago", duration / 3600)
} else {
format!("{}d ago", duration / 86400)
}
}
None => "".to_string(),
}
}
/// Format disk usage in format "usedGB/totalGB"
fn format_repo_size(&self) -> String {
match (self.backup_disk_used_gb, self.backup_disk_total_gb) {
(Some(used_gb), Some(total_gb)) => {
let used_str = Self::format_size_with_proper_units(used_gb);
let total_str = Self::format_size_with_proper_units(total_gb);
format!("{}/{}", used_str, total_str)
}
(Some(used_gb), None) => {
// Fallback to just used size if total not available
Self::format_size_with_proper_units(used_gb)
}
_ => "".to_string(),
}
}
/// Format size with proper units (xxxkB/MB/GB/TB)
fn format_size_with_proper_units(size_gb: f32) -> String {
@@ -137,23 +106,7 @@ impl BackupWidget {
}
}
/// Format product name display
fn format_product_name(&self) -> String {
if let Some(ref product_name) = self.backup_disk_product_name {
format!("P/N: {}", product_name)
} else {
"P/N: Unknown".to_string()
}
}
/// Format serial number display
fn format_serial_number(&self) -> String {
if let Some(ref serial) = self.backup_disk_serial_number {
format!("S/N: {}", serial)
} else {
"S/N: Unknown".to_string()
}
}
/// Extract service name from metric name (e.g., "backup_service_gitea_status" -> "gitea")
fn extract_service_name(metric_name: &str) -> Option<String> {
@@ -324,9 +277,6 @@ impl Widget for BackupWidget {
}
}
fn render(&mut self, frame: &mut Frame, area: Rect) {
self.render_with_scroll(frame, area, 0);
}
}
impl BackupWidget {

View File

@@ -1,139 +1 @@
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::{StatusIcons, Typography};
/// CPU widget displaying load, temperature, and frequency
#[derive(Clone)]
pub struct CpuWidget {
/// CPU load averages (1, 5, 15 minutes)
load_1min: Option<f32>,
load_5min: Option<f32>,
load_15min: Option<f32>,
/// CPU temperature in Celsius
temperature: Option<f32>,
/// CPU frequency in MHz
frequency: Option<f32>,
/// 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()
}
}
// This file is intentionally left minimal - CPU functionality is handled by the SystemWidget

View File

@@ -1,253 +1 @@
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::{StatusIcons, Typography};
/// Memory widget displaying usage, totals, and swap information
#[derive(Clone)]
pub struct MemoryWidget {
/// Memory usage percentage
usage_percent: Option<f32>,
/// Total memory in GB
total_gb: Option<f32>,
/// Used memory in GB
used_gb: Option<f32>,
/// Available memory in GB
available_gb: Option<f32>,
/// Total swap in GB
swap_total_gb: Option<f32>,
/// Used swap in GB
swap_used_gb: Option<f32>,
/// /tmp directory size in MB
tmp_size_mb: Option<f32>,
/// /tmp total size in MB
tmp_total_mb: Option<f32>,
/// /tmp usage percentage
tmp_usage_percent: Option<f32>,
/// Aggregated status
status: Status,
/// Last update indicator
has_data: bool,
}
impl MemoryWidget {
pub fn new() -> Self {
Self {
usage_percent: None,
total_gb: None,
used_gb: None,
available_gb: None,
swap_total_gb: None,
swap_used_gb: None,
tmp_size_mb: None,
tmp_total_mb: None,
tmp_usage_percent: None,
status: Status::Unknown,
has_data: false,
}
}
/// Get memory usage percentage for gauge
fn get_memory_percentage(&self) -> u16 {
match self.usage_percent {
Some(percent) => percent.min(100.0).max(0.0) as u16,
None => {
// Calculate from used/total if percentage not available
match (self.used_gb, self.total_gb) {
(Some(used), Some(total)) if total > 0.0 => {
let percent = (used / total * 100.0).min(100.0).max(0.0);
percent as u16
}
_ => 0,
}
}
}
}
/// Format size with proper units (kB/MB/GB)
fn format_size_units(size_mb: f32) -> String {
if size_mb >= 1024.0 {
// Convert to GB
let size_gb = size_mb / 1024.0;
format!("{:.1}GB", size_gb)
} else if size_mb >= 1.0 {
// Show as MB
format!("{:.0}MB", size_mb)
} else if size_mb >= 0.001 {
// Convert to kB
let size_kb = size_mb * 1024.0;
format!("{:.0}kB", size_kb)
} else {
// Show very small sizes in bytes
let size_bytes = size_mb * 1024.0 * 1024.0;
format!("{:.0}B", size_bytes)
}
}
/// Format /tmp usage as "xx% yyykB/MB/GB/zzzGB"
fn format_tmp_usage(&self) -> String {
match (self.tmp_usage_percent, self.tmp_size_mb, self.tmp_total_mb) {
(Some(percent), Some(used_mb), Some(total_mb)) => {
let used_str = Self::format_size_units(used_mb);
let total_str = Self::format_size_units(total_mb);
format!("{:.1}% {}/{}", percent, used_str, total_str)
}
(Some(percent), Some(used_mb), None) => {
let used_str = Self::format_size_units(used_mb);
format!("{:.1}% {}", percent, used_str)
}
(None, Some(used_mb), Some(total_mb)) => {
let used_str = Self::format_size_units(used_mb);
let total_str = Self::format_size_units(total_mb);
format!("{}/{}", used_str, total_str)
}
(None, Some(used_mb), None) => Self::format_size_units(used_mb),
_ => "".to_string(),
}
}
/// Get tmp status based on usage percentage
fn get_tmp_status(&self) -> Status {
if let Some(tmp_percent) = self.tmp_usage_percent {
if tmp_percent >= 90.0 {
Status::Critical
} else if tmp_percent >= 70.0 {
Status::Warning
} else {
Status::Ok
}
} else {
Status::Unknown
}
}
}
impl Widget for MemoryWidget {
fn update_from_metrics(&mut self, metrics: &[&Metric]) {
debug!("Memory widget updating with {} metrics", metrics.len());
// Reset status aggregation
let mut statuses = Vec::new();
for metric in metrics {
match metric.name.as_str() {
"memory_usage_percent" => {
if let Some(value) = metric.value.as_f32() {
self.usage_percent = Some(value);
statuses.push(metric.status);
}
}
"memory_total_gb" => {
if let Some(value) = metric.value.as_f32() {
self.total_gb = Some(value);
statuses.push(metric.status);
}
}
"memory_used_gb" => {
if let Some(value) = metric.value.as_f32() {
self.used_gb = Some(value);
statuses.push(metric.status);
}
}
"memory_available_gb" => {
if let Some(value) = metric.value.as_f32() {
self.available_gb = Some(value);
statuses.push(metric.status);
}
}
"memory_swap_total_gb" => {
if let Some(value) = metric.value.as_f32() {
self.swap_total_gb = Some(value);
statuses.push(metric.status);
}
}
"memory_swap_used_gb" => {
if let Some(value) = metric.value.as_f32() {
self.swap_used_gb = Some(value);
statuses.push(metric.status);
}
}
"disk_tmp_size_mb" => {
if let Some(value) = metric.value.as_f32() {
self.tmp_size_mb = Some(value);
statuses.push(metric.status);
}
}
"disk_tmp_total_mb" => {
if let Some(value) = metric.value.as_f32() {
self.tmp_total_mb = Some(value);
statuses.push(metric.status);
}
}
"disk_tmp_usage_percent" => {
if let Some(value) = metric.value.as_f32() {
self.tmp_usage_percent = 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!("Memory widget updated: usage={:?}%, total={:?}GB, swap_total={:?}GB, tmp={:?}/{:?}MB, status={:?}",
self.usage_percent, self.total_gb, self.swap_total_gb, self.tmp_size_mb, self.tmp_total_mb, 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),
Constraint::Length(1),
])
.split(area);
let mem_title = Paragraph::new("RAM:").style(Typography::widget_title());
frame.render_widget(mem_title, content_chunks[0]);
// Format used and total memory with smart units, percentage, and status icon
let used_str = self
.used_gb
.map_or("".to_string(), |v| Self::format_size_units(v * 1024.0)); // Convert GB to MB for formatting
let total_str = self
.total_gb
.map_or("".to_string(), |v| Self::format_size_units(v * 1024.0)); // Convert GB to MB for formatting
let percentage = self.get_memory_percentage();
let mem_details_spans = StatusIcons::create_status_spans(
self.status,
&format!("Used: {}% {}/{}", percentage, used_str, total_str),
);
let mem_details_para = Paragraph::new(ratatui::text::Line::from(mem_details_spans));
frame.render_widget(mem_details_para, content_chunks[1]);
// /tmp usage line with status icon
let tmp_status = self.get_tmp_status();
let tmp_spans = StatusIcons::create_status_spans(
tmp_status,
&format!("tmp: {}", self.format_tmp_usage()),
);
let tmp_para = Paragraph::new(ratatui::text::Line::from(tmp_spans));
frame.render_widget(tmp_para, content_chunks[2]);
}
}
impl Default for MemoryWidget {
fn default() -> Self {
Self::new()
}
}
// This file is intentionally left minimal - Memory functionality is handled by the SystemWidget

View File

@@ -1,5 +1,4 @@
use cm_dashboard_shared::Metric;
use ratatui::{layout::Rect, Frame};
pub mod backup;
pub mod cpu;
@@ -16,6 +15,4 @@ pub trait Widget {
/// Update widget with new metrics data
fn update_from_metrics(&mut self, metrics: &[&Metric]);
/// Render the widget to a terminal frame
fn render(&mut self, frame: &mut Frame, area: Rect);
}

View File

@@ -163,15 +163,6 @@ impl ServicesWidget {
(icon.to_string(), info.status.clone(), status_color)
}
/// Create spans for sub-service with icon next to name
fn create_sub_service_spans(
&self,
name: &str,
info: &ServiceInfo,
is_last: bool,
) -> Vec<ratatui::text::Span<'static>> {
self.create_sub_service_spans_with_status(name, info, is_last, None)
}
/// Create spans for sub-service with icon next to name, considering command status
fn create_sub_service_spans_with_status(
@@ -432,16 +423,9 @@ impl Widget for ServicesWidget {
);
}
fn render(&mut self, frame: &mut Frame, area: Rect) {
self.render_with_focus(frame, area, false);
}
}
impl ServicesWidget {
/// Render with optional focus indicator and scroll support
pub fn render_with_focus(&mut self, frame: &mut Frame, area: Rect, is_focused: bool) {
self.render_with_focus_and_scroll(frame, area, is_focused, 0);
}
/// Render with focus, scroll, and command status for visual feedback
pub fn render_with_command_status(&mut self, frame: &mut Frame, area: Rect, is_focused: bool, scroll_offset: usize, command_status: Option<&CommandStatus>) {
@@ -635,167 +619,6 @@ impl ServicesWidget {
}
}
}
/// Render with focus indicator and scroll offset
pub fn render_with_focus_and_scroll(&mut self, frame: &mut Frame, area: Rect, is_focused: bool, scroll_offset: usize) {
let services_block = if is_focused {
Components::focused_widget_block("services")
} else {
Components::widget_block("services")
};
let inner_area = services_block.inner(area);
frame.render_widget(services_block, area);
let content_chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Length(1), Constraint::Min(0)])
.split(inner_area);
// Header
let header = format!(
"{:<25} {:<10} {:<8} {:<8}",
"Service:", "Status:", "RAM:", "Disk:"
);
let header_para = Paragraph::new(header).style(Typography::muted());
frame.render_widget(header_para, content_chunks[0]);
// Check if we have any services to display
if self.parent_services.is_empty() && self.sub_services.is_empty() {
let empty_text = Paragraph::new("No process data").style(Typography::muted());
frame.render_widget(empty_text, content_chunks[1]);
return;
}
// Build hierarchical service list for display
let mut display_lines: Vec<(String, Status, bool, Option<(ServiceInfo, bool)>)> = Vec::new();
// Sort parent services alphabetically for consistent order
let mut parent_services: Vec<_> = self.parent_services.iter().collect();
parent_services.sort_by(|(a, _), (b, _)| a.cmp(b));
for (parent_name, parent_info) in parent_services {
// Add parent service line
let parent_line = self.format_parent_service_line(parent_name, parent_info);
display_lines.push((parent_line, parent_info.widget_status, false, None)); // false = not sub-service
// Add sub-services for this parent (if any)
if let Some(sub_list) = self.sub_services.get(parent_name) {
// Sort sub-services by name for consistent display
let mut sorted_subs = sub_list.clone();
sorted_subs.sort_by(|(a, _), (b, _)| a.cmp(b));
for (i, (sub_name, sub_info)) in sorted_subs.iter().enumerate() {
let is_last_sub = i == sorted_subs.len() - 1;
// Store sub-service info for custom span rendering
display_lines.push((
sub_name.clone(),
sub_info.widget_status,
true,
Some((sub_info.clone(), is_last_sub)),
)); // true = sub-service, with is_last info
}
}
}
// Apply scroll offset and render visible lines
let available_lines = content_chunks[1].height as usize;
let total_lines = display_lines.len();
// Calculate scroll boundaries
let max_scroll = if total_lines > available_lines {
total_lines - available_lines
} else {
total_lines.saturating_sub(1)
};
let effective_scroll = scroll_offset.min(max_scroll);
// Get visible lines after scrolling
let visible_lines: Vec<_> = display_lines
.iter()
.skip(effective_scroll)
.take(available_lines)
.collect();
let lines_to_show = visible_lines.len();
if lines_to_show > 0 {
let service_chunks = Layout::default()
.direction(Direction::Vertical)
.constraints(vec![Constraint::Length(1); lines_to_show])
.split(content_chunks[1]);
for (i, (line_text, line_status, is_sub, sub_info)) in visible_lines.iter().enumerate()
{
let actual_index = effective_scroll + i; // Real index in the full list
// Only parent services can be selected - calculate parent service index
let is_selected = if !*is_sub {
// This is a parent service - count how many parent services came before this one
let parent_index = self.calculate_parent_service_index(&actual_index);
parent_index == self.selected_index
} else {
false // Sub-services are never selected
};
let mut spans = if *is_sub && sub_info.is_some() {
// Use custom sub-service span creation
let (service_info, is_last) = sub_info.as_ref().unwrap();
self.create_sub_service_spans(line_text, service_info, *is_last)
} else {
// Use regular status spans for parent services
StatusIcons::create_status_spans(*line_status, line_text)
};
// Apply selection highlighting to parent services only, preserving status icon color
// Only show selection when Services panel is focused
if is_selected && !*is_sub && is_focused {
for (i, span) in spans.iter_mut().enumerate() {
if i == 0 {
// First span is the status icon - preserve its color
span.style = span.style.bg(Theme::highlight());
} else {
// Other spans (text) get full selection highlighting
span.style = span.style
.bg(Theme::highlight())
.fg(Theme::background());
}
}
}
let service_para = Paragraph::new(ratatui::text::Line::from(spans));
frame.render_widget(service_para, service_chunks[i]);
}
}
// Show scroll indicator if there are more services than we can display
if total_lines > available_lines {
let hidden_above = effective_scroll;
let hidden_below = total_lines.saturating_sub(effective_scroll + available_lines);
if hidden_above > 0 || hidden_below > 0 {
let scroll_text = if hidden_above > 0 && hidden_below > 0 {
format!("... {} above, {} below", hidden_above, hidden_below)
} else if hidden_above > 0 {
format!("... {} more above", hidden_above)
} else {
format!("... {} more below", hidden_below)
};
if available_lines > 0 && lines_to_show > 0 {
let last_line_area = Rect {
x: content_chunks[1].x,
y: content_chunks[1].y + (lines_to_show - 1) as u16,
width: content_chunks[1].width,
height: 1,
};
let scroll_para = Paragraph::new(scroll_text).style(Typography::muted());
frame.render_widget(scroll_para, last_line_area);
}
}
}
}
}
impl Default for ServicesWidget {

View File

@@ -249,7 +249,7 @@ impl SystemWidget {
}
/// Render storage section with tree structure
fn render_storage(&self) -> Vec<Line> {
fn render_storage(&self) -> Vec<Line<'_>> {
let mut lines = Vec::new();
for pool in &self.storage_pools {
@@ -410,9 +410,6 @@ impl Widget for SystemWidget {
self.update_storage_from_metrics(metrics);
}
fn render(&mut self, frame: &mut Frame, area: Rect) {
self.render_with_scroll(frame, area, 0);
}
}
impl SystemWidget {