use super::helpers::*;
use crate::core::types;
use std::path::Path;
pub(crate) fn cmd_status_fleet_overview(state_dir: &Path, json: bool) -> Result<(), String> {
let machines = discover_machines(state_dir);
let mut total_resources = 0usize;
let mut total_converged = 0usize;
let mut total_failed = 0usize;
let mut total_drifted = 0usize;
let mut machine_count = 0usize;
for m in &machines {
let lock_path = state_dir.join(m).join("state.lock.yaml");
if !lock_path.exists() {
continue;
}
let content = std::fs::read_to_string(&lock_path).unwrap_or_default();
if let Ok(lock) = serde_yaml_ng::from_str::<crate::core::types::StateLock>(&content) {
machine_count += 1;
for (_rname, rlock) in &lock.resources {
total_resources += 1;
match rlock.status {
crate::core::types::ResourceStatus::Converged => total_converged += 1,
crate::core::types::ResourceStatus::Failed => total_failed += 1,
crate::core::types::ResourceStatus::Drifted => total_drifted += 1,
_ => {}
}
}
}
}
if json {
println!(
r#"{{"fleet":{{"machines":{machine_count},"resources":{total_resources},"converged":{total_converged},"failed":{total_failed},"drifted":{total_drifted}}}}}"#
);
} else {
println!("Fleet overview:");
println!(" Machines: {machine_count}");
println!(
" Resources: {total_resources} (converged: {total_converged}, failed: {total_failed}, drifted: {total_drifted})"
);
if total_resources > 0 {
let health =
(total_converged as f64 / total_resources as f64 * 100.0).clamp(0.0, 100.0);
println!(" Fleet health: {health:.0}%");
}
}
Ok(())
}
fn tally_machine_lock(lock: &crate::core::types::StateLock) -> (usize, usize, usize, usize) {
let mut converged = 0usize;
let mut failed = 0usize;
let mut drifted = 0usize;
for rlock in lock.resources.values() {
match rlock.status {
crate::core::types::ResourceStatus::Converged => converged += 1,
crate::core::types::ResourceStatus::Failed => failed += 1,
crate::core::types::ResourceStatus::Drifted => drifted += 1,
_ => {}
}
}
(lock.resources.len(), converged, failed, drifted)
}
fn collect_machine_health_reports(
state_dir: &Path,
machines: &[String],
machine: Option<&str>,
) -> Vec<(String, usize, usize, usize, usize)> {
let mut reports = Vec::new();
for m in machines {
if let Some(filter) = machine {
if m != filter {
continue;
}
}
let lock_path = state_dir.join(m).join("state.lock.yaml");
if !lock_path.exists() {
reports.push((m.clone(), 0, 0, 0, 0));
continue;
}
let content = std::fs::read_to_string(&lock_path).unwrap_or_default();
if let Ok(lock) = serde_yaml_ng::from_str::<crate::core::types::StateLock>(&content) {
let (total, converged, failed, drifted) = tally_machine_lock(&lock);
reports.push((m.clone(), total, converged, failed, drifted));
}
}
reports
}
fn machine_health_pct(total: usize, converged: usize) -> f64 {
if total > 0 {
(converged as f64 / total as f64 * 100.0).clamp(0.0, 100.0)
} else {
100.0
}
}
pub(crate) fn cmd_status_machine_health(
state_dir: &Path,
machine: Option<&str>,
json: bool,
) -> Result<(), String> {
let machines = discover_machines(state_dir);
let reports = collect_machine_health_reports(state_dir, &machines, machine);
if json {
let items: Vec<String> = reports.iter()
.map(|(m, t, c, f, d)| {
let health = machine_health_pct(*t, *c);
format!(r#"{{"machine":"{m}","total":{t},"converged":{c},"failed":{f},"drifted":{d},"health":{health:.0}}}"#)
})
.collect();
println!(r#"{{"machine_health":[{}]}}"#, items.join(","));
} else {
println!("Machine health:");
for (m, total, converged, failed, drifted) in &reports {
let health = machine_health_pct(*total, *converged);
println!(
" {m} — {health:.0}% ({total} resources: {converged} converged, {failed} failed, {drifted} drifted)"
);
}
}
Ok(())
}
pub(crate) fn cmd_status_machine_summary(
state_dir: &Path,
machine: Option<&str>,
json: bool,
) -> Result<(), String> {
let machines = discover_machines(state_dir);
let targets: Vec<&String> = if let Some(m) = machine {
machines.iter().filter(|x| x.as_str() == m).collect()
} else {
machines.iter().collect()
};
if json {
print!("{{\"machines\":[");
}
let mut first = true;
for m in &targets {
let lock_path = state_dir.join(m).join("state.lock.yaml");
if !lock_path.exists() {
continue;
}
let content = std::fs::read_to_string(&lock_path).unwrap_or_default();
let lock: crate::core::types::StateLock = match serde_yaml_ng::from_str(&content) {
Ok(l) => l,
Err(_) => continue,
};
let total = lock.resources.len();
let converged = lock
.resources
.values()
.filter(|r| matches!(r.status, crate::core::types::ResourceStatus::Converged))
.count();
let failed = lock
.resources
.values()
.filter(|r| matches!(r.status, crate::core::types::ResourceStatus::Failed))
.count();
let drifted = lock
.resources
.values()
.filter(|r| matches!(r.status, crate::core::types::ResourceStatus::Drifted))
.count();
if json {
if !first {
print!(",");
}
first = false;
print!(
r#"{{"machine":"{m}","total":{total},"converged":{converged},"failed":{failed},"drifted":{drifted}}}"#
);
} else {
let health = if failed > 0 {
"UNHEALTHY"
} else if drifted > 0 {
"DRIFTED"
} else {
"HEALTHY"
};
println!(
"{m}: {total} resources ({converged} converged, {failed} failed, {drifted} drifted) [{health}]"
);
}
}
if json {
println!("]}}");
}
Ok(())
}
pub(crate) fn cmd_status_executive_summary(state_dir: &Path, json: bool) -> Result<(), String> {
let machines = discover_machines(state_dir);
let mut summaries: Vec<(String, usize, usize, usize, usize)> = Vec::new();
for m in &machines {
let lock_path = state_dir.join(m).join("state.lock.yaml");
if !lock_path.exists() {
continue;
}
let content = std::fs::read_to_string(&lock_path).unwrap_or_default();
let lock: types::StateLock = match serde_yaml_ng::from_str(&content) {
Ok(l) => l,
Err(_) => continue,
};
let total = lock.resources.len();
let conv = lock
.resources
.values()
.filter(|r| r.status == types::ResourceStatus::Converged)
.count();
let fail = lock
.resources
.values()
.filter(|r| r.status == types::ResourceStatus::Failed)
.count();
let drift = lock
.resources
.values()
.filter(|r| r.status == types::ResourceStatus::Drifted)
.count();
summaries.push((m.clone(), total, conv, fail, drift));
}
if json {
let entries: Vec<String> = summaries
.iter()
.map(|(m, t, c, f, d)| {
format!(
r#"{{"machine":"{m}","total":{t},"converged":{c},"failed":{f},"drifted":{d}}}"#
)
})
.collect();
println!("[{}]", entries.join(","));
} else if summaries.is_empty() {
println!("No machine state found.");
} else {
for (m, total, conv, fail, drift) in &summaries {
let status = if *fail > 0 {
red("FAIL")
} else if *drift > 0 {
yellow("DRIFT")
} else {
green("OK")
};
println!(" [{status}] {m} — {conv}/{total} converged, {fail} failed, {drift} drifted");
}
}
Ok(())
}
pub(crate) fn cmd_status_pipeline_status(
state_dir: &Path,
machine: Option<&str>,
json: bool,
) -> Result<(), String> {
let machines = discover_machines(state_dir);
let mut statuses: Vec<(String, String, String)> = Vec::new();
for m in &machines {
if let Some(filter) = machine {
if m != filter {
continue;
}
}
let lock_path = state_dir.join(m).join("state.lock.yaml");
if !lock_path.exists() {
continue;
}
let content = std::fs::read_to_string(&lock_path).unwrap_or_default();
if let Ok(lock) = serde_yaml_ng::from_str::<crate::core::types::StateLock>(&content) {
let total = lock.resources.len();
let converged = lock
.resources
.values()
.filter(|r| r.status == crate::core::types::ResourceStatus::Converged)
.count();
let status = if converged == total {
"green"
} else {
"yellow"
};
statuses.push((m.clone(), lock.generated_at.clone(), status.to_string()));
}
}
if json {
let items: Vec<String> = statuses
.iter()
.map(|(m, ts, s)| {
format!(r#"{{"machine":"{m}","last_apply":"{ts}","pipeline":"{s}"}}"#)
})
.collect();
println!(
r#"{{"pipeline_statuses":[{}],"count":{}}}"#,
items.join(","),
statuses.len()
);
} else if statuses.is_empty() {
println!("No pipeline status available");
} else {
println!("Pipeline status ({} machines):", statuses.len());
for (m, ts, s) in &statuses {
println!(" {m} — {s} (last: {ts})");
}
}
Ok(())
}