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:
parent
7f85a6436e
commit
0141a6e111
57
agent/src/cache/manager.rs
vendored
57
agent/src/cache/manager.rs
vendored
@ -6,7 +6,6 @@ use tracing::info;
|
||||
/// Manages metric caching with background tasks
|
||||
pub struct MetricCacheManager {
|
||||
cache: Arc<ConfigurableCache>,
|
||||
config: CacheConfig,
|
||||
}
|
||||
|
||||
impl MetricCacheManager {
|
||||
@ -15,7 +14,6 @@ impl MetricCacheManager {
|
||||
|
||||
Self {
|
||||
cache,
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,69 +23,14 @@ impl MetricCacheManager {
|
||||
info!("Cache manager background tasks disabled for debugging");
|
||||
}
|
||||
|
||||
/// Check if metric should be collected
|
||||
pub async fn should_collect_metric(&self, metric_name: &str) -> bool {
|
||||
self.cache.should_collect(metric_name).await
|
||||
}
|
||||
|
||||
/// Store metric in cache
|
||||
pub async fn cache_metric(&self, metric: Metric) {
|
||||
self.cache.store_metric(metric).await;
|
||||
}
|
||||
|
||||
/// Get cached metric if valid
|
||||
pub async fn get_cached_metric(&self, metric_name: &str) -> Option<Metric> {
|
||||
self.cache.get_cached_metric(metric_name).await
|
||||
}
|
||||
|
||||
/// Get all valid cached metrics
|
||||
pub async fn get_all_valid_metrics(&self) -> Vec<Metric> {
|
||||
self.cache.get_all_valid_metrics().await
|
||||
}
|
||||
|
||||
/// Get all cached metrics (including expired ones) for broadcasting
|
||||
pub async fn get_all_cached_metrics(&self) -> Vec<Metric> {
|
||||
self.cache.get_all_cached_metrics().await
|
||||
}
|
||||
|
||||
/// Cache warm-up: collect and cache high-priority metrics
|
||||
pub async fn warm_cache<F>(&self, collector_fn: F)
|
||||
where
|
||||
F: Fn(&str) -> Option<Metric>,
|
||||
{
|
||||
if !self.config.enabled {
|
||||
return;
|
||||
}
|
||||
|
||||
let high_priority_patterns = ["cpu_load_*", "memory_usage_*"];
|
||||
let mut warmed_count = 0;
|
||||
|
||||
for pattern in &high_priority_patterns {
|
||||
// This is a simplified warm-up - in practice, you'd iterate through
|
||||
// known metric names or use a registry
|
||||
if pattern.starts_with("cpu_load_") {
|
||||
for suffix in &["1min", "5min", "15min"] {
|
||||
let metric_name = format!("cpu_load_{}", suffix);
|
||||
if let Some(metric) = collector_fn(&metric_name) {
|
||||
self.cache_metric(metric).await;
|
||||
warmed_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if warmed_count > 0 {
|
||||
info!("Cache warmed with {} metrics", warmed_count);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get cache configuration
|
||||
pub fn get_config(&self) -> &CacheConfig {
|
||||
&self.config
|
||||
}
|
||||
|
||||
/// Get cache tier interval for a metric
|
||||
pub fn get_cache_interval(&self, metric_name: &str) -> u64 {
|
||||
self.config.get_cache_interval(metric_name)
|
||||
}
|
||||
}
|
||||
103
agent/src/cache/mod.rs
vendored
103
agent/src/cache/mod.rs
vendored
@ -24,26 +24,6 @@ impl ConfigurableCache {
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if metric should be collected based on cache tier
|
||||
pub async fn should_collect(&self, metric_name: &str) -> bool {
|
||||
if !self.config.enabled {
|
||||
return true;
|
||||
}
|
||||
|
||||
let cache = self.cache.read().await;
|
||||
|
||||
if let Some(cached_metric) = cache.get(metric_name) {
|
||||
let cache_interval = self.config.get_cache_interval(metric_name);
|
||||
let elapsed = cached_metric.collected_at.elapsed().as_secs();
|
||||
|
||||
// Should collect if cache interval has passed
|
||||
elapsed >= cache_interval
|
||||
} else {
|
||||
// Not cached yet, should collect
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Store metric in cache
|
||||
pub async fn store_metric(&self, metric: Metric) {
|
||||
if !self.config.enabled {
|
||||
@ -69,50 +49,6 @@ impl ConfigurableCache {
|
||||
// Cached metric (debug logging disabled for performance)
|
||||
}
|
||||
|
||||
/// Get cached metric if valid
|
||||
pub async fn get_cached_metric(&self, metric_name: &str) -> Option<Metric> {
|
||||
if !self.config.enabled {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut cache = self.cache.write().await;
|
||||
|
||||
if let Some(cached_metric) = cache.get_mut(metric_name) {
|
||||
let cache_interval = self.config.get_cache_interval(metric_name);
|
||||
let elapsed = cached_metric.collected_at.elapsed().as_secs();
|
||||
|
||||
if elapsed < cache_interval {
|
||||
cached_metric.access_count += 1;
|
||||
// Cache hit (debug logging disabled for performance)
|
||||
return Some(cached_metric.metric.clone());
|
||||
} else {
|
||||
// Cache expired (debug logging disabled for performance)
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Get all cached metrics that are still valid
|
||||
pub async fn get_all_valid_metrics(&self) -> Vec<Metric> {
|
||||
if !self.config.enabled {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
let cache = self.cache.read().await;
|
||||
let mut valid_metrics = Vec::new();
|
||||
|
||||
for (metric_name, cached_metric) in cache.iter() {
|
||||
let cache_interval = self.config.get_cache_interval(metric_name);
|
||||
let elapsed = cached_metric.collected_at.elapsed().as_secs();
|
||||
|
||||
if elapsed < cache_interval {
|
||||
valid_metrics.push(cached_metric.metric.clone());
|
||||
}
|
||||
}
|
||||
|
||||
valid_metrics
|
||||
}
|
||||
|
||||
/// Get all cached metrics (including expired ones) for broadcasting
|
||||
pub async fn get_all_cached_metrics(&self) -> Vec<Metric> {
|
||||
@ -162,43 +98,4 @@ impl ConfigurableCache {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get cache statistics
|
||||
pub async fn get_stats(&self) -> CacheStats {
|
||||
let cache = self.cache.read().await;
|
||||
|
||||
let mut stats_by_tier = HashMap::new();
|
||||
for (_metric_name, cached_metric) in cache.iter() {
|
||||
let tier_name = cached_metric.tier
|
||||
.as_ref()
|
||||
.map(|t| t.description.clone())
|
||||
.unwrap_or_else(|| "default".to_string());
|
||||
|
||||
let tier_stats = stats_by_tier.entry(tier_name).or_insert(TierStats {
|
||||
count: 0,
|
||||
total_access_count: 0,
|
||||
});
|
||||
|
||||
tier_stats.count += 1;
|
||||
tier_stats.total_access_count += cached_metric.access_count;
|
||||
}
|
||||
|
||||
CacheStats {
|
||||
total_entries: cache.len(),
|
||||
stats_by_tier,
|
||||
enabled: self.config.enabled,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CacheStats {
|
||||
pub total_entries: usize,
|
||||
pub stats_by_tier: HashMap<String, TierStats>,
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TierStats {
|
||||
pub count: usize,
|
||||
pub total_access_count: u64,
|
||||
}
|
||||
@ -1,74 +0,0 @@
|
||||
use super::{Collector, CollectorError};
|
||||
use crate::cache::MetricCacheManager;
|
||||
use cm_dashboard_shared::Metric;
|
||||
use async_trait::async_trait;
|
||||
use std::sync::Arc;
|
||||
use tracing::{debug, instrument};
|
||||
|
||||
/// Wrapper that adds caching to any collector
|
||||
pub struct CachedCollector {
|
||||
name: String,
|
||||
inner: Box<dyn Collector>,
|
||||
cache_manager: Arc<MetricCacheManager>,
|
||||
}
|
||||
|
||||
impl CachedCollector {
|
||||
pub fn new(
|
||||
name: String,
|
||||
inner: Box<dyn Collector>,
|
||||
cache_manager: Arc<MetricCacheManager>
|
||||
) -> Self {
|
||||
Self {
|
||||
name,
|
||||
inner,
|
||||
cache_manager,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Collector for CachedCollector {
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
#[instrument(skip(self), fields(collector = %self.name))]
|
||||
async fn collect(&self) -> Result<Vec<Metric>, CollectorError> {
|
||||
// First, get all metrics this collector would normally produce
|
||||
let all_metrics = self.inner.collect().await?;
|
||||
|
||||
let mut result_metrics = Vec::new();
|
||||
let mut metrics_to_collect = Vec::new();
|
||||
|
||||
// Check cache for each metric
|
||||
for metric in all_metrics {
|
||||
if let Some(cached_metric) = self.cache_manager.get_cached_metric(&metric.name).await {
|
||||
// Use cached version
|
||||
result_metrics.push(cached_metric);
|
||||
debug!("Using cached metric: {}", metric.name);
|
||||
} else {
|
||||
// Need to collect this metric
|
||||
metrics_to_collect.push(metric.name.clone());
|
||||
result_metrics.push(metric);
|
||||
}
|
||||
}
|
||||
|
||||
// Cache the newly collected metrics
|
||||
for metric in &result_metrics {
|
||||
if metrics_to_collect.contains(&metric.name) {
|
||||
self.cache_manager.cache_metric(metric.clone()).await;
|
||||
debug!("Cached new metric: {} (tier: {}s)",
|
||||
metric.name,
|
||||
self.cache_manager.get_cache_interval(&metric.name));
|
||||
}
|
||||
}
|
||||
|
||||
if !metrics_to_collect.is_empty() {
|
||||
debug!("Collected {} new metrics, used {} cached metrics",
|
||||
metrics_to_collect.len(),
|
||||
result_metrics.len() - metrics_to_collect.len());
|
||||
}
|
||||
|
||||
Ok(result_metrics)
|
||||
}
|
||||
}
|
||||
@ -7,16 +7,4 @@ pub enum CollectorError {
|
||||
|
||||
#[error("Failed to parse value '{value}': {error}")]
|
||||
Parse { value: String, error: String },
|
||||
|
||||
#[error("System command failed: {command}: {error}")]
|
||||
CommandFailed { command: String, error: String },
|
||||
|
||||
#[error("Configuration error: {message}")]
|
||||
Configuration { message: String },
|
||||
|
||||
#[error("Metric calculation error: {message}")]
|
||||
Calculation { message: String },
|
||||
|
||||
#[error("Timeout error: operation took longer than {timeout_ms}ms")]
|
||||
Timeout { timeout_ms: u64 },
|
||||
}
|
||||
@ -2,7 +2,6 @@ use async_trait::async_trait;
|
||||
use cm_dashboard_shared::Metric;
|
||||
use std::time::Duration;
|
||||
|
||||
pub mod cached_collector;
|
||||
pub mod cpu;
|
||||
pub mod memory;
|
||||
pub mod disk;
|
||||
|
||||
@ -46,9 +46,6 @@ pub const DEFAULT_SMART_WEAR_CRITICAL: f32 = 90.0;
|
||||
// Backup configuration
|
||||
pub const DEFAULT_BACKUP_MAX_AGE_HOURS: u64 = 48;
|
||||
|
||||
// Cache configuration
|
||||
pub const DEFAULT_CACHE_TTL_SECONDS: u64 = 30;
|
||||
pub const DEFAULT_CACHE_MAX_ENTRIES: usize = 10000;
|
||||
|
||||
// Notification configuration (from legacy)
|
||||
pub const DEFAULT_SMTP_HOST: &str = "localhost";
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
}
|
||||
@ -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,
|
||||
}
|
||||
@ -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()
|
||||
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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>> {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user