feat: ls only lists running jobs, prune removes all running jobs
All checks were successful
CI / Check, test, lint (push) Successful in 31s
All checks were successful
CI / Check, test, lint (push) Successful in 31s
This commit is contained in:
14
SPEC.md
14
SPEC.md
@@ -66,8 +66,10 @@ Useful for commands that need no local files (e.g. `p -n -- htop`).
|
||||
```
|
||||
p ls
|
||||
```
|
||||
List jobs across all workers. Shows: ID (short), worker, original CWD,
|
||||
command, status, duration. Style inspired by `docker ps` / `lxc list`.
|
||||
List **running jobs** across all workers. Pass `-a` / `--all` to also show
|
||||
completed jobs (done, failed, stopped).
|
||||
Shows: ID (short), worker, original CWD, command, status, duration.
|
||||
Style inspired by `docker ps` / `lxc list`.
|
||||
|
||||
```
|
||||
p attach <job-id>
|
||||
@@ -100,6 +102,14 @@ p rm <job-id>
|
||||
Remove a job record and its remote work directory. Refuses to remove a
|
||||
running job without `--force`.
|
||||
|
||||
```
|
||||
p prune
|
||||
```
|
||||
Remove all finished job records (status: done, failed, stopped) and their
|
||||
remote work directories. Jobs with status `running` or `unknown` are left
|
||||
untouched. Pass `--force` to also include `unknown` jobs.
|
||||
Pass `--dry-run` to preview what would be removed without deleting anything.
|
||||
|
||||
### Worker management
|
||||
|
||||
```
|
||||
|
||||
18
p/src/cli.rs
18
p/src/cli.rs
@@ -23,8 +23,12 @@ pub struct Cli {
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub enum Command {
|
||||
/// List all jobs across all workers
|
||||
Ls,
|
||||
/// List running jobs (pass --all to include finished jobs)
|
||||
Ls {
|
||||
/// Show all jobs, not just running ones
|
||||
#[arg(short, long)]
|
||||
all: bool,
|
||||
},
|
||||
|
||||
/// Re-attach to the tmux session of a running job
|
||||
Attach {
|
||||
@@ -66,6 +70,16 @@ pub enum Command {
|
||||
force: bool,
|
||||
},
|
||||
|
||||
/// Remove all finished job records and their remote work directories
|
||||
Prune {
|
||||
/// Also remove jobs with unknown status (worker was unreachable)
|
||||
#[arg(short, long)]
|
||||
force: bool,
|
||||
/// Preview what would be removed without deleting anything
|
||||
#[arg(short = 'n', long)]
|
||||
dry_run: bool,
|
||||
},
|
||||
|
||||
/// Manage registered workers
|
||||
Worker {
|
||||
#[command(subcommand)]
|
||||
|
||||
@@ -7,12 +7,21 @@ use crate::{
|
||||
ssh,
|
||||
};
|
||||
|
||||
pub fn execute() -> Result<()> {
|
||||
pub fn execute(all: bool) -> Result<()> {
|
||||
let cfg = config::load()?;
|
||||
let mut jobs = db::list()?;
|
||||
|
||||
// By default only show running jobs; --all includes finished ones.
|
||||
if !all {
|
||||
jobs.retain(|j| j.status == JobStatus::Running || j.status == JobStatus::Unknown);
|
||||
}
|
||||
|
||||
if jobs.is_empty() {
|
||||
println!("No jobs yet. Run 'p -- <command>' to start one.");
|
||||
if all {
|
||||
println!("No jobs yet. Run 'p -- <command>' to start one.");
|
||||
} else {
|
||||
println!("No running jobs. Use 'p ls --all' to see finished jobs.");
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
pub mod attach;
|
||||
pub mod logs;
|
||||
pub mod ls;
|
||||
pub mod prune;
|
||||
pub mod pull;
|
||||
pub mod rm;
|
||||
pub mod run;
|
||||
|
||||
79
p/src/commands/prune.rs
Normal file
79
p/src/commands/prune.rs
Normal file
@@ -0,0 +1,79 @@
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::{config, db, job::JobStatus, ssh};
|
||||
|
||||
pub fn execute(force: bool, dry_run: bool) -> Result<()> {
|
||||
let cfg = config::load()?;
|
||||
let jobs = db::list()?;
|
||||
|
||||
let to_prune: Vec<_> = jobs
|
||||
.into_iter()
|
||||
.filter(|j| match j.status {
|
||||
JobStatus::Done | JobStatus::Failed | JobStatus::Stopped => true,
|
||||
JobStatus::Unknown => force,
|
||||
JobStatus::Running => false,
|
||||
})
|
||||
.collect();
|
||||
|
||||
if to_prune.is_empty() {
|
||||
println!("Nothing to prune.");
|
||||
if !force {
|
||||
println!("(use --force to also include jobs with unknown status)");
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if dry_run {
|
||||
println!("Would remove {} job(s):", to_prune.len());
|
||||
} else {
|
||||
println!("Removing {} job(s)...", to_prune.len());
|
||||
}
|
||||
|
||||
let mut removed = 0;
|
||||
let mut errors = 0;
|
||||
|
||||
for job in &to_prune {
|
||||
let remote_rm = format!(
|
||||
"rm -rf {}/.p/jobs/{id} {}/.p/workdirs/{id}",
|
||||
// use the worker's home via SSH expansion
|
||||
"~",
|
||||
"~",
|
||||
id = job.id
|
||||
);
|
||||
|
||||
println!(
|
||||
" {} {} ({})",
|
||||
job.short_id(),
|
||||
job.status_display(),
|
||||
job.command_display(40)
|
||||
);
|
||||
|
||||
if dry_run {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Best-effort remote cleanup.
|
||||
if let Some(worker) = cfg.get_worker(&job.worker) {
|
||||
if let Err(e) = ssh::run_capture(worker, &remote_rm) {
|
||||
eprintln!(" warning: could not remove remote files: {}", e);
|
||||
errors += 1;
|
||||
}
|
||||
}
|
||||
|
||||
db::delete(&job.id)?;
|
||||
removed += 1;
|
||||
}
|
||||
|
||||
if !dry_run {
|
||||
if errors > 0 {
|
||||
eprintln!(
|
||||
"Removed {} local record(s); {} remote cleanup(s) failed.",
|
||||
removed, errors
|
||||
);
|
||||
} else {
|
||||
println!("Done. Removed {} job(s).", removed);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -53,7 +53,7 @@ fn main() -> Result<()> {
|
||||
// All other subcommands go through clap.
|
||||
let cli = cli::Cli::parse();
|
||||
match cli.command {
|
||||
cli::Command::Ls => commands::ls::execute(),
|
||||
cli::Command::Ls { all } => commands::ls::execute(all),
|
||||
cli::Command::Attach { job_id } => commands::attach::execute(&job_id),
|
||||
cli::Command::Logs { job_id, follow } => commands::logs::execute(&job_id, follow),
|
||||
cli::Command::Stop { job_id } => commands::stop::execute(&job_id),
|
||||
@@ -63,6 +63,7 @@ fn main() -> Result<()> {
|
||||
local_dest,
|
||||
} => commands::pull::execute(&job_id, &remote_path, local_dest.as_deref()),
|
||||
cli::Command::Rm { job_id, force } => commands::rm::execute(&job_id, force),
|
||||
cli::Command::Prune { force, dry_run } => commands::prune::execute(force, dry_run),
|
||||
cli::Command::Worker { command } => match command {
|
||||
cli::WorkerCommand::Ls { check } => commands::worker::ls::execute(check),
|
||||
cli::WorkerCommand::Register { connection, name } => {
|
||||
|
||||
Reference in New Issue
Block a user