Skip to main content

apm/cmd/
sessions.rs

1use anyhow::Result;
2use std::path::Path;
3
4#[derive(serde::Deserialize)]
5struct SessionInfo {
6    username: String,
7    device_hint: Option<String>,
8    last_seen: chrono::DateTime<chrono::Utc>,
9    expires_at: chrono::DateTime<chrono::Utc>,
10}
11
12pub fn run(root: &Path) -> Result<()> {
13    let config = apm_core::config::Config::load(root)?;
14    let url = format!("{}/api/auth/sessions", config.server.url);
15    let client = reqwest::blocking::Client::new();
16    let resp = client
17        .get(&url)
18        .send()
19        .map_err(|e| anyhow::anyhow!("error: cannot connect to apm-server: {e}"))?;
20    if !resp.status().is_success() {
21        let status = resp.status();
22        eprintln!("error: server returned {status}");
23        std::process::exit(1);
24    }
25    let sessions: Vec<SessionInfo> = resp
26        .json()
27        .map_err(|e| anyhow::anyhow!("error: invalid response from server: {e}"))?;
28    if sessions.is_empty() {
29        println!("No active sessions.");
30        return Ok(());
31    }
32    let col_user = sessions.iter().map(|s| s.username.len()).max().unwrap_or(0).max(8);
33    let col_device = sessions
34        .iter()
35        .map(|s| s.device_hint.as_deref().unwrap_or("-").len())
36        .max()
37        .unwrap_or(0)
38        .max(6);
39    println!(
40        "{:<col_user$}  {:<col_device$}  {:<20}  {}",
41        "USERNAME", "DEVICE", "LAST SEEN", "EXPIRES"
42    );
43    for s in &sessions {
44        let device = s.device_hint.as_deref().unwrap_or("-");
45        let last_seen = s.last_seen.format("%Y-%m-%d %H:%M UTC").to_string();
46        let expires = s.expires_at.format("%Y-%m-%d %H:%M UTC").to_string();
47        println!(
48            "{:<col_user$}  {:<col_device$}  {:<20}  {}",
49            s.username, device, last_seen, expires
50        );
51    }
52    Ok(())
53}