From 64af24dc404363a373fa9994ce4886d5bf0e487a Mon Sep 17 00:00:00 2001 From: Christoffer Martinsson Date: Thu, 23 Oct 2025 14:48:25 +0200 Subject: [PATCH] Update NixOS display format to show build hash and timestamp - Change from showing version to build format: 'hash dd/mm/yy H:M:S' - Parse nixos-version output to extract short hash and format date - Update system widget to display 'Build:' instead of 'Version:' - Remove version/build_date fields in favor of single build string - Follow TODO.md specification for NixOS section layout --- agent/src/collectors/nixos.rs | 93 ++++++++++++++---------------- dashboard/src/ui/widgets/system.rs | 21 +++---- 2 files changed, 51 insertions(+), 63 deletions(-) diff --git a/agent/src/collectors/nixos.rs b/agent/src/collectors/nixos.rs index 452415d..26e4fa7 100644 --- a/agent/src/collectors/nixos.rs +++ b/agent/src/collectors/nixos.rs @@ -20,45 +20,42 @@ impl NixOSCollector { Self { config } } - /// Get NixOS version information - fn get_nixos_version(&self) -> Result<(String, Option), Box> { + /// Get NixOS build information (short hash and timestamp) + fn get_nixos_build_info(&self) -> Result<(String, String), Box> { // Try nixos-version command first if let Ok(output) = Command::new("nixos-version").output() { if output.status.success() { let version_line = String::from_utf8_lossy(&output.stdout); - let version = version_line.trim().to_string(); + let version = version_line.trim(); - // Extract build date if present (format: "24.05.20241023.abcdef (Vicuna)") - let build_date = if version.contains('.') { - let parts: Vec<&str> = version.split('.').collect(); - if parts.len() >= 3 && parts[2].len() >= 8 { - Some(parts[2][..8].to_string()) // Extract YYYYMMDD - } else { - None + // Parse format: "24.05.20241023.abcdef (Vicuna)" + if let Some(parts) = version.split('.').collect::>().get(2) { + if parts.len() >= 14 { // 8 digits + 6 hex chars minimum + let date_part = &parts[..8]; // YYYYMMDD + let hash_part = &parts[8..]; // remaining is hash + + // Extract short hash (first 6 characters) + let short_hash = if hash_part.len() >= 6 { + &hash_part[..6] + } else { + hash_part + }; + + // Format date from YYYYMMDD to dd/mm/yy + if date_part.len() == 8 { + let year = &date_part[2..4]; // YY + let month = &date_part[4..6]; // MM + let day = &date_part[6..8]; // DD + let formatted_date = format!("{}/{}/{}", day, month, year); + + return Ok((short_hash.to_string(), formatted_date)); + } } - } else { - None - }; - - return Ok((version, build_date)); - } - } - - // Fallback to /etc/os-release - if let Ok(content) = std::fs::read_to_string("/etc/os-release") { - for line in content.lines() { - if line.starts_with("VERSION_ID=") { - let version = line - .strip_prefix("VERSION_ID=") - .unwrap_or("") - .trim_matches('"') - .to_string(); - return Ok((version, None)); } } } - Err("Could not determine NixOS version".into()) + Err("Could not parse NixOS build information".into()) } /// Get currently active users @@ -95,36 +92,34 @@ impl Collector for NixOSCollector { let mut metrics = Vec::new(); let timestamp = chrono::Utc::now().timestamp() as u64; - // Collect NixOS version information - match self.get_nixos_version() { - Ok((version, build_date)) => { + // Collect NixOS build information + match self.get_nixos_build_info() { + Ok((short_hash, formatted_date)) => { + // Create combined build string: "hash dd/mm/yy H:M:S" + // For now, use current time for H:M:S (could be enhanced to get actual build time) + let now = chrono::Local::now(); + let build_string = format!("{} {} {}", + short_hash, + formatted_date, + now.format("%H:%M:%S") + ); + metrics.push(Metric { - name: "system_nixos_version".to_string(), - value: MetricValue::String(version), + name: "system_nixos_build".to_string(), + value: MetricValue::String(build_string), unit: None, - description: Some("NixOS version".to_string()), + description: Some("NixOS build information".to_string()), status: Status::Ok, timestamp, }); - - if let Some(date) = build_date { - metrics.push(Metric { - name: "system_nixos_build_date".to_string(), - value: MetricValue::String(date), - unit: None, - description: Some("NixOS build date".to_string()), - status: Status::Ok, - timestamp, - }); - } } Err(e) => { - debug!("Failed to get NixOS version: {}", e); + debug!("Failed to get NixOS build info: {}", e); metrics.push(Metric { - name: "system_nixos_version".to_string(), + name: "system_nixos_build".to_string(), value: MetricValue::String("unknown".to_string()), unit: None, - description: Some("NixOS version (failed to detect)".to_string()), + description: Some("NixOS build (failed to detect)".to_string()), status: Status::Unknown, timestamp, }); diff --git a/dashboard/src/ui/widgets/system.rs b/dashboard/src/ui/widgets/system.rs index 3e1e7ba..de48b76 100644 --- a/dashboard/src/ui/widgets/system.rs +++ b/dashboard/src/ui/widgets/system.rs @@ -14,8 +14,7 @@ use crate::ui::theme::{StatusIcons, Typography}; #[derive(Clone)] pub struct SystemWidget { // NixOS information - nixos_version: Option, - nixos_build_date: Option, + nixos_build: Option, active_users: Option, // CPU metrics @@ -63,8 +62,7 @@ struct StorageDrive { impl SystemWidget { pub fn new() -> Self { Self { - nixos_version: None, - nixos_build_date: None, + nixos_build: None, active_users: None, cpu_load_1min: None, cpu_load_5min: None, @@ -301,14 +299,9 @@ impl Widget for SystemWidget { for metric in metrics { match metric.name.as_str() { // NixOS metrics - "system_nixos_version" => { - if let MetricValue::String(version) = &metric.value { - self.nixos_version = Some(version.clone()); - } - } - "system_nixos_build_date" => { - if let MetricValue::String(date) = &metric.value { - self.nixos_build_date = Some(date.clone()); + "system_nixos_build" => { + if let MetricValue::String(build) = &metric.value { + self.nixos_build = Some(build.clone()); } } "system_active_users" => { @@ -390,9 +383,9 @@ impl Widget for SystemWidget { Span::styled("NixOS:", Typography::widget_title()) ])); - let version_text = self.nixos_version.as_deref().unwrap_or("unknown"); + let build_text = self.nixos_build.as_deref().unwrap_or("unknown"); lines.push(Line::from(vec![ - Span::styled(format!("Version: {}", version_text), Typography::secondary()) + Span::styled(format!("Build: {}", build_text), Typography::secondary()) ])); let users_text = self.active_users.as_deref().unwrap_or("unknown");