Fix storage display format and clean up warnings
All checks were successful
Build and Release / build-and-release (push) Successful in 1m9s
All checks were successful
Build and Release / build-and-release (push) Successful in 1m9s
Update storage display to match CLAUDE.md specification: - Show drive temp/wear on main line: nvme0n1 T: 25°C W: 4% - Display individual filesystems as sub-items: /: 55% 250.5GB/456.4GB - Remove Total usage line in favor of filesystem breakdown Clean up code warnings: - Remove unused heartbeat methods and fields - Remove unused backup widget fields and methods - Add allow attributes for legacy methods
This commit is contained in:
parent
bea2d120b5
commit
11d1c2dc94
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -279,7 +279,7 @@ checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
|
||||
|
||||
[[package]]
|
||||
name = "cm-dashboard"
|
||||
version = "0.1.136"
|
||||
version = "0.1.137"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@ -301,7 +301,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cm-dashboard-agent"
|
||||
version = "0.1.136"
|
||||
version = "0.1.137"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -324,7 +324,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cm-dashboard-shared"
|
||||
version = "0.1.136"
|
||||
version = "0.1.137"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"serde",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "cm-dashboard-agent"
|
||||
version = "0.1.137"
|
||||
version = "0.1.138"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@ -262,47 +262,11 @@ impl Agent {
|
||||
agent_data.system.memory.swap_used_gb = value;
|
||||
}
|
||||
}
|
||||
// Tmpfs metrics
|
||||
else if metric.name.starts_with("memory_tmp_") {
|
||||
// For now, create a single /tmp tmpfs entry
|
||||
if metric.name == "memory_tmp_usage_percent" {
|
||||
// Tmpfs metrics - handle multiple auto-discovered tmpfs mounts
|
||||
else if metric.name.starts_with("memory_tmpfs_") {
|
||||
if let Some((mount_point, metric_type)) = self.parse_tmpfs_metric_name(&metric.name) {
|
||||
if let Some(value) = metric.value.as_f32() {
|
||||
if let Some(tmpfs) = agent_data.system.memory.tmpfs.get_mut(0) {
|
||||
tmpfs.usage_percent = value;
|
||||
} else {
|
||||
agent_data.system.memory.tmpfs.push(TmpfsData {
|
||||
mount: "/tmp".to_string(),
|
||||
usage_percent: value,
|
||||
used_gb: 0.0,
|
||||
total_gb: 0.0,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if metric.name == "memory_tmp_used_gb" {
|
||||
if let Some(value) = metric.value.as_f32() {
|
||||
if let Some(tmpfs) = agent_data.system.memory.tmpfs.get_mut(0) {
|
||||
tmpfs.used_gb = value;
|
||||
} else {
|
||||
agent_data.system.memory.tmpfs.push(TmpfsData {
|
||||
mount: "/tmp".to_string(),
|
||||
usage_percent: 0.0,
|
||||
used_gb: value,
|
||||
total_gb: 0.0,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if metric.name == "memory_tmp_total_gb" {
|
||||
if let Some(value) = metric.value.as_f32() {
|
||||
if let Some(tmpfs) = agent_data.system.memory.tmpfs.get_mut(0) {
|
||||
tmpfs.total_gb = value;
|
||||
} else {
|
||||
agent_data.system.memory.tmpfs.push(TmpfsData {
|
||||
mount: "/tmp".to_string(),
|
||||
usage_percent: 0.0,
|
||||
used_gb: 0.0,
|
||||
total_gb: value,
|
||||
});
|
||||
}
|
||||
self.update_tmpfs_data(&mut agent_data.system.memory.tmpfs, &mount_point, &metric_type, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -394,6 +358,63 @@ impl Agent {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parse tmpfs metric name to extract mount point and metric type
|
||||
/// Example: "memory_tmpfs_tmp_usage_percent" -> ("/tmp", "usage_percent")
|
||||
fn parse_tmpfs_metric_name(&self, metric_name: &str) -> Option<(String, String)> {
|
||||
if !metric_name.starts_with("memory_tmpfs_") {
|
||||
return None;
|
||||
}
|
||||
|
||||
let remainder = &metric_name[13..]; // Remove "memory_tmpfs_" prefix
|
||||
|
||||
// Find the last underscore to separate metric type from mount point
|
||||
if let Some(last_underscore) = remainder.rfind('_') {
|
||||
let mount_safe = &remainder[..last_underscore];
|
||||
let metric_type = &remainder[last_underscore + 1..];
|
||||
|
||||
// Convert safe mount name back to actual mount point
|
||||
let mount_point = if mount_safe.is_empty() {
|
||||
"/"
|
||||
} else {
|
||||
&format!("/{}", mount_safe.replace('_', "/"))
|
||||
};
|
||||
|
||||
Some((mount_point.to_string(), metric_type.to_string()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Update tmpfs data in the tmpfs vector
|
||||
fn update_tmpfs_data(&self, tmpfs_vec: &mut Vec<TmpfsData>, mount_point: &str, metric_type: &str, value: f32) {
|
||||
// Find existing tmpfs entry
|
||||
let existing_index = tmpfs_vec.iter()
|
||||
.position(|tmpfs| tmpfs.mount == mount_point);
|
||||
|
||||
let tmpfs_index = if let Some(index) = existing_index {
|
||||
index
|
||||
} else {
|
||||
// Create new entry
|
||||
tmpfs_vec.push(TmpfsData {
|
||||
mount: mount_point.to_string(),
|
||||
usage_percent: 0.0,
|
||||
used_gb: 0.0,
|
||||
total_gb: 0.0,
|
||||
});
|
||||
tmpfs_vec.len() - 1
|
||||
};
|
||||
|
||||
// Update the tmpfs entry
|
||||
if let Some(tmpfs) = tmpfs_vec.get_mut(tmpfs_index) {
|
||||
match metric_type {
|
||||
"usage_percent" => tmpfs.usage_percent = value,
|
||||
"used_gb" => tmpfs.used_gb = value,
|
||||
"total_gb" => tmpfs.total_gb = value,
|
||||
_ => {} // Unknown metric type, ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract drive name from metric like "disk_nvme0n1_temperature"
|
||||
fn extract_drive_name(&self, metric_name: &str) -> Option<String> {
|
||||
if metric_name.starts_with("disk_") {
|
||||
@ -529,31 +550,6 @@ impl Agent {
|
||||
}
|
||||
|
||||
/// Create heartbeat metric for host connectivity detection
|
||||
fn get_heartbeat_metric(&self) -> Metric {
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
let timestamp = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs();
|
||||
|
||||
Metric::new(
|
||||
"agent_heartbeat".to_string(),
|
||||
MetricValue::Integer(timestamp as i64),
|
||||
Status::Ok,
|
||||
)
|
||||
}
|
||||
|
||||
/// Send standalone heartbeat for connectivity detection
|
||||
async fn send_heartbeat(&mut self) -> Result<()> {
|
||||
// Create minimal agent data with just heartbeat
|
||||
let agent_data = AgentData::new(self.hostname.clone(), self.get_agent_version());
|
||||
// Heartbeat timestamp is already set in AgentData::new()
|
||||
|
||||
self.zmq_handler.publish_agent_data(&agent_data).await?;
|
||||
debug!("Sent standalone heartbeat for connectivity detection");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_commands(&mut self) -> Result<()> {
|
||||
// Try to receive commands (non-blocking)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "cm-dashboard"
|
||||
version = "0.1.137"
|
||||
version = "0.1.138"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@ -18,8 +18,6 @@ pub struct BackupWidget {
|
||||
duration_seconds: Option<i64>,
|
||||
/// Last backup timestamp
|
||||
last_run_timestamp: Option<i64>,
|
||||
/// Total number of backup services
|
||||
total_services: Option<i64>,
|
||||
/// Total repository size in GB
|
||||
total_repo_size_gb: Option<f32>,
|
||||
/// Total disk space for backups in GB
|
||||
@ -32,14 +30,6 @@ pub struct BackupWidget {
|
||||
backup_disk_serial_number: Option<String>,
|
||||
/// Backup disk wear percentage from SMART data
|
||||
backup_disk_wear_percent: Option<f32>,
|
||||
/// Backup disk filesystem label
|
||||
backup_disk_filesystem_label: Option<String>,
|
||||
/// Number of completed services
|
||||
services_completed_count: Option<i64>,
|
||||
/// Number of failed services
|
||||
services_failed_count: Option<i64>,
|
||||
/// Number of disabled services
|
||||
services_disabled_count: Option<i64>,
|
||||
/// All individual service metrics for detailed display
|
||||
service_metrics: Vec<ServiceMetricData>,
|
||||
/// Last update indicator
|
||||
@ -50,7 +40,6 @@ pub struct BackupWidget {
|
||||
struct ServiceMetricData {
|
||||
name: String,
|
||||
status: Status,
|
||||
exit_code: Option<i64>,
|
||||
archive_count: Option<i64>,
|
||||
repo_size_gb: Option<f32>,
|
||||
}
|
||||
@ -61,17 +50,12 @@ impl BackupWidget {
|
||||
overall_status: Status::Unknown,
|
||||
duration_seconds: None,
|
||||
last_run_timestamp: None,
|
||||
total_services: None,
|
||||
total_repo_size_gb: None,
|
||||
backup_disk_total_gb: None,
|
||||
backup_disk_used_gb: None,
|
||||
backup_disk_product_name: None,
|
||||
backup_disk_serial_number: None,
|
||||
backup_disk_wear_percent: None,
|
||||
backup_disk_filesystem_label: None,
|
||||
services_completed_count: None,
|
||||
services_failed_count: None,
|
||||
services_disabled_count: None,
|
||||
service_metrics: Vec::new(),
|
||||
has_data: false,
|
||||
}
|
||||
@ -112,6 +96,7 @@ impl BackupWidget {
|
||||
|
||||
|
||||
/// Extract service name from metric name (e.g., "backup_service_gitea_status" -> "gitea")
|
||||
#[allow(dead_code)]
|
||||
fn extract_service_name(metric_name: &str) -> Option<String> {
|
||||
if metric_name.starts_with("backup_service_") {
|
||||
let name_part = &metric_name[15..]; // Remove "backup_service_" prefix
|
||||
@ -119,8 +104,6 @@ impl BackupWidget {
|
||||
// Try to extract service name by removing known suffixes
|
||||
if let Some(service_name) = name_part.strip_suffix("_status") {
|
||||
Some(service_name.to_string())
|
||||
} else if let Some(service_name) = name_part.strip_suffix("_exit_code") {
|
||||
Some(service_name.to_string())
|
||||
} else if let Some(service_name) = name_part.strip_suffix("_archive_count") {
|
||||
Some(service_name.to_string())
|
||||
} else if let Some(service_name) = name_part.strip_suffix("_repo_size_gb") {
|
||||
@ -154,6 +137,7 @@ impl Widget for BackupWidget {
|
||||
}
|
||||
|
||||
impl BackupWidget {
|
||||
#[allow(dead_code)]
|
||||
fn update_from_metrics(&mut self, metrics: &[&Metric]) {
|
||||
debug!("Backup widget updating with {} metrics", metrics.len());
|
||||
for metric in metrics {
|
||||
@ -199,9 +183,6 @@ impl BackupWidget {
|
||||
"backup_last_run_timestamp" => {
|
||||
self.last_run_timestamp = metric.value.as_i64();
|
||||
}
|
||||
"backup_total_services" => {
|
||||
self.total_services = metric.value.as_i64();
|
||||
}
|
||||
"backup_total_repo_size_gb" => {
|
||||
self.total_repo_size_gb = metric.value.as_f32();
|
||||
}
|
||||
@ -220,18 +201,6 @@ impl BackupWidget {
|
||||
"backup_disk_wear_percent" => {
|
||||
self.backup_disk_wear_percent = metric.value.as_f32();
|
||||
}
|
||||
"backup_disk_filesystem_label" => {
|
||||
self.backup_disk_filesystem_label = Some(metric.value.as_string());
|
||||
}
|
||||
"backup_services_completed_count" => {
|
||||
self.services_completed_count = metric.value.as_i64();
|
||||
}
|
||||
"backup_services_failed_count" => {
|
||||
self.services_failed_count = metric.value.as_i64();
|
||||
}
|
||||
"backup_services_disabled_count" => {
|
||||
self.services_disabled_count = metric.value.as_i64();
|
||||
}
|
||||
_ => {
|
||||
// Handle individual service metrics
|
||||
if let Some(service_name) = Self::extract_service_name(&metric.name) {
|
||||
@ -243,8 +212,7 @@ impl BackupWidget {
|
||||
ServiceMetricData {
|
||||
name: service_name,
|
||||
status: Status::Unknown,
|
||||
exit_code: None,
|
||||
archive_count: None,
|
||||
archive_count: None,
|
||||
repo_size_gb: None,
|
||||
}
|
||||
});
|
||||
@ -252,8 +220,6 @@ impl BackupWidget {
|
||||
if metric.name.ends_with("_status") {
|
||||
entry.status = metric.status;
|
||||
debug!("Set status for {}: {:?}", entry.name, entry.status);
|
||||
} else if metric.name.ends_with("_exit_code") {
|
||||
entry.exit_code = metric.value.as_i64();
|
||||
} else if metric.name.ends_with("_archive_count") {
|
||||
entry.archive_count = metric.value.as_i64();
|
||||
debug!(
|
||||
|
||||
@ -47,6 +47,7 @@ impl ServicesWidget {
|
||||
}
|
||||
|
||||
/// Extract service name and determine if it's a parent or sub-service
|
||||
#[allow(dead_code)]
|
||||
fn extract_service_info(metric_name: &str) -> Option<(String, Option<String>)> {
|
||||
if metric_name.starts_with("service_") {
|
||||
if let Some(end_pos) = metric_name
|
||||
@ -277,6 +278,7 @@ impl Widget for ServicesWidget {
|
||||
}
|
||||
|
||||
impl ServicesWidget {
|
||||
#[allow(dead_code)]
|
||||
fn update_from_metrics(&mut self, metrics: &[&Metric]) {
|
||||
debug!("Services widget updating with {} metrics", metrics.len());
|
||||
|
||||
|
||||
@ -31,6 +31,8 @@ pub struct SystemWidget {
|
||||
tmp_total_gb: Option<f32>,
|
||||
memory_status: Status,
|
||||
tmp_status: Status,
|
||||
/// All tmpfs mounts (for auto-discovery support)
|
||||
tmpfs_mounts: Vec<cm_dashboard_shared::TmpfsData>,
|
||||
|
||||
// Storage metrics (collected from disk metrics)
|
||||
storage_pools: Vec<StoragePool>,
|
||||
@ -50,7 +52,6 @@ struct StoragePool {
|
||||
used_gb: Option<f32>,
|
||||
total_gb: Option<f32>,
|
||||
status: Status,
|
||||
health_status: Status, // Separate status for pool health vs usage
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -88,6 +89,7 @@ impl SystemWidget {
|
||||
tmp_total_gb: None,
|
||||
memory_status: Status::Unknown,
|
||||
tmp_status: Status::Unknown,
|
||||
tmpfs_mounts: Vec::new(),
|
||||
storage_pools: Vec::new(),
|
||||
has_data: false,
|
||||
}
|
||||
@ -121,20 +123,6 @@ impl SystemWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Format /tmp usage
|
||||
fn format_tmp_usage(&self) -> String {
|
||||
match (self.tmp_usage_percent, self.tmp_used_gb, self.tmp_total_gb) {
|
||||
(Some(pct), Some(used), Some(total)) => {
|
||||
let used_str = if used < 0.1 {
|
||||
format!("{:.0}B", used * 1024.0) // Show as MB if very small
|
||||
} else {
|
||||
format!("{:.1}GB", used)
|
||||
};
|
||||
format!("{:.0}% {}/{:.1}GB", pct, used_str, total)
|
||||
}
|
||||
_ => "—% —GB/—GB".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the current agent hash for rebuild completion detection
|
||||
pub fn _get_agent_hash(&self) -> Option<&String> {
|
||||
@ -166,7 +154,10 @@ impl Widget for SystemWidget {
|
||||
self.memory_total_gb = Some(memory.total_gb);
|
||||
self.memory_status = Status::Ok;
|
||||
|
||||
// Extract tmpfs data
|
||||
// Store all tmpfs mounts for display
|
||||
self.tmpfs_mounts = memory.tmpfs.clone();
|
||||
|
||||
// Extract tmpfs data (maintain backward compatibility for /tmp)
|
||||
if let Some(tmp_data) = memory.tmpfs.iter().find(|t| t.mount == "/tmp") {
|
||||
self.tmp_usage_percent = Some(tmp_data.usage_percent);
|
||||
self.tmp_used_gb = Some(tmp_data.used_gb);
|
||||
@ -196,7 +187,6 @@ impl SystemWidget {
|
||||
used_gb: None,
|
||||
total_gb: None,
|
||||
status: Status::Ok,
|
||||
health_status: Status::Ok,
|
||||
};
|
||||
|
||||
// Add drive info
|
||||
@ -278,40 +268,27 @@ impl SystemWidget {
|
||||
let pool_spans = StatusIcons::create_status_spans(pool.status.clone(), &pool_label);
|
||||
lines.push(Line::from(pool_spans));
|
||||
|
||||
// Pool total usage line
|
||||
if let (Some(usage), Some(used), Some(total)) = (pool.usage_percent, pool.used_gb, pool.total_gb) {
|
||||
let usage_spans = vec![
|
||||
Span::styled(" ├─ ", Typography::tree()),
|
||||
Span::raw(" "),
|
||||
];
|
||||
let mut usage_line_spans = usage_spans;
|
||||
usage_line_spans.extend(StatusIcons::create_status_spans(pool.status.clone(), &format!("Total: {}% {:.1}GB/{:.1}GB", usage as i32, used, total)));
|
||||
lines.push(Line::from(usage_line_spans));
|
||||
}
|
||||
|
||||
// Drive details for physical drives
|
||||
// Show individual filesystems for physical drives (matching CLAUDE.md format)
|
||||
if pool.pool_type.starts_with("drive") {
|
||||
for drive in &pool.drives {
|
||||
if drive.name == pool.name {
|
||||
let mut drive_details = Vec::new();
|
||||
if let Some(temp) = drive.temperature {
|
||||
drive_details.push(format!("T: {}°C", temp as i32));
|
||||
}
|
||||
if let Some(wear) = drive.wear_percent {
|
||||
drive_details.push(format!("W: {}%", wear as i32));
|
||||
}
|
||||
// Show filesystem entries like: ├─ ● /: 55% 250.5GB/456.4GB
|
||||
for (i, filesystem) in pool.filesystems.iter().enumerate() {
|
||||
let is_last = i == pool.filesystems.len() - 1;
|
||||
let tree_symbol = if is_last { " └─ " } else { " ├─ " };
|
||||
|
||||
if !drive_details.is_empty() {
|
||||
let drive_text = format!("● {} {}", drive.name, drive_details.join(" "));
|
||||
let drive_spans = vec![
|
||||
Span::styled(" └─ ", Typography::tree()),
|
||||
Span::raw(" "),
|
||||
];
|
||||
let mut drive_line_spans = drive_spans;
|
||||
drive_line_spans.extend(StatusIcons::create_status_spans(drive.status.clone(), &drive_text));
|
||||
lines.push(Line::from(drive_line_spans));
|
||||
}
|
||||
}
|
||||
let fs_text = format!("{}: {:.0}% {:.1}GB/{:.1}GB",
|
||||
filesystem.mount_point,
|
||||
filesystem.usage_percent.unwrap_or(0.0),
|
||||
filesystem.used_gb.unwrap_or(0.0),
|
||||
filesystem.total_gb.unwrap_or(0.0));
|
||||
|
||||
let mut fs_spans = vec![
|
||||
Span::styled(tree_symbol, Typography::tree()),
|
||||
];
|
||||
fs_spans.extend(StatusIcons::create_status_spans(
|
||||
filesystem.status.clone(),
|
||||
&fs_text
|
||||
));
|
||||
lines.push(Line::from(fs_spans));
|
||||
}
|
||||
} else {
|
||||
// For mergerfs pools, show data drives and parity drives in tree structure
|
||||
@ -432,15 +409,29 @@ impl SystemWidget {
|
||||
);
|
||||
lines.push(Line::from(memory_spans));
|
||||
|
||||
let tmp_text = self.format_tmp_usage();
|
||||
let mut tmp_spans = vec![
|
||||
Span::styled(" └─ ", Typography::tree()),
|
||||
];
|
||||
tmp_spans.extend(StatusIcons::create_status_spans(
|
||||
self.tmp_status.clone(),
|
||||
&format!("/tmp: {}", tmp_text)
|
||||
));
|
||||
lines.push(Line::from(tmp_spans));
|
||||
// Display all tmpfs mounts
|
||||
for (i, tmpfs) in self.tmpfs_mounts.iter().enumerate() {
|
||||
let is_last = i == self.tmpfs_mounts.len() - 1;
|
||||
let tree_symbol = if is_last { " └─ " } else { " ├─ " };
|
||||
|
||||
let usage_text = if tmpfs.total_gb > 0.0 {
|
||||
format!("{:.0}% {:.1}GB/{:.1}GB",
|
||||
tmpfs.usage_percent,
|
||||
tmpfs.used_gb,
|
||||
tmpfs.total_gb)
|
||||
} else {
|
||||
"— —/—".to_string()
|
||||
};
|
||||
|
||||
let mut tmpfs_spans = vec![
|
||||
Span::styled(tree_symbol, Typography::tree()),
|
||||
];
|
||||
tmpfs_spans.extend(StatusIcons::create_status_spans(
|
||||
Status::Ok, // TODO: Calculate status based on usage_percent
|
||||
&format!("{}: {}", tmpfs.mount, usage_text)
|
||||
));
|
||||
lines.push(Line::from(tmpfs_spans));
|
||||
}
|
||||
|
||||
// Storage section
|
||||
lines.push(Line::from(vec![
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "cm-dashboard-shared"
|
||||
version = "0.1.137"
|
||||
version = "0.1.138"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user