use owo_colors::OwoColorize;
use crate::client::{ApiClient, PrometheusData, PrometheusResponse};
use crate::config::PartiriConfig;
use crate::error::Result;
use crate::output::{colored_job_status, sparkline};
fn extract_values(data: &PrometheusData) -> Vec<f64> {
data.result
.first()
.map(|r| {
r.values
.iter()
.filter_map(|(_, v)| v.parse::<f64>().ok())
.collect()
})
.unwrap_or_default()
}
fn format_bytes_rate(bytes: f64) -> String {
if bytes >= 1_048_576.0 {
format!("{:.1} MB/s", bytes / 1_048_576.0)
} else if bytes >= 1024.0 {
format!("{:.0} KB/s", bytes / 1024.0)
} else {
format!("{:.0} B/s", bytes)
}
}
fn print_sparkline(label: &str, resp: &PrometheusResponse, fmt: fn(f64) -> String) {
print!(" {} ", label.bold());
let vals = extract_values(&resp.data);
if vals.is_empty() {
println!("{}", "no data".dimmed());
} else {
let last = *vals.last().unwrap();
let trimmed: Vec<f64> = vals.into_iter().rev().take(60).rev().collect();
println!("{} {}", sparkline(&trimmed).cyan(), fmt(last).dimmed());
}
}
fn format_bytes(bytes: f64) -> String {
if bytes >= 1_073_741_824.0 {
format!("{:.1} GB", bytes / 1_073_741_824.0)
} else if bytes >= 1_048_576.0 {
format!("{:.0} MB", bytes / 1_048_576.0)
} else if bytes >= 1024.0 {
format!("{:.0} KB", bytes / 1024.0)
} else {
format!("{:.0} B", bytes)
}
}
pub fn run(client: &ApiClient, config: &PartiriConfig) -> Result<()> {
let id = config.id_or_err()?;
let service = client.read_service(id)?;
let deploy_tag = config.deploy_tag.as_deref();
let cpu_resp = client.read_metrics_cpu(id, deploy_tag);
let mem_resp = client.read_metrics_memory(id, deploy_tag);
let net_resp = client.read_metrics_network(id, deploy_tag);
let jobs_resp = client.list_service_jobs(id);
println!(
"\n {} {}\n",
service.name.bold(),
format!("({})", service.deploy_type).dimmed(),
);
match cpu_resp {
Ok(r) => print_sparkline("CPU", &r, |v| format!("{:.3} cores", v)),
Err(_) => println!(" {} {}", "CPU".bold(), "unavailable".dimmed()),
}
match mem_resp {
Ok(r) => print_sparkline("RAM", &r, format_bytes),
Err(_) => println!(" {} {}", "RAM".bold(), "unavailable".dimmed()),
}
match net_resp {
Ok(r) => {
print_sparkline("NET↓", &r.download, format_bytes_rate);
print_sparkline("NET↑", &r.upload, format_bytes_rate);
}
Err(_) => println!(" {} {}", "NET".bold(), "unavailable".dimmed()),
}
println!();
println!(" {}", "Recent jobs".bold());
match jobs_resp {
Ok(jobs) => {
let recent: Vec<_> = jobs.iter().take(5).collect();
if recent.is_empty() {
println!(" {}", "No jobs found.".dimmed());
} else {
for job in recent {
let ts = job
.created_at
.as_deref()
.filter(|s| s.len() >= 16 && s.as_bytes().get(10) == Some(&b'T'))
.map(|s| format!("{} {}", &s[..10], &s[11..16]))
.unwrap_or_else(|| "—".to_string());
let ref_str = job
.deploy_ref
.as_deref()
.map(|r| format!(" {}", r.get(..7).unwrap_or(r).dimmed()))
.unwrap_or_default();
println!(
" {} {}{} {}",
ts.dimmed(),
job.job_type.bold(),
ref_str,
colored_job_status(&job.status),
);
}
}
}
Err(_) => println!(" {}", "unavailable".dimmed()),
}
println!();
Ok(())
}