use anyhow::{bail, Result};
use trusty_common::monitor::dashboard::{IndexRow, SearchData};
use trusty_common::monitor::search_client::{resolve_search_url, SearchClient};
fn fmt_count(n: u64) -> String {
let s = n.to_string();
let mut out = String::with_capacity(s.len() + s.len() / 3);
for (i, c) in s.chars().rev().enumerate() {
if i > 0 && i % 3 == 0 {
out.push(',');
}
out.push(c);
}
out.chars().rev().collect()
}
async fn fetch_search_data() -> Result<SearchData> {
let url = resolve_search_url();
let client = SearchClient::new(url.clone());
client
.fetch_all()
.await
.map_err(|e| anyhow::anyhow!("could not reach trusty-search daemon at {url}: {e}"))
}
pub async fn handle_status(json: bool) -> Result<()> {
let data = fetch_search_data().await?;
let total_chunks: u64 = data.indexes.iter().map(|i| i.chunk_count).sum();
if json {
println!(
"{}",
serde_json::json!({
"status": "online",
"version": data.version,
"uptime_secs": data.uptime_secs,
"index_count": data.indexes.len(),
"total_chunks": total_chunks,
})
);
} else {
println!("trusty-search v{}", data.version);
println!("status: online");
println!(
"uptime: {}",
trusty_common::monitor::dashboard::format_uptime(data.uptime_secs)
);
println!("indexes: {}", data.indexes.len());
println!("total chunks: {}", fmt_count(total_chunks));
}
Ok(())
}
pub async fn handle_indexes(id: Option<String>, json: bool) -> Result<()> {
let data = fetch_search_data().await?;
match id {
Some(id) => print_index_detail(&data.indexes, &id, json),
None => {
print_index_table(&data.indexes, json);
Ok(())
}
}
}
fn print_index_table(indexes: &[IndexRow], json: bool) {
if json {
let arr: Vec<serde_json::Value> = indexes
.iter()
.map(|i| {
serde_json::json!({
"name": i.id,
"chunks": i.chunk_count,
"path": i.root_path,
})
})
.collect();
println!("{}", serde_json::Value::Array(arr));
return;
}
if indexes.is_empty() {
println!("(no indexes registered)");
return;
}
let name_w = indexes
.iter()
.map(|i| i.id.len())
.max()
.unwrap_or(0)
.max(12);
let chunk_w = indexes
.iter()
.map(|i| fmt_count(i.chunk_count).len())
.max()
.unwrap_or(0)
.max(6);
println!("{:<name_w$} {:>chunk_w$} PATH", "NAME", "CHUNKS");
for i in indexes {
println!(
"{:<name_w$} {:>chunk_w$} {}",
i.id,
fmt_count(i.chunk_count),
i.root_path,
);
}
}
fn print_index_detail(indexes: &[IndexRow], id: &str, json: bool) -> Result<()> {
let Some(row) = indexes.iter().find(|i| i.id == id) else {
bail!("no index named '{id}' is registered");
};
if json {
println!(
"{}",
serde_json::json!({
"name": row.id,
"chunks": row.chunk_count,
"path": row.root_path,
})
);
} else {
println!("name: {}", row.id);
println!("chunks: {}", fmt_count(row.chunk_count));
println!("path: {}", row.root_path);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fmt_count_groups_thousands() {
assert_eq!(fmt_count(0), "0");
assert_eq!(fmt_count(7), "7");
assert_eq!(fmt_count(999), "999");
assert_eq!(fmt_count(1_200), "1,200");
assert_eq!(fmt_count(18_994), "18,994");
assert_eq!(fmt_count(1_000_000), "1,000,000");
}
#[test]
fn print_index_detail_errors_on_unknown_id() {
let rows = vec![IndexRow {
id: "known".into(),
chunk_count: 10,
root_path: "/tmp/known".into(),
}];
assert!(print_index_detail(&rows, "missing", false).is_err());
assert!(print_index_detail(&rows, "known", true).is_ok());
}
}