Add disk quota display to services widget

Implement disk quota/total display in services widget showing usage/quota
format. When services don't have specific disk quotas configured, use
system total disk capacity as the quota value.

Changes:
- Add disk_quota_gb field to ServiceData struct in agent
- Add disk_quota_gb field to ServiceInfo struct in dashboard
- Update format_disk_value to show usage/quota format
- Use system disk total capacity as default quota for services
- Rename DiskUsage.total_gb to total_capacity_gb for clarity

Services will now display disk usage as "5.2/500.0 GB" format where
500.0 GB is either the service's specific quota or system total capacity.
This commit is contained in:
Christoffer Martinsson 2025-10-14 10:14:24 +02:00
parent 6265b1afb3
commit 630d2ff674
3 changed files with 27 additions and 9 deletions

View File

@ -113,6 +113,7 @@ impl ServiceCollector {
cpu_percent, cpu_percent,
sandbox_limit: None, // TODO: Implement sandbox limit detection sandbox_limit: None, // TODO: Implement sandbox limit detection
disk_used_gb, disk_used_gb,
disk_quota_gb: 0.0, // Will be set to system total in collect()
description, description,
sub_service: None, sub_service: None,
}) })
@ -331,7 +332,7 @@ impl ServiceCollector {
}; };
Ok(DiskUsage { Ok(DiskUsage {
total_gb: parse_size(parts[0])?, total_capacity_gb: parse_size(parts[0])?,
used_gb: parse_size(parts[1])?, used_gb: parse_size(parts[1])?,
}) })
} }
@ -1122,6 +1123,7 @@ impl Collector for ServiceCollector {
cpu_percent: 0.0, cpu_percent: 0.0,
sandbox_limit: None, sandbox_limit: None,
disk_used_gb: 0.0, disk_used_gb: 0.0,
disk_quota_gb: 0.0,
description: None, description: None,
sub_service: Some("nginx".to_string()), sub_service: Some("nginx".to_string()),
}); });
@ -1147,6 +1149,7 @@ impl Collector for ServiceCollector {
cpu_percent: 0.0, cpu_percent: 0.0,
sandbox_limit: None, sandbox_limit: None,
disk_used_gb: 0.0, disk_used_gb: 0.0,
disk_quota_gb: 0.0,
description: None, description: None,
sub_service: Some("docker".to_string()), sub_service: Some("docker".to_string()),
}); });
@ -1168,6 +1171,7 @@ impl Collector for ServiceCollector {
cpu_percent: 0.0, cpu_percent: 0.0,
sandbox_limit: None, sandbox_limit: None,
disk_used_gb: 0.0, disk_used_gb: 0.0,
disk_quota_gb: 0.0,
description: None, description: None,
sub_service: None, sub_service: None,
}); });
@ -1176,12 +1180,18 @@ impl Collector for ServiceCollector {
} }
} }
let disk_usage = self.get_disk_usage().await.unwrap_or(DiskUsage {
let _disk_usage = self.get_disk_usage().await.unwrap_or(DiskUsage { total_capacity_gb: 0.0,
total_gb: 0.0,
used_gb: 0.0, used_gb: 0.0,
}); });
// Set disk quota to system total capacity for services that don't have specific quotas
let system_disk_capacity_gb = disk_usage.total_capacity_gb;
for service in &mut services {
if service.disk_quota_gb == 0.0 {
service.disk_quota_gb = system_disk_capacity_gb;
}
}
// Calculate overall services status // Calculate overall services status
let services_status = self.determine_services_status(healthy, degraded, failed); let services_status = self.determine_services_status(healthy, degraded, failed);
@ -1226,6 +1236,7 @@ struct ServiceData {
cpu_percent: f32, cpu_percent: f32,
sandbox_limit: Option<f32>, sandbox_limit: Option<f32>,
disk_used_gb: f32, disk_used_gb: f32,
disk_quota_gb: f32,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
description: Option<Vec<String>>, description: Option<Vec<String>>,
#[serde(default)] #[serde(default)]
@ -1243,6 +1254,6 @@ enum ServiceStatus {
#[allow(dead_code)] #[allow(dead_code)]
struct DiskUsage { struct DiskUsage {
total_gb: f32, total_capacity_gb: f32,
used_gb: f32, used_gb: f32,
} }

View File

@ -117,6 +117,8 @@ pub struct ServiceInfo {
#[serde(default)] #[serde(default)]
pub disk_used_gb: f32, pub disk_used_gb: f32,
#[serde(default)] #[serde(default)]
pub disk_quota_gb: f32,
#[serde(default)]
pub description: Option<Vec<String>>, pub description: Option<Vec<String>>,
#[serde(default)] #[serde(default)]
pub sub_service: Option<String>, pub sub_service: Option<String>,

View File

@ -126,7 +126,7 @@ fn render_metrics(
svc.name.clone(), svc.name.clone(),
format_memory_value(svc.memory_used_mb, svc.memory_quota_mb), format_memory_value(svc.memory_used_mb, svc.memory_quota_mb),
format_cpu_value(svc.cpu_percent), format_cpu_value(svc.cpu_percent),
format_disk_value(svc.disk_used_gb), format_disk_value(svc.disk_used_gb, svc.disk_quota_gb),
], ],
); );
} }
@ -158,14 +158,19 @@ fn format_cpu_value(cpu_percent: f32) -> String {
} }
} }
fn format_disk_value(used: f32) -> String { fn format_disk_value(used: f32, quota: f32) -> String {
if used >= 1.0 { if quota > 0.05 {
// Show usage/quota format when quota is set
format!("{:.1}/{:.1} GB", used, quota)
} else if used >= 1.0 {
format!("{:.1} GB", used) format!("{:.1} GB", used)
} else if used >= 0.001 { } else if used >= 0.001 {
// 1 MB or more // 1 MB or more
format!("{:.0} MB", used * 1000.0) format!("{:.0} MB", used * 1000.0)
} else { } else if used > 0.0 {
"<1 MB".to_string() "<1 MB".to_string()
} else {
"".to_string() // Em dash for no disk usage
} }
} }