Remove unused code and eliminate build warnings

Removed unused widget subscription system, cache utilities, error variants,
theme functions, and struct fields. Replaced subscription-based widgets
with direct metric filtering. Build now completes with zero warnings.
This commit is contained in:
2025-10-18 23:50:15 +02:00
parent 7f85a6436e
commit 0141a6e111
14 changed files with 15 additions and 764 deletions

View File

@@ -18,7 +18,6 @@ use crate::metrics::MetricStore;
use crate::ui::TuiApp;
pub struct Dashboard {
config: DashboardConfig,
zmq_consumer: ZmqConsumer,
zmq_command_sender: ZmqCommandSender,
metric_store: MetricStore,
@@ -111,7 +110,6 @@ impl Dashboard {
info!("Dashboard initialization complete");
Ok(Self {
config,
zmq_consumer,
zmq_command_sender,
metric_store,

View File

@@ -1,136 +1,14 @@
use cm_dashboard_shared::{Metric, Status};
use std::time::Instant;
pub mod store;
pub mod subscription;
pub use store::MetricStore;
/// Widget types that can subscribe to metrics
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
pub enum WidgetType {
Cpu,
Memory,
Storage,
Services,
Backup,
Hosts,
Alerts,
}
/// Historical metric data point
#[derive(Debug, Clone)]
pub struct MetricDataPoint {
pub metric: Metric,
pub received_at: Instant,
}
/// Metric filtering and selection utilities
pub mod filter {
use super::*;
/// Filter metrics by widget type subscription
pub fn filter_metrics_for_widget<'a>(
metrics: &'a [Metric],
subscriptions: &[String],
) -> Vec<&'a Metric> {
metrics
.iter()
.filter(|metric| subscriptions.contains(&metric.name))
.collect()
}
/// Get metrics by pattern matching
pub fn filter_metrics_by_pattern<'a>(
metrics: &'a [Metric],
pattern: &str,
) -> Vec<&'a Metric> {
if pattern.is_empty() {
return metrics.iter().collect();
}
metrics
.iter()
.filter(|metric| metric.name.contains(pattern))
.collect()
}
/// Aggregate status from multiple metrics
pub fn aggregate_widget_status(metrics: &[&Metric]) -> Status {
if metrics.is_empty() {
return Status::Unknown;
}
let statuses: Vec<Status> = metrics.iter().map(|m| m.status).collect();
Status::aggregate(&statuses)
}
}
/// Widget metric subscription definitions
pub mod subscriptions {
/// CPU widget metric subscriptions
pub const CPU_WIDGET_METRICS: &[&str] = &[
"cpu_load_1min",
"cpu_load_5min",
"cpu_load_15min",
"cpu_temperature_celsius",
"cpu_frequency_mhz",
];
/// Memory widget metric subscriptions
pub const MEMORY_WIDGET_METRICS: &[&str] = &[
"memory_usage_percent",
"memory_total_gb",
"memory_used_gb",
"memory_available_gb",
"memory_swap_total_gb",
"memory_swap_used_gb",
"disk_tmp_size_mb",
"disk_tmp_total_mb",
"disk_tmp_usage_percent",
];
/// Storage widget metric subscriptions
pub const STORAGE_WIDGET_METRICS: &[&str] = &[
"disk_nvme0_temperature_celsius",
"disk_nvme0_wear_percent",
"disk_nvme0_spare_percent",
"disk_nvme0_hours",
"disk_nvme0_capacity_gb",
"disk_nvme0_usage_gb",
"disk_nvme0_usage_percent",
];
/// Services widget metric subscriptions
/// Note: Individual service metrics are dynamically discovered
/// Pattern: "service_{name}_status" and "service_{name}_memory_mb"
pub const SERVICES_WIDGET_METRICS: &[&str] = &[
// Individual service metrics will be matched by pattern in the widget
// e.g., "service_sshd_status", "service_nginx_status", etc.
];
/// Backup widget metric subscriptions
pub const BACKUP_WIDGET_METRICS: &[&str] = &[
"backup_overall_status",
"backup_duration_seconds",
"backup_last_run_timestamp",
"backup_total_services",
"backup_total_repo_size_gb",
"backup_services_completed_count",
"backup_services_failed_count",
"backup_services_disabled_count",
];
/// Get all metric subscriptions for a widget type
pub fn get_widget_subscriptions(widget_type: super::WidgetType) -> &'static [&'static str] {
match widget_type {
super::WidgetType::Cpu => CPU_WIDGET_METRICS,
super::WidgetType::Memory => MEMORY_WIDGET_METRICS,
super::WidgetType::Storage => STORAGE_WIDGET_METRICS,
super::WidgetType::Services => SERVICES_WIDGET_METRICS,
super::WidgetType::Backup => BACKUP_WIDGET_METRICS,
super::WidgetType::Hosts => &[], // Hosts widget doesn't subscribe to specific metrics
super::WidgetType::Alerts => &[], // Alerts widget aggregates from all metrics
}
}
}

