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 = "Show job messages for each job")]
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 match &job.tool {
85 Some(tool) => println!(" Tool: {}", tool.yellow()),
86 None => println!(" Tool: {}", "N/A".dimmed()),
87 }
88
89 match &job.job_stage {
91 Some(stage) => {
92 let stage_icon = get_stage_icon(stage);
93 println!(" Status: {} {}", stage_icon, stage.bold());
94 }
95 None => println!(" Status: {}", "Unknown".dimmed()),
96 }
97
98 if job.failed {
100 println!(" Failed: {} YES", "✗".red().bold());
101 } else {
102 println!(" Failed: {} No", "✓".green());
103 }
104
105 match &job.date_submitted {
107 Some(date) => println!(" Submitted: {}", format_timestamp(date)),
108 None => println!(" Submitted: {}", "N/A".dimmed()),
109 }
110
111 match &job.date_completed {
113 Some(date) => println!(" Completed: {}", format_timestamp(date).green()),
114 None => println!(" Completed: {}", "Not completed".dimmed()),
115 }
116
117 if self.detailed {
119 match client.get_job_status(&job.url) {
120 Ok(status) => {
121 if !status.messages.is_empty() {
122 println!(" Messages:");
123 for msg in &status.messages {
124 println!(" [{}] {}", msg.stage.cyan(), truncate(&msg.text, 80));
125 }
126 }
127 }
128 Err(_) => {
129 println!(" Messages: {} (failed to fetch)", "?".yellow());
130 }
131 }
132 }
133
134 println!(" URL: {}", job.url.dimmed());
135 println!("{}", "=".repeat(80));
136 }
137
138 println!();
139 println!("{}", "Commands:".bold());
140 println!(" Check job status: {}", "nsg status <JOB_ID>".cyan());
141 println!(" Download results: {}", "nsg download <JOB_ID>".cyan());
142
143 if showing_jobs < total_jobs {
144 println!();
145 println!("{}", "Tip:".bold());
146 println!(" Use {} to see all {} jobs", "--all".cyan(), total_jobs);
147 println!(" Use {} to see job messages", "--detailed".cyan());
148 println!(" Use {} to limit results", "--limit N".cyan());
149 println!(" Use {} to show N most recent jobs", "--recent N".cyan());
150 }
151 println!();
152
153 Ok(())
154 }
155}
156
157fn get_stage_icon(stage: &str) -> String {
158 match stage {
159 "COMPLETED" => "✓".green().bold().to_string(),
160 "RUNNING" | "RUN" => "⟳".yellow().bold().to_string(),
161 "QUEUE" | "SUBMITTED" => "⏳".cyan().to_string(),
162 "FAILED" => "✗".red().bold().to_string(),
163 _ => "?".dimmed().to_string(),
164 }
165}
166
167fn format_timestamp(ts: &str) -> String {
168 use chrono::{DateTime, Utc};
169 if let Ok(dt) = ts.parse::<DateTime<Utc>>() {
170 dt.format("%Y-%m-%d %H:%M:%S UTC").to_string()
171 } else {
172 ts.to_string()
173 }
174}
175
176fn truncate(s: &str, max_len: usize) -> String {
177 if s.len() <= max_len {
178 s.to_string()
179 } else {
180 format!("{}...", &s[..max_len])
181 }
182}