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)"
This commit is contained in:
Christoffer Martinsson 2025-10-14 19:23:26 +02:00
parent 1ee398e648
commit c6e8749ddd
2 changed files with 38 additions and 5 deletions

View File

@ -124,6 +124,32 @@ impl SystemCollector {
Ok((used_mb, total_mb))
}
async fn get_logged_in_users(&self) -> Option<Vec<String>> {
// 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<Vec<String>> {
// 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);

View File

@ -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,