1use clap::Subcommand;
2
3use crate::api::HtbClient;
4use crate::output::{self, OutputFormat};
5
6#[derive(Subcommand)]
7pub enum PwnboxCommand {
8 Usage,
10 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}