darpan 0.2.0

Linux developer service monitoring utility with auto-detection, real-time health checks, and interactive TUI for databases, APIs, Docker containers, and more
Documentation
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<()> {
        // Refresh services and health
        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(());
        }

        // Text output
        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);

        // Summary
        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(&current_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()
        );

        // This would require proper parsing and modification of YAML
        // For now, just provide instructions
        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(())
    }
}