214 lines
6.7 KiB
Rust
214 lines
6.7 KiB
Rust
//! status: detailed state of one agent, or of every installed agent.
|
|
|
|
use super::*;
|
|
use crate::output::{boxed_table, kv_table, print_json, status_style};
|
|
use crate::probe::ExternalInfo;
|
|
use crate::state::InstalledEntry;
|
|
use serde::Serialize;
|
|
|
|
#[derive(Serialize)]
|
|
struct StatusDetail {
|
|
name: String,
|
|
display_name: String,
|
|
status: String,
|
|
version: Option<String>,
|
|
method: Option<String>,
|
|
path: Option<String>,
|
|
run: Option<String>,
|
|
pid: Option<u32>,
|
|
started_at: Option<String>,
|
|
installed_at: Option<String>,
|
|
log: Option<String>,
|
|
managed: bool,
|
|
}
|
|
|
|
pub fn run(app: &App, agent_name: Option<&str>) -> Result<i32> {
|
|
let probe = crate::probe::detect_externals(app);
|
|
match agent_name {
|
|
Some(name) => status_one(app, name, &probe),
|
|
None => status_all(app, &probe),
|
|
}
|
|
}
|
|
|
|
fn status_one(app: &App, name: &str, probe: &ExternalInfo) -> Result<i32> {
|
|
let agent = require_agent(app, name)?;
|
|
let entry: Option<InstalledEntry> = app.state.get(&agent.name).ok().flatten();
|
|
let detail = detail_for(app, agent, entry.as_ref(), probe);
|
|
if app.json() {
|
|
print_json(&detail);
|
|
return Ok(0);
|
|
}
|
|
let color = app.color();
|
|
let theme = app.theme();
|
|
// Favorite star (issue #39).
|
|
let star = if crate::commands::annotations_cmd::is_favorite(app, &agent.name) {
|
|
"★ "
|
|
} else {
|
|
""
|
|
};
|
|
println!("{}{} ({})", star, agent.title(), agent.name);
|
|
let status_line = status_style(&detail.status, theme, color);
|
|
let mut rows: Vec<(String, String)> = vec![
|
|
("agent".to_string(), agent.name.clone()),
|
|
("status".to_string(), status_line),
|
|
(
|
|
"version".to_string(),
|
|
detail.version.clone().unwrap_or_else(|| "unknown".to_string()),
|
|
),
|
|
(
|
|
"method".to_string(),
|
|
detail.method.clone().unwrap_or_else(|| "—".to_string()),
|
|
),
|
|
(
|
|
"path".to_string(),
|
|
detail.path.clone().unwrap_or_else(|| "—".to_string()),
|
|
),
|
|
(
|
|
"run".to_string(),
|
|
detail.run.clone().unwrap_or_else(|| "—".to_string()),
|
|
),
|
|
];
|
|
if let Some(pid) = detail.pid {
|
|
rows.push(("pid".to_string(), pid.to_string()));
|
|
}
|
|
if let Some(t) = &detail.started_at {
|
|
rows.push(("started".to_string(), t.clone()));
|
|
}
|
|
if let Some(t) = &detail.installed_at {
|
|
rows.push(("installed".to_string(), t.clone()));
|
|
}
|
|
if let Some(l) = &detail.log {
|
|
rows.push(("log".to_string(), l.clone()));
|
|
}
|
|
print!("{}", kv_table(&rows, theme, color));
|
|
Ok(0)
|
|
}
|
|
|
|
fn status_all(app: &App, probe: &ExternalInfo) -> Result<i32> {
|
|
let entries = app.state.all().unwrap_or_default();
|
|
let mut rows: Vec<AgentRow> = Vec::new();
|
|
for agent in app.catalog.agents() {
|
|
if agent.hidden {
|
|
continue;
|
|
}
|
|
let entry = entries.get(&agent.name);
|
|
let d = detail_for(app, agent, entry, probe);
|
|
if d.status == "running" || d.status == "installed" || d.status == "external" {
|
|
rows.push(AgentRow {
|
|
name: agent.name.clone(),
|
|
display_name: agent.title().to_string(),
|
|
status: d.status,
|
|
version: d.version,
|
|
pid: d.pid,
|
|
path: d.path,
|
|
});
|
|
}
|
|
}
|
|
rows.sort_by(|a, b| a.name.cmp(&b.name));
|
|
if app.json() {
|
|
print_json(&rows);
|
|
return Ok(0);
|
|
}
|
|
let color = app.color();
|
|
let theme = app.theme();
|
|
let mut table_rows = Vec::new();
|
|
for r in &rows {
|
|
let star = if crate::commands::annotations_cmd::is_favorite(app, &r.name) {
|
|
"★ "
|
|
} else {
|
|
""
|
|
};
|
|
table_rows.push(vec![
|
|
format!("{star}{}", r.display_name),
|
|
r.version.clone().unwrap_or_else(|| "—".to_string()),
|
|
status_style(&r.status, theme, color),
|
|
r.pid.map(|p| p.to_string()).unwrap_or_else(|| "—".to_string()),
|
|
r.path.clone().unwrap_or_else(|| "—".to_string()),
|
|
]);
|
|
}
|
|
print!(
|
|
"{}",
|
|
boxed_table(
|
|
vec!["NAME", "VERSION", "STATUS", "PID", "PATH"],
|
|
table_rows,
|
|
theme,
|
|
color,
|
|
)
|
|
);
|
|
if rows.is_empty() {
|
|
app.log.info("no installed or running agents");
|
|
}
|
|
Ok(0)
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
struct AgentRow {
|
|
name: String,
|
|
display_name: String,
|
|
status: String,
|
|
version: Option<String>,
|
|
pid: Option<u32>,
|
|
path: Option<String>,
|
|
}
|
|
|
|
fn detail_for(
|
|
app: &App,
|
|
agent: &AgentDef,
|
|
entry: Option<&InstalledEntry>,
|
|
probe: &ExternalInfo,
|
|
) -> StatusDetail {
|
|
match entry {
|
|
Some(e) => {
|
|
let running = e.pid.map(crate::process::is_running).unwrap_or(false);
|
|
let status = if running { "running" } else { "installed" };
|
|
let path = e
|
|
.bins
|
|
.first()
|
|
.filter(|b| std::path::Path::new(b).exists())
|
|
.cloned()
|
|
.or_else(|| Some(e.install_dir.clone()));
|
|
StatusDetail {
|
|
name: agent.name.clone(),
|
|
display_name: agent.title().to_string(),
|
|
status: status.to_string(),
|
|
version: e.version.clone(),
|
|
method: Some(e.method.clone()),
|
|
path,
|
|
run: Some(e.run.clone()),
|
|
pid: if running { e.pid } else { None },
|
|
started_at: if running { e.started_at.clone() } else { None },
|
|
installed_at: Some(e.installed_at.clone()),
|
|
log: Some(crate::process::agent_log_path(app, &agent.name).display().to_string()),
|
|
managed: true,
|
|
}
|
|
}
|
|
None => {
|
|
let external = probe.path_of(&agent.name).cloned();
|
|
let version = external
|
|
.as_ref()
|
|
.and_then(|_p| probe.version_of(&agent.name).cloned().flatten())
|
|
.or_else(|| agent.version.clone());
|
|
StatusDetail {
|
|
name: agent.name.clone(),
|
|
display_name: agent.title().to_string(),
|
|
status: if external.is_some() {
|
|
"external".to_string()
|
|
} else if agent.installable {
|
|
"not-installed".to_string()
|
|
} else {
|
|
"not-installable".to_string()
|
|
},
|
|
version,
|
|
method: None,
|
|
path: external.map(|p| p.display().to_string()),
|
|
run: agent.run.clone(),
|
|
pid: None,
|
|
started_at: None,
|
|
installed_at: None,
|
|
log: None,
|
|
managed: false,
|
|
}
|
|
}
|
|
}
|
|
}
|