use std::io::{self, Write};
use crate::paths::jobs_root;
use crossterm::{cursor, event, execute, style, terminal, ExecutableCommand};
pub(crate) fn run_tui() -> io::Result<()> {
let mut stdout = io::stdout();
terminal::enable_raw_mode()?;
execute!(stdout, terminal::EnterAlternateScreen, cursor::Hide)?;
let res = (|| -> io::Result<()> {
loop {
while event::poll(std::time::Duration::from_millis(100))? {
if let event::Event::Key(key) = event::read()? {
if key.code == event::KeyCode::Char('q') || key.code == event::KeyCode::Esc {
return Ok(());
}
}
}
let root = jobs_root()?;
let mut jobs: Vec<(String, String)> = Vec::new();
if let Ok(entries) = std::fs::read_dir(&root) {
for entry in entries.flatten() {
if let Some(name) = entry.file_name().to_str() {
if let Some((job, ext)) = name.rsplit_once('.') {
if matches!(ext, "out" | "err" | "log" | "exit" | "json" | "signal" | "lock") {
jobs.push((job.to_string(), ext.to_string()));
}
}
}
}
}
jobs.sort_by(|a, b| a.0.cmp(&b.0));
let mut unique: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
for (job, _) in jobs {
unique.insert(job);
}
let mut y = 0;
stdout.execute(cursor::MoveTo(0, 0))?;
stdout.execute(terminal::Clear(terminal::ClearType::All))?;
writeln!(stdout, "press 'q' to quit\n")?;
y += 2;
use chrono::{DateTime, Local};
for job in unique {
let exit_path = root.join(format!("{job}.exit"));
if exit_path.exists() {
let code = std::fs::read_to_string(&exit_path)?.trim().to_string();
let ts = std::fs::metadata(&exit_path)
.and_then(|m| m.modified())
.map(|m| {
let dt: DateTime<Local> = m.into();
dt.format("%Y-%m-%d %H:%M:%S").to_string()
})
.unwrap_or_else(|_| "?".into());
stdout.execute(cursor::MoveTo(0, y))?;
stdout.execute(style::Print(format!(
"{:<20} {:<8} {}",
job, format!("exit {code}"), ts
)))?;
} else {
stdout.execute(cursor::MoveTo(0, y))?;
stdout.execute(style::Print(format!("{:<20} running", job)))?;
}
y += 1;
}
stdout.flush()?;
}
})();
execute!(stdout, cursor::Show, terminal::LeaveAlternateScreen)?;
terminal::disable_raw_mode()?;
res
}