Skip to main content

htb_cli/cli/
pwnbox.rs

1use clap::Subcommand;
2
3use crate::api::HtbClient;
4use crate::output::{self, OutputFormat};
5
6#[derive(Subcommand)]
7pub enum PwnboxCommand {
8    /// Show PwnBox usage quota
9    Usage,
10    /// Show active PwnBox status
11    Status,
12}
13
14pub async fn handle(
15    client: &HtbClient,
16    cmd: PwnboxCommand,
17    format: OutputFormat,
18) -> anyhow::Result<()> {
19    match cmd {
20        PwnboxCommand::Usage => {
21            let usage: PwnboxUsage = client.get("/api/v4/pwnbox/usage").await?;
22            let fields = vec![
23                ("Remaining", format!("{} min", usage.remaining)),
24                ("Used", format!("{} min", usage.used)),
25                ("Allowed", format!("{} min", usage.allowed)),
26                ("Sessions", usage.sessions.to_string()),
27            ];
28            output::print_detail(&usage, format, &fields);
29        }
30        PwnboxCommand::Status => {
31            let result: Result<PwnboxStatus, _> = client.get("/api/v4/pwnbox/status").await;
32            match result {
33                Ok(status) if status.hostname.is_some() => {
34                    let fields = vec![
35                        ("Hostname", status.hostname.clone().unwrap_or_default()),
36                        ("IP", status.ip.clone().unwrap_or_else(|| "-".into())),
37                        ("Region", status.region.clone().unwrap_or_default()),
38                        ("Lab", status.lab.clone().unwrap_or_default()),
39                        ("Expires", status.expires_at.clone().unwrap_or_default()),
40                    ];
41                    output::print_detail(&status, format, &fields);
42                }
43                _ => {
44                    output::print_message("No active PwnBox instance.");
45                }
46            }
47        }
48    }
49    Ok(())
50}
51
52#[derive(Debug, serde::Deserialize, serde::Serialize)]
53struct PwnboxUsage {
54    #[serde(default)]
55    remaining: u32,
56    #[serde(default)]
57    used: u32,
58    #[serde(default)]
59    allowed: u32,
60    #[serde(default)]
61    sessions: u32,
62}
63
64#[derive(Debug, serde::Deserialize, serde::Serialize)]
65struct PwnboxStatus {
66    #[serde(default)]
67    hostname: Option<String>,
68    #[serde(default)]
69    ip: Option<String>,
70    #[serde(default)]
71    region: Option<String>,
72    #[serde(default)]
73    lab: Option<String>,
74    #[serde(default)]
75    expires_at: Option<String>,
76}