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}