initial work on compact listing views

This commit is contained in:
core 2023-09-30 17:48:29 -04:00
parent 5a0ceb945a
commit c1a1113d8a
Signed by: core
GPG Key ID: FDBF740DADDCEECF
5 changed files with 169 additions and 37 deletions

66
Cargo.lock generated
View File

@ -926,6 +926,18 @@ dependencies = [
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
[[package]]
name = "comfy-table"
version = "7.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ab77dbd8adecaf3f0db40581631b995f312a8a5ae3aa9993188bb8f23d83a5b"
dependencies = [
"crossterm 0.26.1",
"strum 0.24.1",
"strum_macros",
"unicode-width",
]
[[package]] [[package]]
name = "concurrent-queue" name = "concurrent-queue"
version = "2.3.0" version = "2.3.0"
@ -1038,6 +1050,22 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "crossterm"
version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13"
dependencies = [
"bitflags 1.3.2",
"crossterm_winapi",
"libc",
"mio",
"parking_lot",
"signal-hook",
"signal-hook-mio",
"winapi",
]
[[package]] [[package]]
name = "crossterm_winapi" name = "crossterm_winapi"
version = "0.9.1" version = "0.9.1"
@ -2496,7 +2524,7 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c2a1e77b5cd714b04247ad912b7c8fe9a1fe1d58425048249def91bcf690e4c" checksum = "4c2a1e77b5cd714b04247ad912b7c8fe9a1fe1d58425048249def91bcf690e4c"
dependencies = [ dependencies = [
"crossterm", "crossterm 0.25.0",
"qrcode", "qrcode",
] ]
@ -2844,6 +2872,12 @@ dependencies = [
"untrusted", "untrusted",
] ]
[[package]]
name = "rustversion"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.15" version = "1.0.15"
@ -2899,7 +2933,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"sqlx", "sqlx",
"strum", "strum 0.25.0",
"thiserror", "thiserror",
"time", "time",
"tracing", "tracing",
@ -3537,12 +3571,31 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "strum"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
[[package]] [[package]]
name = "strum" name = "strum"
version = "0.25.0" version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
[[package]]
name = "strum_macros"
version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
"syn 1.0.109",
]
[[package]] [[package]]
name = "subtle" name = "subtle"
version = "2.5.0" version = "2.5.0"
@ -3592,9 +3645,10 @@ dependencies = [
[[package]] [[package]]
name = "tfcli" name = "tfcli"
version = "0.2.0" version = "0.2.1"
dependencies = [ dependencies = [
"clap", "clap",
"comfy-table",
"dirs", "dirs",
"ipnet", "ipnet",
"qr2term", "qr2term",
@ -4003,6 +4057,12 @@ version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
[[package]]
name = "unicode-width"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
[[package]] [[package]]
name = "unicode_categories" name = "unicode_categories"
version = "0.1.1" version = "0.1.1"

View File

@ -1,6 +1,6 @@
[package] [package]
name = "tfcli" name = "tfcli"
version = "0.2.0" version = "0.2.1"
edition = "2021" edition = "2021"
description = "Command-line client for managing trifid-api" description = "Command-line client for managing trifid-api"
license = "GPL-3.0-or-later" license = "GPL-3.0-or-later"
@ -20,3 +20,4 @@ dirs = "5"
qr2term = "0.3" qr2term = "0.3"
ipnet = "2.7" ipnet = "2.7"
serde_json = "1" serde_json = "1"
comfy-table = "7"

View File

@ -1,7 +1,8 @@
use std::error::Error; use std::error::Error;
use std::fmt::{Display, Formatter};
use std::fs; use std::fs;
use std::net::{Ipv4Addr, SocketAddrV4}; use std::net::{Ipv4Addr, SocketAddrV4};
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand, ValueEnum};
use ipnet::Ipv4Net; use ipnet::Ipv4Net;
use url::Url; use url::Url;
use crate::account::account_main; use crate::account::account_main;
@ -93,7 +94,10 @@ pub enum AccountCommands {
#[derive(Subcommand, Debug)] #[derive(Subcommand, Debug)]
pub enum NetworkCommands { pub enum NetworkCommands {
/// List all networks associated with your trifid account. /// List all networks associated with your trifid account.
List {}, List {
#[clap(short = 'T', long, default_value_t = TableStyle::Basic)]
table_style: TableStyle
},
/// Lookup a specific network by ID. /// Lookup a specific network by ID.
Lookup { Lookup {
#[clap(short, long)] #[clap(short, long)]
@ -123,7 +127,10 @@ pub enum RoleCommands {
rules_json: String rules_json: String
}, },
/// List all roles attached to your organization /// List all roles attached to your organization
List {}, List {
#[clap(short = 'T', long, default_value_t = TableStyle::Basic)]
table_style: TableStyle
},
/// Lookup a specific role by it's ID /// Lookup a specific role by it's ID
Lookup { Lookup {
#[clap(short, long)] #[clap(short, long)]
@ -240,6 +247,27 @@ pub enum HostOverrideCommands {
} }
} }
#[derive(Debug, Clone, ValueEnum)]
pub enum TableStyle {
List,
Basic,
Pretty
}
impl Default for TableStyle {
fn default() -> Self {
Self::Basic
}
}
impl Display for TableStyle {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::List => write!(f, "list"),
Self::Basic => write!(f, "basic"),
Self::Pretty => write!(f, "pretty")
}
}
}
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
match main2().await { match main2().await {

View File

@ -1,13 +1,16 @@
use std::error::Error; use std::error::Error;
use std::fs; use std::fs;
use comfy_table::modifiers::UTF8_ROUND_CORNERS;
use comfy_table::presets::UTF8_FULL;
use comfy_table::Table;
use serde::Deserialize; use serde::Deserialize;
use url::Url; use url::Url;
use crate::api::APIErrorResponse; use crate::api::APIErrorResponse;
use crate::NetworkCommands; use crate::{NetworkCommands, TableStyle};
pub async fn network_main(command: NetworkCommands, server: Url) -> Result<(), Box<dyn Error>> { pub async fn network_main(command: NetworkCommands, server: Url) -> Result<(), Box<dyn Error>> {
match command { match command {
NetworkCommands::List {} => list_networks(server).await, NetworkCommands::List { table_style } => list_networks(server, table_style).await,
NetworkCommands::Lookup {id} => get_network(id, server).await NetworkCommands::Lookup {id} => get_network(id, server).await
} }
} }
@ -32,7 +35,7 @@ pub struct Network {
pub name: String pub name: String
} }
pub async fn list_networks(server: Url) -> Result<(), Box<dyn Error>> { pub async fn list_networks(server: Url, table_style: TableStyle) -> Result<(), Box<dyn Error>> {
let client = reqwest::Client::new(); let client = reqwest::Client::new();
// load session token // load session token
@ -48,20 +51,39 @@ pub async fn list_networks(server: Url) -> Result<(), Box<dyn Error>> {
if res.status().is_success() { if res.status().is_success() {
let resp: NetworkListResp = res.json().await?; let resp: NetworkListResp = res.json().await?;
for network in &resp.data {
println!(" Network: {}", network.id);
println!(" CIDR: {}", network.cidr);
println!(" Organization: {}", network.organization_id);
println!(" Signing CA: {}", network.signing_ca_id);
println!("Dedicated Relays: {}", !network.lighthouses_as_relays);
println!(" Name: {}", network.name);
println!(" Created At: {}", network.created_at);
println!();
}
if resp.data.is_empty() { if resp.data.is_empty() {
println!("No networks found"); println!("No networks found");
return Ok(());
} }
if matches!(table_style, TableStyle::List) {
for network in &resp.data {
println!(" Network: {}", network.id);
println!(" CIDR: {}", network.cidr);
println!(" Organization: {}", network.organization_id);
println!(" Signing CA: {}", network.signing_ca_id);
println!("Dedicated Relays: {}", !network.lighthouses_as_relays);
println!(" Name: {}", network.name);
println!(" Created At: {}", network.created_at);
println!();
}
return Ok(());
}
let mut table = Table::new();
match table_style {
TableStyle::List => unreachable!(),
TableStyle::Basic => (),
TableStyle::Pretty => { table.load_preset(UTF8_FULL).apply_modifier(UTF8_ROUND_CORNERS) ; },
};
table.set_header(vec!["ID", "Name", "CIDR", "Organization ID", "Signing CA ID", "Dedicated Relays", "Created At"]);
for network in &resp.data {
table.add_row(vec![&network.id, &network.name, &network.cidr, &network.organization_id, &network.signing_ca_id, (!network.lighthouses_as_relays).to_string().as_str(), &network.created_at]);
}
println!("{table}");
} else { } else {
let resp: APIErrorResponse = res.json().await?; let resp: APIErrorResponse = res.json().await?;

View File

@ -1,13 +1,16 @@
use std::error::Error; use std::error::Error;
use std::fs; use std::fs;
use comfy_table::modifiers::UTF8_ROUND_CORNERS;
use comfy_table::presets::UTF8_FULL;
use comfy_table::Table;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use url::Url; use url::Url;
use crate::api::APIErrorResponse; use crate::api::APIErrorResponse;
use crate::{RoleCommands}; use crate::{RoleCommands, TableStyle};
pub async fn role_main(command: RoleCommands, server: Url) -> Result<(), Box<dyn Error>> { pub async fn role_main(command: RoleCommands, server: Url) -> Result<(), Box<dyn Error>> {
match command { match command {
RoleCommands::List {} => list_roles(server).await, RoleCommands::List { table_style } => list_roles(server, table_style).await,
RoleCommands::Lookup {id} => get_role(id, server).await, RoleCommands::Lookup {id} => get_role(id, server).await,
RoleCommands::Create { name, description, rules_json } => create_role(name, description, rules_json, server).await, RoleCommands::Create { name, description, rules_json } => create_role(name, description, rules_json, server).await,
RoleCommands::Delete { id } => delete_role(id, server).await, RoleCommands::Delete { id } => delete_role(id, server).await,
@ -47,7 +50,7 @@ pub struct RoleFirewallRulePortRange {
pub to: u16 pub to: u16
} }
pub async fn list_roles(server: Url) -> Result<(), Box<dyn Error>> { pub async fn list_roles(server: Url, table_style: TableStyle) -> Result<(), Box<dyn Error>> {
let client = reqwest::Client::new(); let client = reqwest::Client::new();
// load session token // load session token
@ -63,22 +66,40 @@ pub async fn list_roles(server: Url) -> Result<(), Box<dyn Error>> {
if res.status().is_success() { if res.status().is_success() {
let resp: RoleListResp = res.json().await?; let resp: RoleListResp = res.json().await?;
for role in &resp.data {
println!(" Role: {} ({})", role.name, role.id);
println!(" Description: {}", role.description);
for rule in &role.firewall_rules {
println!("Rule Description: {}", rule.description);
println!(" Allowed Role: {}", rule.allowed_role_id.as_ref().unwrap_or(&"All roles".to_string()));
println!(" Protocol: {}", rule.protocol);
println!(" Port Range: {}", if let Some(pr) = rule.port_range.as_ref() { format!("{}-{}", pr.from, pr.to) } else { "Any".to_string() });
}
println!(" Created: {}", role.created_at);
println!(" Updated: {}", role.modified_at);
}
if resp.data.is_empty() { if resp.data.is_empty() {
println!("No roles found"); println!("No roles found");
return Ok(());
} }
if matches!(table_style, TableStyle::List) {
for role in &resp.data {
println!(" Role: {} ({})", role.name, role.id);
println!(" Description: {}", role.description);
for rule in &role.firewall_rules {
println!("Rule Description: {}", rule.description);
println!(" Allowed Role: {}", rule.allowed_role_id.as_ref().unwrap_or(&"All roles".to_string()));
println!(" Protocol: {}", rule.protocol);
println!(" Port Range: {}", if let Some(pr) = rule.port_range.as_ref() { format!("{}-{}", pr.from, pr.to) } else { "Any".to_string() });
}
println!(" Created: {}", role.created_at);
println!(" Updated: {}", role.modified_at);
}
return Ok(());
}
let mut table = Table::new();
match table_style {
TableStyle::List => unreachable!(),
TableStyle::Basic => (),
TableStyle::Pretty => { table.load_preset(UTF8_FULL).apply_modifier(UTF8_ROUND_CORNERS); },
};
table.set_header(vec!["ID", "Name", "Description", "Rule Count", "Created", "Updated"]);
for role in &resp.data {
table.add_row(vec![&role.id, &role.name, &role.description, role.firewall_rules.len().to_string().as_str(), &role.created_at, &role.modified_at]);
}
println!("{table}");
} else { } else {
let resp: APIErrorResponse = res.json().await?; let resp: APIErrorResponse = res.json().await?;