use std::net::TcpStream;
use std::time::Duration;
use colored::Colorize;
#[derive(Debug, Clone)]
pub struct ClusterInfo {
pub name: &'static str,
pub uri: String,
pub available: bool,
pub version: Option<String>,
pub nodes: Option<u32>,
}
#[derive(Debug, Clone)]
pub struct NetworkInfo {
pub local_ip: Option<String>,
pub tailscale_ip: Option<String>,
pub hostname: String,
pub mode: NetworkMode,
}
#[derive(Debug, Clone, Copy)]
pub enum NetworkMode {
Tailscale,
Local,
Unknown,
}
impl NetworkMode {
pub fn as_str(&self) -> &'static str {
match self {
NetworkMode::Tailscale => "Tailscale P2P",
NetworkMode::Local => "Local",
NetworkMode::Unknown => "Unknown",
}
}
}
pub fn detect_clusters() -> Vec<ClusterInfo> {
let mut clusters = Vec::new();
clusters.push(detect_ray());
clusters.push(detect_dask());
clusters.push(detect_spark());
clusters
}
fn detect_ray() -> ClusterInfo {
let default_uri = "ray://127.0.0.1:10001";
let (available, version, nodes) = if let Ok(resp) = ureq::get("http://127.0.0.1:8265/api/cluster_status")
.timeout(Duration::from_secs(2))
.call()
{
if let Ok(body) = resp.into_string() {
if let Ok(json) = serde_json::from_str::<serde_json::Value>(&body) {
let nodes = json["result"]["data"]["clusterStatus"]["loadMetricsReport"]["numNodes"]
.as_u64()
.map(|n| n as u32);
(true, Some("2.x".to_string()), nodes)
} else {
(false, None, None)
}
} else {
(false, None, None)
}
} else {
(false, None, None)
};
ClusterInfo {
name: "Ray",
uri: default_uri.to_string(),
available,
version,
nodes,
}
}
fn detect_dask() -> ClusterInfo {
let default_uri = "dask://127.0.0.1:8786";
let (available, version, nodes) = if let Ok(resp) = ureq::get("http://127.0.0.1:8787/info/main/workers.html")
.timeout(Duration::from_secs(2))
.call()
{
if resp.status() == 200 {
(true, Some("distributed".to_string()), None)
} else {
(false, None, None)
}
} else {
(false, None, None)
};
ClusterInfo {
name: "Dask",
uri: default_uri.to_string(),
available,
version,
nodes,
}
}
fn detect_spark() -> ClusterInfo {
let default_uri = "spark://127.0.0.1:7077";
let (available, version, nodes) = if let Ok(resp) = ureq::get("http://127.0.0.1:8080/json/")
.timeout(Duration::from_secs(2))
.call()
{
if let Ok(body) = resp.into_string() {
if let Ok(json) = serde_json::from_str::<serde_json::Value>(&body) {
let workers = json["aliveworkers"].as_u64().map(|n| n as u32);
(true, Some("3.x".to_string()), workers)
} else {
(false, None, None)
}
} else {
(false, None, None)
}
} else {
(false, None, None)
};
ClusterInfo {
name: "Spark",
uri: default_uri.to_string(),
available,
version,
nodes,
}
}
fn check_port(host: &str, port: u16) -> bool {
TcpStream::connect_timeout(
&format!("{}:{}", host, port).parse().unwrap(),
Duration::from_secs(1),
)
.is_ok()
}
pub fn get_network_info() -> NetworkInfo {
let hostname = hostname::get()
.map(|h| h.to_string_lossy().to_string())
.unwrap_or_else(|_| "unknown".to_string());
let local_ip = get_local_ip();
let tailscale_ip = get_tailscale_ip();
let mode = if tailscale_ip.is_some() {
NetworkMode::Tailscale
} else if local_ip.is_some() {
NetworkMode::Local
} else {
NetworkMode::Unknown
};
NetworkInfo {
local_ip,
tailscale_ip,
hostname,
mode,
}
}
fn get_local_ip() -> Option<String> {
if let Ok(output) = std::process::Command::new("hostname")
.arg("-I")
.output()
{
if output.status.success() {
let ips = String::from_utf8_lossy(&output.stdout);
return ips.split_whitespace().next().map(|s| s.to_string());
}
}
if let Ok(socket) = std::net::UdpSocket::bind("0.0.0.0:0") {
if socket.connect("8.8.8.8:80").is_ok() {
if let Ok(addr) = socket.local_addr() {
return Some(addr.ip().to_string());
}
}
}
None
}
fn get_tailscale_ip() -> Option<String> {
if let Ok(output) = std::process::Command::new("ip")
.args(["addr", "show", "tailscale0"])
.output()
{
if output.status.success() {
let output_str = String::from_utf8_lossy(&output.stdout);
for line in output_str.lines() {
if line.contains("inet ") && !line.contains("inet6") {
if let Some(inet_part) = line.split("inet ").nth(1) {
if let Some(ip) = inet_part.split('/').next() {
return Some(ip.trim().to_string());
}
}
}
}
}
}
if let Ok(output) = std::process::Command::new("tailscale")
.args(["ip", "-4"])
.output()
{
if output.status.success() {
let ip = String::from_utf8_lossy(&output.stdout).trim().to_string();
if !ip.is_empty() {
return Some(ip);
}
}
}
None
}
pub fn print_info() {
println!();
println!(" {}", "╔═══════════════════════════════════════════╗".cyan());
println!(" {} {} {}", "║".cyan(), "Zakuro System Info".bold().white(), "║".cyan());
println!(" {}", "╚═══════════════════════════════════════════╝".cyan());
println!();
let network = get_network_info();
println!(" {}", "Network".bold());
println!(" {}", "─".repeat(50));
println!(" Hostname: {}", network.hostname);
println!(" Mode: {}", match network.mode {
NetworkMode::Tailscale => network.mode.as_str().green(),
NetworkMode::Local => network.mode.as_str().yellow(),
NetworkMode::Unknown => network.mode.as_str().red(),
});
if let Some(ip) = &network.local_ip {
println!(" Local IP: {}", ip);
}
if let Some(ip) = &network.tailscale_ip {
println!(" Tailscale IP: {}", ip.cyan());
}
println!();
let clusters = detect_clusters();
println!(" {}", "Compute Clusters".bold());
println!(" {}", "─".repeat(50));
for cluster in &clusters {
let status = if cluster.available {
"●".green()
} else {
"○".red()
};
let version_str = cluster.version.as_deref().unwrap_or("-");
let nodes_str = cluster.nodes.map(|n| format!("{} nodes", n)).unwrap_or("-".to_string());
println!(
" {} {:8} {:20} {} {}",
status,
cluster.name,
cluster.uri.dimmed(),
version_str,
if cluster.available { nodes_str } else { "not running".dimmed().to_string() }
);
}
println!();
println!(" {}", "Configuration".bold());
println!(" {}", "─".repeat(50));
let zakuro_auth = std::env::var("ZAKURO_API_KEY").ok();
let api_url = std::env::var("ZAKURO_API_URL")
.unwrap_or_else(|_| "https://my.zakuro-ai.com".to_string());
let tailscale_key = std::env::var("TAILSCALE_AUTHKEY").ok();
println!(" API URL: {}", api_url.cyan());
println!(" ZAKURO_API_KEY: {}",
if zakuro_auth.is_some() { "✓ set".green() } else { "✗ not set".red() });
println!(" TAILSCALE_AUTHKEY: {}",
if tailscale_key.is_some() { "✓ set".green() } else { "○ optional".dimmed() });
println!();
println!(" {}", "Services".bold());
println!(" {}", "─".repeat(50));
let services = [
("Production Broker", "my.zakuro-ai.com", 443_u16, true), ("Local Broker", "127.0.0.1", 9000, false),
("Local Worker", "127.0.0.1", 3960, false),
];
for (name, host, port, is_https) in services {
let available = if is_https {
true
} else {
check_port(host, port)
};
let status = if available { "●".green() } else { "○".dimmed() };
let port_display = if is_https { "https".to_string() } else { format!(":{}", port) };
println!(" {} {:20} {}{}", status, name, host.dimmed(), port_display.dimmed());
}
println!();
let mesh_ports: Vec<u16> = (9001..=9009)
.filter(|&p| check_port("127.0.0.1", p))
.collect();
if !mesh_ports.is_empty() {
println!(" {}", "Mesh Nodes".bold());
println!(" {}", "─".repeat(50));
for port in mesh_ports {
let url = format!("http://127.0.0.1:{}/health", port);
match ureq::get(&url).timeout(Duration::from_millis(500)).call() {
Ok(resp) => {
if let Ok(body) = resp.into_string() {
if let Ok(v) = serde_json::from_str::<serde_json::Value>(&body) {
let node_name = v["node_name"].as_str().unwrap_or("unknown");
let ts_ip = v["tailscale_ip"].as_str();
let ts_connected = v["tailscale_connected"].as_bool().unwrap_or(false);
let ts_status = if ts_connected {
format!("Tailscale {}", ts_ip.unwrap_or("")).green().to_string()
} else {
"no Tailscale".red().to_string()
};
println!(
" {} {:20} :{} {}",
"●".green(),
node_name,
port,
ts_status
);
continue;
}
}
println!(" {} :{}", "●".green(), port);
}
Err(_) => {
println!(" {} :{}", "○".dimmed(), port);
}
}
}
println!();
}
}