1use anyhow::Result;
2use clap::Args;
3use colored::Colorize;
4use crate::client::NsgClient;
5use crate::config::Credentials;
6
7#[derive(Debug, Args)]
8pub struct ListCommand {
9 #[arg(long, help = "Fetch detailed status for each job (slower)")]
10 detailed: bool,
11}
12
13impl ListCommand {
14 pub fn execute(self) -> Result<()> {
15 let credentials = Credentials::load()?;
16 let client = NsgClient::new(credentials.clone())?;
17
18 println!("{}", "NSG Job List".bold().cyan());
19 println!("{}", "=".repeat(80).cyan());
20 println!();
21 println!("{} Fetching jobs for user: {}", "→".cyan(), credentials.username.bold());
22 println!();
23
24 let jobs = client.list_jobs()?;
25
26 if jobs.is_empty() {
27 println!("{}", "No jobs found".yellow());
28 println!();
29 println!("You can submit a test job with:");
30 println!(" {}", "nsg submit <zip_file> --tool PY_EXPANSE".cyan());
31 return Ok(());
32 }
33
34 println!("Found {} job(s)", jobs.len().to_string().bold());
35 println!();
36 println!("{}", "=".repeat(80));
37
38 for (i, job) in jobs.iter().enumerate() {
39 println!();
40 println!("Job #{}", (i + 1).to_string().bold());
41 println!(" ID: {}", job.job_id.cyan());
42
43 if self.detailed {
44 println!(" {}", "Fetching details...".dimmed());
45 match client.get_job_status(&job.url) {
46 Ok(status) => {
47 let stage_icon = get_stage_icon(&status.job_stage);
48 println!(" Status: {} {}", stage_icon, status.job_stage.bold());
49
50 if status.failed {
51 println!(" Failed: {} YES", "✗".red().bold());
52 }
53
54 if let Some(date) = &status.date_submitted {
55 println!(" Submitted: {}", format_timestamp(date));
56 }
57
58 if !status.messages.is_empty() {
59 if let Some(latest) = status.messages.last() {
60 println!(" Latest: [{}] {}", latest.stage, truncate(&latest.text, 100));
61 }
62 }
63 }
64 Err(_) => {
65 println!(" Status: {} (failed to fetch)", "?".yellow());
66 }
67 }
68 } else {
69 println!(" Status: {} (use --detailed for full status)", "?".dimmed());
70 }
71
72 println!(" URL: {}", job.url.dimmed());
73 println!("{}", "=".repeat(80));
74 }
75
76 println!();
77 println!("{}", "Commands:".bold());
78 println!(" Check job status: {}", "nsg status <JOB_ID>".cyan());
79 println!(" Download results: {}", "nsg download <JOB_ID>".cyan());
80 println!();
81
82 Ok(())
83 }
84}
85
86fn get_stage_icon(stage: &str) -> String {
87 match stage {
88 "COMPLETED" => "✓".green().bold().to_string(),
89 "RUNNING" | "RUN" => "⟳".yellow().bold().to_string(),
90 "QUEUE" | "SUBMITTED" => "⏳".cyan().to_string(),
91 "FAILED" => "✗".red().bold().to_string(),
92 _ => "?".dimmed().to_string(),
93 }
94}
95
96fn format_timestamp(ts: &str) -> String {
97 use chrono::{DateTime, Utc};
98 if let Ok(dt) = ts.parse::<DateTime<Utc>>() {
99 dt.format("%Y-%m-%d %H:%M:%S UTC").to_string()
100 } else {
101 ts.to_string()
102 }
103}
104
105fn truncate(s: &str, max_len: usize) -> String {
106 if s.len() <= max_len {
107 s.to_string()
108 } else {
109 format!("{}...", &s[..max_len])
110 }
111}