darpan 0.2.2

Linux developer service monitoring utility with auto-detection, real-time health checks, and interactive TUI for databases, APIs, Docker containers, and more
Documentation
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::time::Duration;

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum HealthStatus {
    /// All checks passed
    Healthy,
    /// Some checks passed, some warnings
    Degraded,
    /// Critical checks failed
    Unhealthy,
    /// Cannot determine status
    Unknown,
    /// Service process not found
    NotRunning,
}

impl HealthStatus {
    pub fn symbol(&self) -> &str {
        match self {
            HealthStatus::Healthy => "",
            HealthStatus::Degraded => "",
            HealthStatus::Unhealthy => "",
            HealthStatus::Unknown => "?",
            HealthStatus::NotRunning => "",
        }
    }

    pub fn color(&self) -> &str {
        match self {
            HealthStatus::Healthy => "green",
            HealthStatus::Degraded => "yellow",
            HealthStatus::Unhealthy => "red",
            HealthStatus::Unknown => "gray",
            HealthStatus::NotRunning => "red",
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthResult {
    pub status: HealthStatus,
    pub response_time_ms: u64,
    pub last_checked: DateTime<Utc>,
    pub details: Option<String>,
    pub suggestion: Option<String>,
}

impl HealthResult {
    pub fn healthy(response_time_ms: u64) -> Self {
        Self {
            status: HealthStatus::Healthy,
            response_time_ms,
            last_checked: Utc::now(),
            details: None,
            suggestion: None,
        }
    }

    pub fn unhealthy(reason: String, suggestion: Option<String>) -> Self {
        Self {
            status: HealthStatus::Unhealthy,
            response_time_ms: 0,
            last_checked: Utc::now(),
            details: Some(reason),
            suggestion,
        }
    }

    pub fn not_running(suggestion: Option<String>) -> Self {
        Self {
            status: HealthStatus::NotRunning,
            response_time_ms: 0,
            last_checked: Utc::now(),
            details: Some("Process not found".to_string()),
            suggestion,
        }
    }

    pub fn degraded(response_time_ms: u64, reason: String) -> Self {
        Self {
            status: HealthStatus::Degraded,
            response_time_ms,
            last_checked: Utc::now(),
            details: Some(reason),
            suggestion: None,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthCheckConfig {
    #[serde(flatten)]
    pub check_type: HealthCheckType,
    
    #[serde(default = "default_interval", skip_serializing_if = "is_default_interval")]
    pub interval: Duration,
    
    #[serde(default = "default_timeout", skip_serializing_if = "is_default_timeout")]
    pub timeout: Duration,
    
    #[serde(default = "default_retry_count", skip_serializing_if = "is_default_retry_count")]
    pub retry_count: u32,
}

fn default_interval() -> Duration {
    Duration::from_secs(30)
}

fn is_default_interval(d: &Duration) -> bool {
    *d == Duration::from_secs(30)
}

fn default_timeout() -> Duration {
    Duration::from_secs(3)
}

fn is_default_timeout(d: &Duration) -> bool {
    *d == Duration::from_secs(3)
}

fn default_retry_count() -> u32 {
    2
}

fn is_default_retry_count(c: &u32) -> bool {
    *c == 2
}

impl Default for HealthCheckConfig {
    fn default() -> Self {
        Self {
            check_type: HealthCheckType::Port,
            interval: Duration::from_secs(30),
            timeout: Duration::from_secs(3),
            retry_count: 2,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum HealthCheckType {
    Port,
    Process,
    Http {
        path: String,
        #[serde(skip_serializing_if = "Option::is_none")]
        expected_status: Option<u16>,
    },
    Postgres {
        #[serde(skip_serializing_if = "Option::is_none")]
        database: Option<String>,
    },
    Redis,
    #[serde(rename = "mysql")]
    MySQL {
        #[serde(skip_serializing_if = "Option::is_none")]
        database: Option<String>,
    },
    #[serde(rename = "mongodb")]
    MongoDB {
        #[serde(skip_serializing_if = "Option::is_none")]
        database: Option<String>,
    },
    Custom {
        script: String,
    },
}