Implement complete structured data architecture
All checks were successful
Build and Release / build-and-release (push) Successful in 2m10s

Replace fragile string-based metrics with type-safe JSON data structures.
Agent converts all metrics to structured data, dashboard processes typed fields.

Changes:
- Add AgentData struct with CPU, memory, storage, services, backup fields
- Replace string parsing with direct field access throughout system
- Maintain UI compatibility via temporary metric bridge conversion
- Fix NVMe temperature display and eliminate string parsing bugs
- Update protocol to support structured data transmission over ZMQ
- Comprehensive metric type coverage: CPU, memory, storage, services, backup

Version bump to 0.1.131
This commit is contained in:
2025-11-23 21:32:00 +01:00
parent 41ded0170c
commit adf3b0f51c
14 changed files with 926 additions and 106 deletions

View File

@@ -380,16 +380,18 @@ impl SystemWidget {
}
}
// Handle physical drive metrics: disk_{drive}_health and disk_{drive}_wear_percent
// Handle physical drive metrics: disk_{drive}_health, disk_{drive}_wear_percent, and disk_{drive}_temperature
if (metric_name.ends_with("_health") && !metric_name.contains("_pool_health"))
|| metric_name.ends_with("_wear_percent") {
|| metric_name.ends_with("_wear_percent")
|| metric_name.ends_with("_temperature") {
// Count underscores to distinguish physical drive metrics (disk_{drive}_metric)
// from pool drive metrics (disk_{pool}_{drive}_metric)
let underscore_count = metric_name.matches('_').count();
// disk_nvme0n1_wear_percent has 3 underscores: disk_nvme0n1_wear_percent
if underscore_count == 3 { // disk_{drive}_wear_percent (where drive has underscores)
if underscore_count == 3 { // disk_{drive}_metric (where drive has underscores)
if let Some(suffix_pos) = metric_name.rfind("_health")
.or_else(|| metric_name.rfind("_wear_percent")) {
.or_else(|| metric_name.rfind("_wear_percent"))
.or_else(|| metric_name.rfind("_temperature")) {
return Some(metric_name[5..suffix_pos].to_string()); // Skip "disk_"
}
}
@@ -468,15 +470,25 @@ impl SystemWidget {
for pool in &self.storage_pools {
// Pool header line with type and health
let pool_label = if pool.pool_type.starts_with("drive (") {
// For physical drives, show the drive name with wear percentage if available
// Look for any drive with wear data (physical drives may have drives named after the pool)
// For physical drives, show the drive name with temperature and wear percentage if available
// Look for any drive with temp/wear data (physical drives may have drives named after the pool)
let temp_opt = pool.drives.iter()
.find_map(|d| d.temperature);
let wear_opt = pool.drives.iter()
.find_map(|d| d.wear_percent);
let mut drive_info = Vec::new();
if let Some(temp) = temp_opt {
drive_info.push(format!("T: {:.0}°C", temp));
}
if let Some(wear) = wear_opt {
format!("{} W: {:.0}%:", pool.name, wear)
} else {
drive_info.push(format!("W: {:.0}%", wear));
}
if drive_info.is_empty() {
format!("{}:", pool.name)
} else {
format!("{} {}:", pool.name, drive_info.join(" "))
}
} else if pool.pool_type == "single" {
format!("{}:", pool.mount_point)