use crate::config::ConfigLoader;
use crate::core::engine::CoreEngine;
use crate::models::{HealthStatus, ServiceType};
use anyhow::Result;
use colored::*;
use std::env;
use tabled::{Table, Tabled, settings::Style};
pub struct CliInterface;
impl CliInterface {
pub fn new() -> Self {
Self
}
pub async fn show_status(&self, engine: &mut CoreEngine, _all: bool, format: &str) -> Result<()> {
engine.refresh().await?;
let registry = engine.get_registry();
let services = registry.all_services();
if format == "json" {
let json = serde_json::to_string_pretty(&services)?;
println!("{}", json);
return Ok(());
}
let project_name = engine
.project_path()
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("unknown");
println!("{}", format!("Project: {}", project_name).bold());
println!();
if services.is_empty() {
println!("{}", "No services detected.".yellow());
println!("Try running {} to create a configuration.", "darpan init".cyan());
return Ok(());
}
#[derive(Tabled)]
struct ServiceRow {
#[tabled(rename = "STATUS")]
status: String,
#[tabled(rename = "NAME")]
name: String,
#[tabled(rename = "TYPE")]
service_type: String,
#[tabled(rename = "HOST:PORT")]
location: String,
#[tabled(rename = "RESPONSE")]
response: String,
}
let mut rows = Vec::new();
for service in &services {
let health = service.health_status.as_ref();
let status_symbol = health
.map(|h| h.status.symbol())
.unwrap_or("?");
let status_str = match health.map(|h| &h.status) {
Some(HealthStatus::Healthy) => format!("{} Healthy", status_symbol).green().to_string(),
Some(HealthStatus::Degraded) => format!("{} Degraded", status_symbol).yellow().to_string(),
Some(HealthStatus::Unhealthy) => format!("{} Unhealthy", status_symbol).red().to_string(),
Some(HealthStatus::NotRunning) => format!("{} Down", status_symbol).red().to_string(),
_ => format!("{} Unknown", status_symbol).normal().to_string(),
};
let service_type_str = match &service.service_type {
ServiceType::HttpServer => "HTTP Server",
ServiceType::Database(_) => "Database",
ServiceType::MessageQueue(_) => "Queue",
ServiceType::Cache(_) => "Cache",
ServiceType::Search(_) => "Search",
ServiceType::DockerContainer => "Docker",
ServiceType::Custom => "Custom",
};
let location = format!("{}:{}", service.host, service.port);
let response = health
.map(|h| {
if h.response_time_ms > 0 {
format!("{}ms", h.response_time_ms)
} else {
"N/A".to_string()
}
})
.unwrap_or_else(|| "N/A".to_string());
rows.push(ServiceRow {
status: status_str,
name: service.name.clone(),
service_type: service_type_str.to_string(),
location,
response,
});
}
let table = Table::new(rows).with(Style::rounded()).to_string();
println!("{}", table);
let healthy_count = services.iter().filter(|s| s.is_healthy()).count();
let total_count = services.len();
println!();
if healthy_count == total_count {
println!(
"{}",
format!("All {} services are healthy ✓", total_count)
.green()
.bold()
);
} else {
println!(
"{}",
format!(
"{}/{} services healthy. {} need attention!",
healthy_count,
total_count,
total_count - healthy_count
)
.yellow()
.bold()
);
}
Ok(())
}
pub async fn show_why(&self, engine: &mut CoreEngine, service_name: &str) -> Result<()> {
engine.refresh().await?;
let registry = engine.get_registry();
let service = registry.get_service_by_name(service_name);
if let Some(service) = service {
println!("{}", format!("Service: {}", service.name).bold().cyan());
println!("{}", "─".repeat(60));
if let Some(health) = &service.health_status {
let status_str = match health.status {
HealthStatus::Healthy => "HEALTHY ✓".green(),
HealthStatus::Degraded => "DEGRADED ⚠".yellow(),
HealthStatus::Unhealthy => "UNHEALTHY ✗".red(),
HealthStatus::NotRunning => "DOWN ✗".red(),
HealthStatus::Unknown => "UNKNOWN ?".normal(),
};
println!("{}: {}", "Status".bold(), status_str);
if let Some(details) = &health.details {
println!("{}: {}", "Reason".bold(), details.yellow());
}
println!("{}: {}:{}", "Location".bold(), service.host, service.port);
if let Some(pid) = service.pid {
println!("{}: {}", "Process ID".bold(), pid);
}
if let Some(cmd) = &service.command_line {
println!("{}: {}", "Command".bold(), cmd);
}
if let Some(suggestion) = &health.suggestion {
println!();
println!("{}: {}", "Suggestion".bold().green(), suggestion.cyan());
}
println!();
println!(
"{}: {}",
"Last checked".bold(),
health.last_checked.format("%Y-%m-%d %H:%M:%S")
);
} else {
println!("{}", "No health information available".yellow());
}
} else {
println!(
"{}",
format!("Service '{}' not found", service_name).red()
);
println!("Run {} to see all services", "darpan status".cyan());
}
Ok(())
}
pub async fn init_config(&self) -> Result<()> {
let current_dir = env::current_dir()?;
match ConfigLoader::create_default_project_config(¤t_dir) {
Ok(_) => {
println!("{}", "✓ Created .darpan.yml".green().bold());
println!();
println!("Edit this file to configure your services.");
println!("Then run {} to see them.", "darpan status".cyan());
}
Err(e) => {
println!("{}", format!("Error: {}", e).red());
}
}
Ok(())
}
pub async fn add_service(
&self,
service_type: &str,
port: Option<u16>,
name: Option<String>,
) -> Result<()> {
let current_dir = env::current_dir()?;
let config_path = current_dir.join(".darpan.yml");
if !config_path.exists() {
println!(
"{}",
".darpan.yml not found. Run 'darpan init' first.".red()
);
return Ok(());
}
println!(
"{}",
format!(
"Adding {} service{}...",
service_type,
port.map(|p| format!(" on port {}", p)).unwrap_or_default()
)
.cyan()
);
println!();
println!("To add a service, edit .darpan.yml and add:");
println!();
println!(" - name: {}", name.unwrap_or_else(|| "My Service".to_string()));
println!(" type: {}", service_type);
if let Some(p) = port {
println!(" port: {}", p);
}
println!(" health_check:");
println!(" type: port");
Ok(())
}
}