use serde::{Deserialize, Serialize};
use std::fmt;
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PortEntry {
pub port: u16,
pub protocol: Protocol,
pub pid: u32,
pub process_name: String,
pub command: String,
pub cwd: Option<PathBuf>,
pub memory_mb: f64,
pub cpu_percent: f32,
pub uptime_secs: u64,
pub project: Option<ProjectInfo>,
pub docker: Option<DockerInfo>,
pub git: Option<GitInfo>,
pub tunnel: Option<TunnelInfo>,
pub status: Status,
pub health_check: Option<HealthResult>,
}
impl PortEntry {
pub fn uptime_display(&self) -> String {
let secs = self.uptime_secs;
if secs < 60 {
format!("{}s", secs)
} else if secs < 3600 {
format!("{}m {}s", secs / 60, secs % 60)
} else if secs < 86400 {
format!("{}h {}m", secs / 3600, (secs % 3600) / 60)
} else {
format!("{}d {}h", secs / 86400, (secs % 86400) / 3600)
}
}
pub fn project_display(&self) -> String {
match &self.project {
Some(p) => {
if p.framework.is_empty() {
p.kind.clone()
} else {
format!("{} ({})", p.framework, p.kind)
}
}
None => String::from("—"),
}
}
pub fn git_display(&self) -> String {
match &self.git {
Some(g) => {
let dirty = if g.dirty { "*" } else { "" };
format!("{}{}", g.branch, dirty)
}
None => String::from("—"),
}
}
pub fn docker_display(&self) -> String {
match &self.docker {
Some(d) => d.container_name.clone(),
None => String::from("—"),
}
}
pub fn tunnel_display(&self) -> String {
match &self.tunnel {
Some(t) => {
if let Some(url) = &t.public_url {
format!("{} → {}", t.kind, url)
} else {
t.kind.clone()
}
}
None => String::from("—"),
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum Protocol {
Tcp,
Udp,
}
impl fmt::Display for Protocol {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Protocol::Tcp => write!(f, "TCP"),
Protocol::Udp => write!(f, "UDP"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectInfo {
pub kind: String,
pub framework: String,
pub version: Option<String>,
pub detected_file: PathBuf,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DockerInfo {
pub container_name: String,
pub image: String,
pub compose_project: Option<String>,
pub container_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GitInfo {
pub branch: String,
pub dirty: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TunnelInfo {
pub kind: String,
pub public_url: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthResult {
pub status: HealthStatus,
pub status_code: Option<u16>,
pub latency_ms: u64,
pub endpoint: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum HealthStatus {
Healthy,
Unhealthy,
Unknown,
}
impl fmt::Display for HealthStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
HealthStatus::Healthy => write!(f, "✓ Healthy"),
HealthStatus::Unhealthy => write!(f, "✗ Unhealthy"),
HealthStatus::Unknown => write!(f, "? Unknown"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum Status {
Healthy,
Orphaned,
Zombie,
Warning(String),
Unknown,
}
impl fmt::Display for Status {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Status::Healthy => write!(f, "● Healthy"),
Status::Orphaned => write!(f, "◌ Orphaned"),
Status::Zombie => write!(f, "✗ Zombie"),
Status::Warning(msg) => write!(f, "⚠ {}", msg),
Status::Unknown => write!(f, "? Unknown"),
}
}
}
impl Status {
pub fn priority(&self) -> u8 {
match self {
Status::Zombie => 0,
Status::Orphaned => 1,
Status::Warning(_) => 2,
Status::Unknown => 3,
Status::Healthy => 4,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SortField {
Port,
Pid,
Process,
Project,
Memory,
Cpu,
Uptime,
Status,
}
impl SortField {
pub fn label(&self) -> &'static str {
match self {
SortField::Port => "Port",
SortField::Pid => "PID",
SortField::Process => "Process",
SortField::Project => "Project",
SortField::Memory => "Memory",
SortField::Cpu => "CPU",
SortField::Uptime => "Uptime",
SortField::Status => "Status",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SortDirection {
Ascending,
Descending,
}
impl SortDirection {
pub fn toggle(&self) -> Self {
match self {
SortDirection::Ascending => SortDirection::Descending,
SortDirection::Descending => SortDirection::Ascending,
}
}
pub fn indicator(&self) -> &'static str {
match self {
SortDirection::Ascending => "▲",
SortDirection::Descending => "▼",
}
}
}