View File

@@ -1,9 +1,9 @@
use cm_dashboard_shared::{Metric, Status};
use cm_dashboard_shared::Metric;
use std::collections::HashMap;
use std::time::{Duration, Instant};
use tracing::{debug, info, warn};
use super::{MetricDataPoint, WidgetType, subscriptions};
use super::MetricDataPoint;
/// Central metric storage for the dashboard
pub struct MetricStore {
@@ -54,7 +54,6 @@ impl MetricStore {
// Add to history
host_history.push(MetricDataPoint {
metric,
received_at: now,
});
}
@@ -80,6 +79,7 @@ impl MetricStore {
}
/// Get all current metrics for a host
#[allow(dead_code)]
pub fn get_host_metrics(&self, hostname: &str) -> Option<&HashMap<String, Metric>> {
self.current_metrics.get(hostname)
}
@@ -93,36 +93,7 @@ impl MetricStore {
}
}
/// Get metrics for a specific widget type
pub fn get_metrics_for_widget(&self, hostname: &str, widget_type: WidgetType) -> Vec<&Metric> {
let subscriptions = subscriptions::get_widget_subscriptions(widget_type);
if let Some(host_metrics) = self.get_host_metrics(hostname) {
subscriptions
.iter()
.filter_map(|&metric_name| host_metrics.get(metric_name))
.collect()
} else {
Vec::new()
}
}
/// Get aggregated status for a widget
pub fn get_widget_status(&self, hostname: &str, widget_type: WidgetType) -> Status {
let metrics = self.get_metrics_for_widget(hostname, widget_type);
if metrics.is_empty() {
Status::Unknown
} else {
let statuses: Vec<Status> = metrics.iter().map(|m| m.status).collect();
Status::aggregate(&statuses)
}
}
/// Get list of all hosts with metrics
pub fn get_hosts(&self) -> Vec<String> {
self.current_metrics.keys().cloned().collect()
}
/// Get connected hosts (hosts with recent updates)
pub fn get_connected_hosts(&self, timeout: Duration) -> Vec<String> {
@@ -140,45 +111,7 @@ impl MetricStore {
.collect()
}
/// Get last update timestamp for a host
pub fn get_last_update(&self, hostname: &str) -> Option<Instant> {
self.last_update.get(hostname).copied()
}
/// Check if host is considered connected
pub fn is_host_connected(&self, hostname: &str, timeout: Duration) -> bool {
if let Some(&last_update) = self.last_update.get(hostname) {
Instant::now().duration_since(last_update) <= timeout
} else {
false
}
}
/// Get metric value as specific type (helper function)
pub fn get_metric_value_f32(&self, hostname: &str, metric_name: &str) -> Option<f32> {
self.get_metric(hostname, metric_name)?
.value
.as_f32()
}
/// Get metric value as string (helper function)
pub fn get_metric_value_string(&self, hostname: &str, metric_name: &str) -> Option<String> {
Some(self.get_metric(hostname, metric_name)?
.value
.as_string())
}
/// Get historical data for a metric
pub fn get_metric_history(&self, hostname: &str, metric_name: &str) -> Vec<&MetricDataPoint> {
if let Some(history) = self.historical_metrics.get(hostname) {
history
.iter()
.filter(|dp| dp.metric.name == metric_name)
.collect()
} else {
Vec::new()
}
}
/// Cleanup old data and enforce limits
fn cleanup_host_data(&mut self, hostname: &str) {
@@ -199,32 +132,4 @@ impl MetricStore {
}
}
/// Get storage statistics
pub fn get_stats(&self) -> MetricStoreStats {
let total_current_metrics: usize = self.current_metrics
.values()
.map(|host_metrics| host_metrics.len())
.sum();
let total_historical_metrics: usize = self.historical_metrics
.values()
.map(|history| history.len())
.sum();
MetricStoreStats {
total_hosts: self.current_metrics.len(),
total_current_metrics,
total_historical_metrics,
connected_hosts: self.get_connected_hosts(Duration::from_secs(30)).len(),
}
}
}
/// Metric store statistics
#[derive(Debug, Clone)]
pub struct MetricStoreStats {
pub total_hosts: usize,
pub total_current_metrics: usize,
pub total_historical_metrics: usize,
pub connected_hosts: usize,
}

