1use crate::client::NsgClient;
2use crate::config::Credentials;
3use anyhow::Result;
4use clap::Args;
5use colored::Colorize;
6
7#[derive(Debug, Args)]
8pub struct ListCommand {
9 #[arg(long, help = "Fetch detailed status for each job (slower)")]
10 detailed: bool,
11
12 #[arg(short, long, help = "Limit number of jobs to display")]
13 limit: Option<usize>,
14
15 #[arg(
16 long,
17 default_value = "20",
18 help = "Show only the N most recent jobs (default: 20, use --recent 0 to show all)"
19 )]
20 recent: usize,
21
22 #[arg(long, help = "Show all jobs (override default limit)")]
23 all: bool,
24}
25
26impl ListCommand {
27 pub fn execute(self) -> Result<()> {
28 let credentials = Credentials::load()?;
29 let client = NsgClient::new(credentials.clone())?;
30
31 println!("{}", "NSG Job List".bold().cyan());
32 println!("{}", "=".repeat(80).cyan());
33 println!();
34 println!(
35 "{} Fetching jobs for user: {}",
36 "→".cyan(),
37 credentials.username.bold()
38 );
39 println!();
40
41 let mut jobs = client.list_jobs()?;
42
43 if jobs.is_empty() {
44 println!("{}", "No jobs found".yellow());
45 println!();
46 println!("You can submit a test job with:");
47 println!(" {}", "nsg submit <zip_file> --tool PY_EXPANSE".cyan());
48 return Ok(());
49 }
50
51 let total_jobs = jobs.len();
52
53 if self.all {
55 } else if let Some(limit) = self.limit {
57 jobs.truncate(limit);
59 } else if self.recent > 0 && jobs.len() > self.recent {
60 jobs.drain(0..jobs.len() - self.recent);
62 }
63
64 let showing_jobs = jobs.len();
65
66 if showing_jobs < total_jobs {
67 println!(
68 "Found {} job(s) total, showing {}",
69 total_jobs.to_string().bold(),
70 showing_jobs.to_string().bold()
71 );
72 } else {
73 println!("Found {} job(s)", jobs.len().to_string().bold());
74 }
75 println!();
76 println!("{}", "=".repeat(80));
77
78 for (i, job) in jobs.iter().enumerate() {
79 println!();
80 println!("Job #{}", (i + 1).to_string().bold());
81 println!(" ID: {}", job.job_id.cyan());
82
83 if self.detailed {
84 println!(" {}", "Fetching details...".dimmed());
85 match client.get_job_status(&job.url) {
86 Ok(status) => {
87 let stage_icon = get_stage_icon(&status.job_stage);
88 println!(" Status: {} {}", stage_icon, status.job_stage.bold());
89
90 if status.failed {
91 println!(" Failed: {} YES", "✗".red().bold());
92 }
93
94 if let Some(date) = &status.date_submitted {
95 println!(" Submitted: {}", format_timestamp(date));
96 }
97
98 if !status.messages.is_empty() {
99 if let Some(latest) = status.messages.last() {
100 println!(
101 " Latest: [{}] {}",
102 latest.stage,
103 truncate(&latest.text, 100)
104 );
105 }
106 }
107 }
108 Err(_) => {
109 println!(" Status: {} (failed to fetch)", "?".yellow());
110 }
111 }
112 } else {
113 println!(
114 " Status: {} (use --detailed for full status)",
115 "?".dimmed()
116 );
117 }
118
119 println!(" URL: {}", job.url.dimmed());
120 println!("{}", "=".repeat(80));
121 }
122
123 println!();
124 println!("{}", "Commands:".bold());
125 println!(" Check job status: {}", "nsg status <JOB_ID>".cyan());
126 println!(" Download results: {}", "nsg download <JOB_ID>".cyan());
127
128 if showing_jobs < total_jobs {
129 println!();
130 println!("{}", "Tip:".bold());
131 println!(" Use {} to see all {} jobs", "--all".cyan(), total_jobs);
132 println!(" Use {} to see detailed status", "--detailed".cyan());
133 println!(" Use {} to limit results", "--limit N".cyan());
134 println!(" Use {} to show N most recent jobs", "--recent N".cyan());
135 }
136 println!();
137
138 Ok(())
139 }
140}
141
142fn get_stage_icon(stage: &str) -> String {
143 match stage {
144 "COMPLETED" => "✓".green().bold().to_string(),
145 "RUNNING" | "RUN" => "⟳".yellow().bold().to_string(),
146 "QUEUE" | "SUBMITTED" => "⏳".cyan().to_string(),
147 "FAILED" => "✗".red().bold().to_string(),
148 _ => "?".dimmed().to_string(),
149 }
150}
151
152fn format_timestamp(ts: &str) -> String {
153 use chrono::{DateTime, Utc};
154 if let Ok(dt) = ts.parse::<DateTime<Utc>>() {
155 dt.format("%Y-%m-%d %H:%M:%S UTC").to_string()
156 } else {
157 ts.to_string()
158 }
159}
160
161fn truncate(s: &str, max_len: usize) -> String {
162 if s.len() <= max_len {
163 s.to_string()
164 } else {
165 format!("{}...", &s[..max_len])
166 }
167}