proc_cli/commands/
info.rs

1//! Info command - Get detailed process information
2//!
3//! Usage:
4//!   proc info 1234       # Info for PID
5//!   proc info :3000      # Info for process on port 3000
6//!   proc info node       # Info for processes named node
7
8use crate::core::{resolve_target, Process, ProcessStatus};
9use crate::error::Result;
10use crate::ui::{OutputFormat, Printer};
11use clap::Args;
12use colored::*;
13use serde::Serialize;
14
15/// Show detailed process information
16#[derive(Args, Debug)]
17pub struct InfoCommand {
18    /// Target(s): PID, :port, or name
19    #[arg(required = true)]
20    targets: Vec<String>,
21
22    /// Output as JSON
23    #[arg(long, short)]
24    json: bool,
25
26    /// Show extra details
27    #[arg(long, short)]
28    verbose: bool,
29}
30
31impl InfoCommand {
32    /// Executes the info command, displaying detailed process information.
33    pub fn execute(&self) -> Result<()> {
34        let format = if self.json {
35            OutputFormat::Json
36        } else {
37            OutputFormat::Human
38        };
39        let printer = Printer::new(format, self.verbose);
40
41        let mut found = Vec::new();
42        let mut not_found = Vec::new();
43
44        for target in &self.targets {
45            match resolve_target(target) {
46                Ok(processes) => {
47                    if processes.is_empty() {
48                        not_found.push(target.clone());
49                    } else {
50                        found.extend(processes);
51                    }
52                }
53                Err(_) => not_found.push(target.clone()),
54            }
55        }
56
57        if self.json {
58            printer.print_json(&InfoOutput {
59                action: "info",
60                success: !found.is_empty(),
61                found_count: found.len(),
62                not_found_count: not_found.len(),
63                processes: &found,
64                not_found: &not_found,
65            });
66        } else {
67            for proc in &found {
68                self.print_process_info(proc);
69            }
70
71            if !not_found.is_empty() {
72                for target in &not_found {
73                    printer.warning(&format!("Target '{}' not found", target));
74                }
75            }
76        }
77
78        Ok(())
79    }
80
81    fn print_process_info(&self, proc: &Process) {
82        println!(
83            "{} Process {}",
84            "✓".green().bold(),
85            proc.pid.to_string().cyan().bold()
86        );
87        println!();
88        println!("  {} {}", "Name:".bright_black(), proc.name.white().bold());
89        println!(
90            "  {} {}",
91            "PID:".bright_black(),
92            proc.pid.to_string().cyan()
93        );
94
95        if let Some(ref path) = proc.exe_path {
96            println!("  {} {}", "Path:".bright_black(), path);
97        }
98
99        if let Some(ref user) = proc.user {
100            println!("  {} {}", "User:".bright_black(), user);
101        }
102
103        if let Some(ppid) = proc.parent_pid {
104            println!(
105                "  {} {}",
106                "Parent PID:".bright_black(),
107                ppid.to_string().cyan()
108            );
109        }
110
111        let status_str = format!("{:?}", proc.status);
112        let status_colored = match proc.status {
113            ProcessStatus::Running => status_str.green(),
114            ProcessStatus::Sleeping => status_str.blue(),
115            ProcessStatus::Stopped => status_str.yellow(),
116            ProcessStatus::Zombie => status_str.red(),
117            _ => status_str.white(),
118        };
119        println!("  {} {}", "Status:".bright_black(), status_colored);
120
121        println!("  {} {:.1}%", "CPU:".bright_black(), proc.cpu_percent);
122        println!("  {} {:.1} MB", "Memory:".bright_black(), proc.memory_mb);
123
124        if let Some(start_time) = proc.start_time {
125            let duration = std::time::SystemTime::now()
126                .duration_since(std::time::UNIX_EPOCH)
127                .map(|d| d.as_secs().saturating_sub(start_time))
128                .unwrap_or(0);
129
130            let uptime = format_duration(duration);
131            println!("  {} {}", "Uptime:".bright_black(), uptime);
132        }
133
134        if self.verbose {
135            if let Some(ref cmd) = proc.command {
136                println!("  {} {}", "Command:".bright_black(), cmd.bright_black());
137            }
138        }
139
140        println!();
141    }
142}
143
144fn format_duration(secs: u64) -> String {
145    if secs < 60 {
146        format!("{}s", secs)
147    } else if secs < 3600 {
148        format!("{}m {}s", secs / 60, secs % 60)
149    } else if secs < 86400 {
150        format!("{}h {}m", secs / 3600, (secs % 3600) / 60)
151    } else {
152        format!("{}d {}h", secs / 86400, (secs % 86400) / 3600)
153    }
154}
155
156#[derive(Serialize)]
157struct InfoOutput<'a> {
158    action: &'static str,
159    success: bool,
160    found_count: usize,
161    not_found_count: usize,
162    processes: &'a [Process],
163    not_found: &'a [String],
164}