View File

@@ -1,160 +0,0 @@
use std::collections::{HashMap, HashSet};
use tracing::{debug, info};
use super::{WidgetType, subscriptions};
/// Manages metric subscriptions for widgets
pub struct SubscriptionManager {
/// Widget subscriptions: widget_type -> metric_names
widget_subscriptions: HashMap<WidgetType, Vec<String>>,
/// All subscribed metric names (for efficient filtering)
all_subscribed_metrics: HashSet<String>,
/// Active hosts
active_hosts: HashSet<String>,
}
impl SubscriptionManager {
pub fn new() -> Self {
let mut manager = Self {
widget_subscriptions: HashMap::new(),
all_subscribed_metrics: HashSet::new(),
active_hosts: HashSet::new(),
};
// Initialize default subscriptions
manager.initialize_default_subscriptions();
manager
}
/// Initialize default widget subscriptions
fn initialize_default_subscriptions(&mut self) {
// Subscribe CPU widget to CPU metrics
self.subscribe_widget(
WidgetType::Cpu,
subscriptions::CPU_WIDGET_METRICS.iter().map(|&s| s.to_string()).collect()
);
// Subscribe Memory widget to memory metrics
self.subscribe_widget(
WidgetType::Memory,
subscriptions::MEMORY_WIDGET_METRICS.iter().map(|&s| s.to_string()).collect()
);
info!("Initialized default widget subscriptions for {} widgets",
self.widget_subscriptions.len());
}
/// Subscribe a widget to specific metrics
pub fn subscribe_widget(&mut self, widget_type: WidgetType, metric_names: Vec<String>) {
debug!("Subscribing {:?} widget to {} metrics", widget_type, metric_names.len());
// Update widget subscriptions
self.widget_subscriptions.insert(widget_type, metric_names.clone());
// Update global subscription set
for metric_name in metric_names {
self.all_subscribed_metrics.insert(metric_name);
}
debug!("Total subscribed metrics: {}", self.all_subscribed_metrics.len());
}
/// Get metrics subscribed by a specific widget
pub fn get_widget_subscriptions(&self, widget_type: WidgetType) -> Vec<String> {
self.widget_subscriptions
.get(&widget_type)
.cloned()
.unwrap_or_default()
}
/// Get all subscribed metric names
pub fn get_all_subscribed_metrics(&self) -> Vec<String> {
self.all_subscribed_metrics.iter().cloned().collect()
}
/// Check if a metric is subscribed by any widget
pub fn is_metric_subscribed(&self, metric_name: &str) -> bool {
self.all_subscribed_metrics.contains(metric_name)
}
/// Add a host to active hosts list
pub fn add_host(&mut self, hostname: String) {
if self.active_hosts.insert(hostname.clone()) {
info!("Added host to subscription manager: {}", hostname);
}
}
/// Remove a host from active hosts list
pub fn remove_host(&mut self, hostname: &str) {
if self.active_hosts.remove(hostname) {
info!("Removed host from subscription manager: {}", hostname);
}
}
/// Get list of active hosts
pub fn get_active_hosts(&self) -> Vec<String> {
self.active_hosts.iter().cloned().collect()
}
/// Get subscription statistics
pub fn get_stats(&self) -> SubscriptionStats {
SubscriptionStats {
total_widgets_subscribed: self.widget_subscriptions.len(),
total_metric_subscriptions: self.all_subscribed_metrics.len(),
active_hosts: self.active_hosts.len(),
}
}
/// Update widget subscription dynamically
pub fn update_widget_subscription(&mut self, widget_type: WidgetType, metric_names: Vec<String>) {
// Remove old subscriptions from global set
if let Some(old_subscriptions) = self.widget_subscriptions.get(&widget_type) {
for old_metric in old_subscriptions {
// Only remove if no other widget subscribes to it
let still_subscribed = self.widget_subscriptions
.iter()
.filter(|(&wt, _)| wt != widget_type)
.any(|(_, metrics)| metrics.contains(old_metric));
if !still_subscribed {
self.all_subscribed_metrics.remove(old_metric);
}
}
}
// Add new subscriptions
self.subscribe_widget(widget_type, metric_names);
debug!("Updated subscription for {:?} widget", widget_type);
}
/// Get widgets that subscribe to a specific metric
pub fn get_widgets_for_metric(&self, metric_name: &str) -> Vec<WidgetType> {
self.widget_subscriptions
.iter()
.filter_map(|(&widget_type, metrics)| {
if metrics.contains(&metric_name.to_string()) {
Some(widget_type)
} else {
None
}
})
.collect()
}
}
impl Default for SubscriptionManager {
fn default() -> Self {
Self::new()
}
}
/// Subscription manager statistics
#[derive(Debug, Clone)]
pub struct SubscriptionStats {
pub total_widgets_subscribed: usize,
pub total_metric_subscriptions: usize,
pub active_hosts: usize,
}

