darpan 0.2.5

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::ProjectConfig;
use crate::detectors::DetectorTrait;
use crate::models::{DetectionSource, Service};
use crate::utils::ServiceSignatures;
use anyhow::Result;
use async_trait::async_trait;
use std::net::{TcpStream, SocketAddr};
use std::path::Path;
use std::time::Duration;
use tracing::{debug, trace};

pub struct PortDetector {
    start_port: u16,
    end_port: u16,
}

impl PortDetector {
    pub fn new(start_port: u16, end_port: u16) -> Self {
        Self {
            start_port,
            end_port,
        }
    }

    fn is_port_open(&self, port: u16) -> bool {
        let addr = SocketAddr::from(([127, 0, 0, 1], port));
        
        match TcpStream::connect_timeout(&addr, Duration::from_millis(100)) {
            Ok(_) => true,
            Err(_) => false,
        }
    }
}

#[async_trait]
impl DetectorTrait for PortDetector {
    fn name(&self) -> &str {
        "PortDetector"
    }

    fn priority(&self) -> i32 {
        30
    }

    async fn detect(&self, project_path: &Path, _config: Option<&ProjectConfig>) -> Result<Vec<Service>> {
        let mut services = Vec::new();
        
        debug!("Scanning ports {} to {}", self.start_port, self.end_port);
        
        for port in self.start_port..=self.end_port {
            if self.is_port_open(port) {
                trace!("Port {} is open", port);
                
                // Try to match against known service signatures
                if let Some(signature) = ServiceSignatures::find_by_port(port) {
                    let role = signature.role;
                    let mut service = Service::new_with_role(
                        signature.name.to_string(),
                        signature.service_type.clone(),
                        "localhost".to_string(),
                        port,
                        DetectionSource::PortScan,
                        project_path.to_string_lossy().to_string(),
                        role,
                    );
                    
                    service.tags.push("auto-detected".to_string());
                    service.systemd_unit = signature.systemd_unit.map(|s| s.to_string());
                    services.push(service);
                    debug!("Detected {} on port {} (role: {:?})", signature.name, port, role);
                } else {
                    // Unknown service on this port - infer role
                    let service_type = crate::models::ServiceType::Custom;
                    let role = ServiceSignatures::infer_role(port, None, &service_type);
                    let service = Service::new_with_role(
                        format!("Unknown Service (Port {})", port),
                        service_type,
                        "localhost".to_string(),
                        port,
                        DetectionSource::PortScan,
                        project_path.to_string_lossy().to_string(),
                        role,
                    );
                    
                    services.push(service);
                    debug!("Detected unknown service on port {} (inferred role: {:?})", port, role);
                }
            }
        }
        
        Ok(services)
    }
}