Implement unified system widget with NixOS info, CPU, RAM, and Storage
- Create NixOS collector for version and active users detection - Add SystemWidget combining all system information in TODO.md layout - Replace separate CPU/Memory widgets with unified system display - Add tree structure for storage with drive temperature/wear info - Support NixOS version, active users, load averages, memory usage - Follow exact decimal formatting from specification
This commit is contained in:
@@ -7,6 +7,7 @@ pub mod cpu;
|
||||
pub mod disk;
|
||||
pub mod error;
|
||||
pub mod memory;
|
||||
pub mod nixos;
|
||||
pub mod systemd;
|
||||
|
||||
pub use error::CollectorError;
|
||||
|
||||
163
agent/src/collectors/nixos.rs
Normal file
163
agent/src/collectors/nixos.rs
Normal file
@@ -0,0 +1,163 @@
|
||||
use async_trait::async_trait;
|
||||
use cm_dashboard_shared::{Metric, MetricValue, Status, StatusTracker};
|
||||
use std::process::Command;
|
||||
use tracing::debug;
|
||||
|
||||
use super::{Collector, CollectorError};
|
||||
use crate::config::NixOSConfig;
|
||||
|
||||
/// NixOS system information collector
|
||||
///
|
||||
/// Collects NixOS-specific system information including:
|
||||
/// - NixOS version and build information
|
||||
/// - Currently active/logged in users
|
||||
pub struct NixOSCollector {
|
||||
config: NixOSConfig,
|
||||
}
|
||||
|
||||
impl NixOSCollector {
|
||||
pub fn new(config: NixOSConfig) -> Self {
|
||||
Self { config }
|
||||
}
|
||||
|
||||
/// Get NixOS version information
|
||||
fn get_nixos_version(&self) -> Result<(String, Option<String>), Box<dyn std::error::Error>> {
|
||||
// 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();
|
||||
|
||||
// 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
|
||||
}
|
||||
} 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())
|
||||
}
|
||||
|
||||
/// Get currently active users
|
||||
fn get_active_users(&self) -> Result<Vec<String>, Box<dyn std::error::Error>> {
|
||||
let output = Command::new("who").output()?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err("who command failed".into());
|
||||
}
|
||||
|
||||
let who_output = String::from_utf8_lossy(&output.stdout);
|
||||
let mut users = std::collections::HashSet::new();
|
||||
|
||||
for line in who_output.lines() {
|
||||
if let Some(username) = line.split_whitespace().next() {
|
||||
if !username.is_empty() {
|
||||
users.insert(username.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(users.into_iter().collect())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Collector for NixOSCollector {
|
||||
fn name(&self) -> &str {
|
||||
"nixos"
|
||||
}
|
||||
|
||||
async fn collect(&self, _status_tracker: &mut StatusTracker) -> Result<Vec<Metric>, CollectorError> {
|
||||
debug!("Collecting NixOS system information");
|
||||
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)) => {
|
||||
metrics.push(Metric {
|
||||
name: "system_nixos_version".to_string(),
|
||||
value: MetricValue::String(version),
|
||||
unit: None,
|
||||
description: Some("NixOS version".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);
|
||||
metrics.push(Metric {
|
||||
name: "system_nixos_version".to_string(),
|
||||
value: MetricValue::String("unknown".to_string()),
|
||||
unit: None,
|
||||
description: Some("NixOS version (failed to detect)".to_string()),
|
||||
status: Status::Unknown,
|
||||
timestamp,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Collect active users
|
||||
match self.get_active_users() {
|
||||
Ok(users) => {
|
||||
let users_str = users.join(", ");
|
||||
metrics.push(Metric {
|
||||
name: "system_active_users".to_string(),
|
||||
value: MetricValue::String(users_str),
|
||||
unit: None,
|
||||
description: Some("Currently active users".to_string()),
|
||||
status: Status::Ok,
|
||||
timestamp,
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("Failed to get active users: {}", e);
|
||||
metrics.push(Metric {
|
||||
name: "system_active_users".to_string(),
|
||||
value: MetricValue::String("unknown".to_string()),
|
||||
unit: None,
|
||||
description: Some("Active users (failed to detect)".to_string()),
|
||||
status: Status::Unknown,
|
||||
timestamp,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
debug!("Collected {} NixOS metrics", metrics.len());
|
||||
Ok(metrics)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user