Fix service status icon mismatch with single source of truth architecture
All checks were successful
Build and Release / build-and-release (push) Successful in 1m8s

- Remove duplicate status string fields from ServiceData and SubServiceData
- Use only Status enum as single source of truth for service status
- Agent calculates Status enum using calculate_service_status()
- Dashboard converts Status enum to display text for UI
- Implement flexible metrics system for sub-services with label/value/unit
- Fix status icon/text mismatches (inactive services now show gray circles)
- Ensure perfect alignment between service icons and status text
This commit is contained in:
2025-11-24 22:43:22 +01:00
parent eb892096d9
commit 75ec190b93
7 changed files with 129 additions and 69 deletions

View File

@@ -1,6 +1,6 @@
use anyhow::Result;
use async_trait::async_trait;
use cm_dashboard_shared::{AgentData, ServiceData, Status};
use cm_dashboard_shared::{AgentData, ServiceData, SubServiceData, SubServiceMetric, Status};
use std::process::Command;
use std::sync::RwLock;
use std::time::Instant;
@@ -99,15 +99,9 @@ impl SystemdCollector {
let memory_mb = self.get_service_memory_usage(service_name).await.unwrap_or(0.0);
let disk_gb = self.get_service_disk_usage(service_name).await.unwrap_or(0.0);
let service_info = ServiceInfo {
name: service_name.clone(),
status: active_status.clone(),
memory_mb,
disk_gb,
};
services.push(service_info);
let mut sub_services = Vec::new();
// Sub-service metrics for specific services
// Collect sub-services for specific services
if service_name.contains("nginx") && active_status == "active" {
let nginx_sites = self.get_nginx_site_metrics();
for (site_name, latency_ms) in nginx_sites {
@@ -117,11 +111,17 @@ impl SystemdCollector {
"failed"
};
services.push(ServiceInfo {
name: site_name,
status: site_status.to_string(),
memory_mb: 0.0,
disk_gb: latency_ms / 1000.0, // Store latency in disk_gb field as workaround
let mut metrics = Vec::new();
metrics.push(SubServiceMetric {
label: "latency_ms".to_string(),
value: latency_ms,
unit: Some("ms".to_string()),
});
sub_services.push(SubServiceData {
name: site_name.clone(),
service_status: self.calculate_service_status(&site_name, &site_status),
metrics,
});
}
}
@@ -129,14 +129,35 @@ impl SystemdCollector {
if service_name.contains("docker") && active_status == "active" {
let docker_containers = self.get_docker_containers();
for (container_name, container_status) in docker_containers {
services.push(ServiceInfo {
name: container_name,
status: container_status,
memory_mb: 0.0,
disk_gb: 0.0,
// For now, docker containers have no additional metrics
// Future: could add memory_mb, cpu_percent, restart_count, etc.
let metrics = Vec::new();
sub_services.push(SubServiceData {
name: container_name.clone(),
service_status: self.calculate_service_status(&container_name, &container_status),
metrics,
});
}
}
let service_info = ServiceInfo {
name: service_name.clone(),
status: active_status.clone(),
memory_mb,
disk_gb,
};
services.push(service_info);
// Add to AgentData with hierarchical structure
agent_data.services.push(ServiceData {
name: service_name.clone(),
memory_mb,
disk_gb,
user_stopped: false, // TODO: Integrate with service tracker
service_status: self.calculate_service_status(service_name, &active_status),
sub_services,
});
}
Err(e) => {
debug!("Failed to get status for service {}: {}", service_name, e);
@@ -148,19 +169,7 @@ impl SystemdCollector {
{
let mut state = self.state.write().unwrap();
state.last_collection = Some(start_time);
state.services = services.clone();
}
// Populate AgentData with service information
for service in services {
agent_data.services.push(ServiceData {
name: service.name.clone(),
status: service.status.clone(),
memory_mb: service.memory_mb,
disk_gb: service.disk_gb,
user_stopped: false, // TODO: Integrate with service tracker
service_status: self.calculate_service_status(&service.name, &service.status),
});
state.services = services;
}
let elapsed = start_time.elapsed();
@@ -832,11 +841,11 @@ impl Collector for SystemdCollector {
for service in cached_services {
agent_data.services.push(ServiceData {
name: service.name.clone(),
status: service.status.clone(),
memory_mb: service.memory_mb,
disk_gb: service.disk_gb,
user_stopped: false, // TODO: Integrate with service tracker
service_status: self.calculate_service_status(&service.name, &service.status),
sub_services: Vec::new(), // Cached services don't have sub-services
});
}
Ok(())