View File

@@ -14,7 +14,7 @@ pub mod widgets;
pub mod theme;
use widgets::{CpuWidget, MemoryWidget, ServicesWidget, BackupWidget, Widget};
use crate::metrics::{MetricStore, WidgetType};
use crate::metrics::MetricStore;
use cm_dashboard_shared::{Metric, Status};
use theme::{Theme, Layout as ThemeLayout, Typography, Components, StatusIcons};
@@ -82,8 +82,14 @@ impl TuiApp {
let all_metrics = metric_store.get_metrics_for_host(&hostname);
if !all_metrics.is_empty() {
// 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 cpu_metrics: Vec<&Metric> = all_metrics.iter()
.filter(|m| m.name.starts_with("cpu_") || m.name.contains("c_state_") || m.name.starts_with("process_top_"))
.copied()
.collect();
let memory_metrics: Vec<&Metric> = all_metrics.iter()
.filter(|m| m.name.starts_with("memory_") || m.name.starts_with("disk_tmp_"))
.copied()
.collect();
let service_metrics: Vec<&Metric> = all_metrics.iter()
.filter(|m| m.name.starts_with("service_"))
.copied()

View File

@@ -3,6 +3,7 @@ use ratatui::widgets::{Block, Borders};
use cm_dashboard_shared::Status;
/// Complete terminal color palette matching your configuration
#[allow(dead_code)]
pub struct TerminalColors {
// Primary colors
pub foreground: Color,
@@ -86,6 +87,7 @@ impl Default for TerminalColors {
/// Comprehensive theming engine for dashboard consistency
pub struct Theme;
#[allow(dead_code)]
impl Theme {
fn colors() -> &'static TerminalColors {
static COLORS: std::sync::OnceLock<TerminalColors> = std::sync::OnceLock::new();
@@ -243,22 +245,6 @@ impl StatusIcons {
}
}
/// Get style for status-colored text (icon + text in status color)
pub fn get_status_style(status: Status) -> Style {
let color = match status {
Status::Ok => Theme::success(), // Green
Status::Warning => Theme::warning(), // Yellow
Status::Critical => Theme::error(), // Red
Status::Unknown => Theme::muted_text(), // Gray
};
Style::default().fg(color).bg(Theme::background())
}
/// Format text with status icon (entire line uses status color)
pub fn format_with_status(status: Status, text: &str) -> String {
let icon = Self::get_icon(status);
format!("{} {}", icon, text)
}
/// Create spans with status icon colored and text in foreground color
pub fn create_status_spans(status: Status, text: &str) -> Vec<ratatui::text::Span<'static>> {
@@ -282,17 +268,11 @@ impl StatusIcons {
]
}
/// Get style for secondary text with status icon (icon in status color, text in secondary)
pub fn get_secondary_with_status_style(_status: Status) -> Style {
// For now, use secondary color but we could enhance this later
// The icon color will come from the status color in the formatted text
Style::default().fg(Theme::secondary_text()).bg(Theme::background())
}
}
impl Components {
/// Standard widget block with title using bright foreground for title
pub fn widget_block(title: &str) -> Block {
pub fn widget_block(title: &str) -> Block<'_> {
Block::default()
.title(title)
.borders(Borders::ALL)
@@ -300,40 +280,6 @@ impl Components {
.title_style(Style::default().fg(Theme::border_title()).bg(Theme::background()))
}
/// Status bar style
pub fn status_bar() -> Style {
Style::default()
.fg(Theme::muted_text())
.bg(Theme::background())
}
/// CPU usage styling based on percentage
pub fn cpu_usage_style(percentage: u16) -> Style {
Style::default()
.fg(Theme::cpu_color(percentage))
.bg(Theme::background())
}
/// Memory usage styling based on percentage
pub fn memory_usage_style(percentage: u16) -> Style {
Style::default()
.fg(Theme::memory_color(percentage))
.bg(Theme::background())
}
/// Service status styling
pub fn service_status_style(status: Status) -> Style {
Style::default()
.fg(Theme::status_color(status))
.bg(Theme::background())
}
/// Backup item styling
pub fn backup_item_style(status: Status) -> Style {
Style::default()
.fg(Theme::status_color(status))
.bg(Theme::background())
}
}
impl Typography {
@@ -367,10 +313,4 @@ impl Typography {
.add_modifier(Modifier::BOLD)
}
/// Status text with dynamic colors
pub fn status(status: Status) -> Style {
Style::default()
.fg(Theme::status_color(status))
.bg(Theme::background())
}
}

View File

@@ -107,19 +107,6 @@ impl BackupWidget {
}
}
/// Format service status counts
fn format_service_counts(&self) -> String {
let completed = self.services_completed_count.unwrap_or(0);
let failed = self.services_failed_count.unwrap_or(0);
let disabled = self.services_disabled_count.unwrap_or(0);
let total = self.total_services.unwrap_or(0);
if failed > 0 {
format!("{}{}{}◯ ({})", completed, failed, disabled, total)
} else {
format!("{}{}◯ ({})", completed, disabled, total)
}
}
/// Format disk usage in format "usedGB/totalGB"
fn format_repo_size(&self) -> String {
@@ -160,30 +147,7 @@ impl BackupWidget {
}
}
/// Get status indicator symbol
fn get_status_symbol(&self) -> &str {
match self.overall_status {
Status::Ok => "",
Status::Warning => "",
Status::Critical => "",
Status::Unknown => "?",
}
}
/// Format size in GB to appropriate unit (kB/MB/GB)
fn format_size_gb(size_gb: f32) -> String {
if size_gb >= 1.0 {
format!("{:.1}GB", size_gb)
} else if size_gb >= 0.001 {
let size_mb = size_gb * 1024.0;
format!("{:.1}MB", size_mb)
} else if size_gb >= 0.000001 {
let size_kb = size_gb * 1024.0 * 1024.0;
format!("{:.0}kB", size_kb)
} else {
format!("{:.0}B", size_gb * 1024.0 * 1024.0 * 1024.0)
}
}
/// Format product name display
fn format_product_name(&self) -> String {

View File

@@ -118,36 +118,6 @@ impl ServicesWidget {
disk_str)
}
/// Format sub-service line (indented, no memory/disk columns) - returns text without icon for span formatting
fn format_sub_service_line(&self, name: &str, info: &ServiceInfo) -> String {
// Truncate long sub-service names to fit layout (accounting for indentation)
let short_name = if name.len() > 18 {
format!("{}...", &name[..15])
} else {
name.to_string()
};
// Sub-services show latency if available, otherwise status
let status_str = if let Some(latency) = info.latency_ms {
if latency < 0.0 {
"timeout".to_string()
} else {
format!("{:.0}ms", latency)
}
} else {
match info.widget_status {
Status::Ok => "active".to_string(),
Status::Warning => "inactive".to_string(),
Status::Critical => "failed".to_string(),
Status::Unknown => "unknown".to_string(),
}
};
// Indent sub-services with " ├─ " prefix (no memory/disk columns)
format!(" ├─ {:<20} {:<10}",
short_name,
status_str)
}
/// Create spans for sub-service with icon next to name
fn create_sub_service_spans(&self, name: &str, info: &ServiceInfo) -> Vec<ratatui::text::Span<'static>> {