diff --git a/docs/CommandLineHelp.md b/docs/CommandLineHelp.md index 797429b..6b2031e 100644 --- a/docs/CommandLineHelp.md +++ b/docs/CommandLineHelp.md @@ -11,6 +11,7 @@ This document contains the help content for the `screenly` command-line program. * [`screenly screen list`↴](#screenly-screen-list) * [`screenly screen get`↴](#screenly-screen-get) * [`screenly screen add`↴](#screenly-screen-add) +* [`screenly screen status`↴](#screenly-screen-status) * [`screenly screen delete`↴](#screenly-screen-delete) * [`screenly asset`↴](#screenly-asset) * [`screenly asset list`↴](#screenly-asset-list) @@ -97,6 +98,7 @@ Screen related commands * `list` — Lists your screens * `get` — Gets a single screen by id * `add` — Adds a new screen +* `status` — Shows screen counts: total, online/offline, out of sync * `delete` — Deletes a screen. This cannot be undone @@ -146,6 +148,18 @@ Adds a new screen +## `screenly screen status` + +Shows screen counts: total, online/offline, out of sync + +**Usage:** `screenly screen status [OPTIONS]` + +###### **Options:** + +* `-j`, `--json` — Enables JSON output + + + ## `screenly screen delete` Deletes a screen. This cannot be undone diff --git a/src/cli.rs b/src/cli.rs index d3ba88e..28018dc 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -123,6 +123,12 @@ pub enum ScreenCommands { /// Optional name of the new screen. name: Option, }, + /// Shows screen counts: total, online/offline, out of sync. + Status { + /// Enables JSON output. + #[arg(short, long, action = clap::ArgAction::SetTrue)] + json: Option, + }, /// Deletes a screen. This cannot be undone. Delete { /// UUID of the screen to be deleted. @@ -649,6 +655,21 @@ pub fn handle_cli_screen_command(command: &ScreenCommands) { ScreenCommands::Add { pin, name, json } => { handle_command_execution_result(screen_command.add(pin, name.clone()), json); } + ScreenCommands::Status { json } => { + let screen_status_command = commands::screen::ScreenCommand::new(get_authentication()); + let output_type = if json == &Some(true) { + OutputType::Json + } else { + OutputType::HumanReadable + }; + match screen_status_command.status() { + Ok(status) => println!("{}", status.format(output_type)), + Err(e) => { + error!("Error occurred: {e:?}"); + std::process::exit(1); + } + } + } ScreenCommands::Delete { uuid } => { match get_screen_name(uuid, &screen_command) { Ok(name) => { diff --git a/src/commands/screen.rs b/src/commands/screen.rs index 15f32c2..1e74804 100644 --- a/src/commands/screen.rs +++ b/src/commands/screen.rs @@ -1,10 +1,12 @@ use std::collections::HashMap; +use prettytable::{row, Table}; use reqwest::StatusCode; +use serde::Serialize; use crate::authentication::Authentication; use crate::commands; -use crate::commands::{CommandError, Screens}; +use crate::commands::{CommandError, OutputType, Screens}; pub struct ScreenCommand { authentication: Authentication, @@ -64,6 +66,55 @@ impl ScreenCommand { let endpoint = format!("v3/screens/{id}/"); commands::delete(&self.authentication, &endpoint) } + + pub fn status(&self) -> Result { + let data = commands::get(&self.authentication, "v4/screens?select=id,status,in_sync")?; + let screens = data.as_array().map(|a| a.as_slice()).unwrap_or(&[]); + + let total = screens.len(); + let online = screens + .iter() + .filter(|s| s["status"].as_str() == Some("Online")) + .count(); + let out_of_sync = screens + .iter() + .filter(|s| s["in_sync"].as_bool() != Some(true)) + .count(); + + Ok(ScreensStatus { + total, + online, + offline: total - online, + out_of_sync, + }) + } +} + +#[derive(Debug, Serialize)] +pub struct ScreensStatus { + pub total: usize, + pub online: usize, + pub offline: usize, + pub out_of_sync: usize, +} + +impl ScreensStatus { + pub fn format(&self, output_type: OutputType) -> String { + match output_type { + OutputType::Json => serde_json::to_string_pretty(self).unwrap(), + OutputType::HumanReadable => { + let mut table = Table::new(); + table.add_row(row!["Total", "Online", "Offline", "Out of Sync"]); + table.add_row(row![ + self.total, + self.online, + self.offline, + self.out_of_sync + ]); + table.to_string() + } + } + } } #[cfg(test)] @@ -188,4 +239,74 @@ mod tests { assert_eq!(screen.format(OutputType::HumanReadable), expected_output); } + + fn make_screen(status: &str, in_sync: bool) -> serde_json::Value { + json!({ "id": "abc", "status": status, "in_sync": in_sync }) + } + + #[test] + fn test_screens_counts_online_offline_and_out_of_sync() { + let screens = json!([ + make_screen("Online", true), + make_screen("Online", false), + make_screen("Offline", true), + make_screen("Offline", false), + ]); + + let mock_server = MockServer::start(); + mock_server.mock(|when, then| { + when.method(GET) + .path("/v4/screens") + .query_param("select", "id,status,in_sync"); + then.status(200).json_body(screens); + }); + + let auth = Authentication::new_with_config(Config::new(mock_server.base_url()), "token"); + let result = ScreenCommand::new(auth).status().unwrap(); + + assert_eq!(result.total, 4); + assert_eq!(result.online, 2); + assert_eq!(result.offline, 2); + assert_eq!(result.out_of_sync, 2); + } + + #[test] + fn test_screens_all_online_and_in_sync() { + let screens = json!([make_screen("Online", true), make_screen("Online", true),]); + + let mock_server = MockServer::start(); + mock_server.mock(|when, then| { + when.method(GET) + .path("/v4/screens") + .query_param("select", "id,status,in_sync"); + then.status(200).json_body(screens); + }); + + let auth = Authentication::new_with_config(Config::new(mock_server.base_url()), "token"); + let result = ScreenCommand::new(auth).status().unwrap(); + + assert_eq!(result.total, 2); + assert_eq!(result.online, 2); + assert_eq!(result.offline, 0); + assert_eq!(result.out_of_sync, 0); + } + + #[test] + fn test_screens_empty() { + let mock_server = MockServer::start(); + mock_server.mock(|when, then| { + when.method(GET) + .path("/v4/screens") + .query_param("select", "id,status,in_sync"); + then.status(200).json_body(json!([])); + }); + + let auth = Authentication::new_with_config(Config::new(mock_server.base_url()), "token"); + let result = ScreenCommand::new(auth).status().unwrap(); + + assert_eq!(result.total, 0); + assert_eq!(result.online, 0); + assert_eq!(result.offline, 0); + assert_eq!(result.out_of_sync, 0); + } }