From c6e8749ddd0f68e4f599993c29a03b425ac268fb Mon Sep 17 00:00:00 2001 From: Christoffer Martinsson Date: Tue, 14 Oct 2025 19:23:26 +0200 Subject: [PATCH] Implement logged-in users monitoring and improve widget formatting Agent improvements: - Add get_logged_in_users() function to SystemCollector using 'who' command - Collect unique, sorted list of currently logged-in users - Include logged_in_users field in system metrics JSON output - Change C-state formatting to show 2 states per row instead of 4 Dashboard improvements: - Update Backups widget to show "Archives: XX, ..." format - System widget ready to display logged-in users with proper formatting The System widget will now show: - C-states formatted as 2 per row for better readability - Logged-in users displayed as "Logged in: user" or "Logged in: X users (user1, user2)" --- agent/src/collectors/system.rs | 41 ++++++++++++++++++++++++++++++---- dashboard/src/ui/backup.rs | 2 +- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/agent/src/collectors/system.rs b/agent/src/collectors/system.rs index dc74aa0..243e2a8 100644 --- a/agent/src/collectors/system.rs +++ b/agent/src/collectors/system.rs @@ -124,6 +124,32 @@ impl SystemCollector { Ok((used_mb, total_mb)) } + async fn get_logged_in_users(&self) -> Option> { + // Get currently logged-in users using 'who' command + let output = Command::new("who") + .output() + .await + .ok()?; + + let who_output = String::from_utf8_lossy(&output.stdout); + let mut users = Vec::new(); + + for line in who_output.lines() { + if let Some(username) = line.split_whitespace().next() { + if !username.is_empty() && !users.contains(&username.to_string()) { + users.push(username.to_string()); + } + } + } + + if users.is_empty() { + None + } else { + users.sort(); + Some(users) + } + } + async fn get_cpu_cstate_info(&self) -> Option> { // Read C-state information to show all sleep state distributions let mut cstate_times: Vec<(String, u64)> = Vec::new(); @@ -178,7 +204,7 @@ impl SystemCollector { order_a.cmp(&order_b) }); - // Format C-states as description lines (split into two rows for readability) + // Format C-states as description lines (2 C-states per row) let mut result = Vec::new(); let mut current_line = Vec::new(); @@ -187,15 +213,15 @@ impl SystemCollector { if percent >= 0.1 { // Only show states with at least 0.1% time current_line.push(format!("{}: {:.1}%", name, percent)); - // Split into two rows when we have 4 items - if current_line.len() == 4 { + // Split into rows when we have 2 items + if current_line.len() == 2 { result.push(current_line.join(", ")); current_line.clear(); } } } - // Add remaining items as second line + // Add remaining items as final line if !current_line.is_empty() { result.push(current_line.join(", ")); } @@ -272,6 +298,9 @@ impl Collector for SystemCollector { // Get C-state information (optional) let cpu_cstate_info = self.get_cpu_cstate_info().await; + + // Get logged-in users (optional) + let logged_in_users = self.get_logged_in_users().await; let mut system_metrics = json!({ "summary": { @@ -299,6 +328,10 @@ impl Collector for SystemCollector { system_metrics["summary"]["cpu_cstate"] = json!(cstates); } + if let Some(users) = logged_in_users { + system_metrics["summary"]["logged_in_users"] = json!(users); + } + debug!("System metrics collected: CPU load {:.2}, Memory {:.1}%", cpu_load_5, memory_usage_percent); diff --git a/dashboard/src/ui/backup.rs b/dashboard/src/ui/backup.rs index 6560452..9becb39 100644 --- a/dashboard/src/ui/backup.rs +++ b/dashboard/src/ui/backup.rs @@ -59,7 +59,7 @@ fn render_metrics(frame: &mut Frame, _host: &HostDisplayData, metrics: &BackupMe data.add_row( Some(WidgetStatus::new(latest_status)), - vec![format!("{} archives, {:.1}GB total", metrics.backup.snapshot_count, metrics.backup.size_gb)], + vec![format!("Archives: {}, {:.1}GB total", metrics.backup.snapshot_count, metrics.backup.size_gb)], vec![ "Latest".to_string(), latest_time,