use colorful::Colorful;
use console::Term;
use miette::{miette, IntoDiagnostic};
use ockam_api::colors::color_primary;
use ockam_api::terminal::{Terminal, TerminalStream};
use ockam_api::{fmt_info, fmt_warn};
use ockam_core::TryClone;
use std::time::Duration;
use tokio::task::JoinSet;
use tokio::time::sleep;
#[ockam_core::async_trait]
pub trait ShowCommandTui {
const ITEM_NAME: PluralTerm;
fn cmd_arg_item_name(&self) -> Option<String>;
fn node_name(&self) -> Option<&str> {
None
}
fn terminal(&self) -> Terminal<TerminalStream<Term>>;
async fn get_arg_item_name_or_default(&self) -> miette::Result<String>;
async fn list_items_names(&self) -> miette::Result<Vec<String>>;
async fn show_single(&self, item_name: &str) -> miette::Result<()>;
async fn show(&self) -> miette::Result<()> {
let terminal = self.terminal();
let items_names = self.list_items_names().await?;
if items_names.is_empty() {
terminal
.to_stdout()
.plain(fmt_info!(
"There are no {} to show{}",
Self::ITEM_NAME.plural(),
get_opt_node_name_message(self.node_name())
))
.json(serde_json::to_string(&items_names).into_diagnostic()?)
.write_line()?;
return Ok(());
}
if self.cmd_arg_item_name().is_some() || !terminal.can_ask_for_user_input() {
let item_name = self.get_arg_item_name_or_default().await?;
if !items_names.contains(&item_name) {
return Err(miette!(
"The {} {} was not found",
Self::ITEM_NAME.singular(),
color_primary(item_name)
));
}
self.show_single(&item_name).await?;
return Ok(());
}
match items_names.len() {
0 => {
unreachable!("this case is already handled above");
}
1 => {
let item_name = items_names[0].as_str();
self.show_single(item_name).await?;
}
_ => {
let selected_item_names = terminal.select_multiple(
format!(
"Select one or more {} that you want to show:",
Self::ITEM_NAME.plural()
),
items_names,
);
match selected_item_names.len() {
0 => {
terminal
.to_stdout()
.plain(fmt_info!(
"No {} selected to show",
Self::ITEM_NAME.plural()
))
.write_line()?;
}
1 => {
let item_name = selected_item_names[0].as_str();
self.show_single(item_name).await?;
}
_ => {
for item_name in selected_item_names {
if self.show_single(&item_name).await.is_err() {
self.terminal()
.to_stdout()
.plain(fmt_warn!(
"Failed to show {} {}",
Self::ITEM_NAME.singular(),
color_primary(item_name)
))
.write_line()?;
}
}
}
}
}
}
Ok(())
}
}
fn get_opt_node_name_message(node_name: Option<&str>) -> String {
if let Some(node_name) = node_name {
format!(" on node {}", color_primary(node_name))
} else {
"".to_string()
}
}
#[ockam_core::async_trait]
pub trait DeleteCommandTui: TryClone + Send
where
Self: 'static,
{
const ITEM_NAME: PluralTerm;
fn cmd_arg_item_name(&self) -> Option<String>;
fn cmd_arg_delete_all(&self) -> bool;
fn cmd_arg_confirm_deletion(&self) -> bool;
fn terminal(&self) -> Terminal<TerminalStream<Term>>;
async fn list_items_names(&self) -> miette::Result<Vec<String>>;
async fn delete_single(&self, item_name: &str) -> miette::Result<()>;
async fn delete_multiple(&self, items_names: Vec<String>) -> miette::Result<()> {
self.delete_multiple_blocking(items_names).await
}
async fn delete_multiple_blocking(&self, items_names: Vec<String>) -> miette::Result<()> {
for item_name in items_names {
if self.delete_single(&item_name).await.is_err() {
self.terminal()
.to_stdout()
.plain(fmt_warn!(
"Failed to delete {} {}",
Self::ITEM_NAME.singular(),
color_primary(item_name)
))
.write_line()?;
}
}
Ok(())
}
async fn delete_multiple_async(&self, items_names: Vec<String>) -> miette::Result<()> {
for chunk in items_names.chunks(10).map(|c| c.to_vec()) {
let mut set: JoinSet<miette::Result<()>> = JoinSet::new();
for item_name in chunk {
let _self = self.try_clone().into_diagnostic()?;
set.spawn(async move {
sleep(tokio_retry::strategy::jitter(Duration::from_millis(500))).await;
if _self.delete_single(&item_name).await.is_err() {
_self
.terminal()
.to_stdout()
.plain(fmt_warn!(
"Failed to delete {} {}",
Self::ITEM_NAME.singular(),
color_primary(&item_name)
))
.write_line()?;
}
Ok(())
});
}
set.join_all().await;
}
Ok(())
}
async fn delete(&self) -> miette::Result<()> {
let terminal = self.terminal();
let items_names = self.list_items_names().await?;
if items_names.is_empty() {
terminal
.to_stdout()
.plain(fmt_info!(
"There are no {} to delete",
Self::ITEM_NAME.plural()
))
.json(serde_json::to_string(&items_names).into_diagnostic()?)
.write_line()?;
return Ok(());
}
if self.cmd_arg_delete_all()
&& terminal.confirmed_with_flag_or_prompt(
self.cmd_arg_confirm_deletion(),
format!(
"Are you sure you want to delete {}?",
if items_names.len() > 1 {
format!("your {} {}", items_names.len(), Self::ITEM_NAME.plural())
} else {
format!("your only {}", Self::ITEM_NAME.singular())
}
),
)?
{
self.delete_multiple(items_names).await?;
return Ok(());
}
if self.cmd_arg_item_name().is_some() || !terminal.can_ask_for_user_input() {
if let Some(item_name) = self.cmd_arg_item_name() {
if !items_names.contains(&item_name.to_string()) {
return Err(miette!(
"The {} {} was not found",
Self::ITEM_NAME.singular(),
color_primary(&item_name)
));
}
if terminal.confirmed_with_flag_or_prompt(
self.cmd_arg_confirm_deletion(),
"Are you sure you want to proceed?",
)? {
self.delete_single(&item_name).await?;
}
}
return Ok(());
}
match items_names.len() {
0 => {
unreachable!("this case is already handled above");
}
1 => {
if terminal.confirmed_with_flag_or_prompt(
self.cmd_arg_confirm_deletion(),
format!(
"You are about to delete your only {}. Are you sure you want to proceed?",
Self::ITEM_NAME.singular()
),
)? {
let item_name = items_names[0].as_str();
self.delete_single(item_name).await?;
}
}
_ => {
let selected_item_names = terminal.select_multiple(
format!(
"Select one or more {} that you want to delete:",
Self::ITEM_NAME.plural()
),
items_names,
);
match selected_item_names.len() {
0 => {
terminal
.to_stdout()
.plain(fmt_info!(
"No {} selected to delete",
Self::ITEM_NAME.plural()
))
.write_line()?;
}
1 => {
if terminal.confirmed_with_flag_or_prompt(
self.cmd_arg_confirm_deletion(),
"Are you sure you want to proceed?",
)? {
let item_name = selected_item_names[0].as_str();
self.delete_single(item_name).await?;
}
}
_ => {
if terminal.confirmed_with_flag_or_prompt(
self.cmd_arg_confirm_deletion(),
"Are you sure you want to proceed?",
)? {
self.delete_multiple(selected_item_names).await?;
}
}
}
}
}
Ok(())
}
}
pub enum PluralTerm {
Vault,
Identity,
Node,
Relay,
Space,
SpaceAdmin,
Project,
ProjectAdmin,
TcpInlet,
TcpOutlet,
KafkaInlet,
KafkaOutlet,
Policy,
Member,
}
impl PluralTerm {
fn singular(&self) -> &'static str {
match self {
PluralTerm::Vault => "vault",
PluralTerm::Identity => "identity",
PluralTerm::Node => "node",
PluralTerm::Relay => "relay",
PluralTerm::Space => "space",
PluralTerm::SpaceAdmin => "space admin",
PluralTerm::Project => "project",
PluralTerm::ProjectAdmin => "project admin",
PluralTerm::TcpInlet => "tcp inlet",
PluralTerm::TcpOutlet => "tcp outlet",
PluralTerm::KafkaInlet => "kafka inlet",
PluralTerm::KafkaOutlet => "kafka outlet",
PluralTerm::Policy => "policy",
PluralTerm::Member => "member",
}
}
fn plural(&self) -> &'static str {
match self {
PluralTerm::Vault => "vaults",
PluralTerm::Identity => "identities",
PluralTerm::Node => "nodes",
PluralTerm::Relay => "relays",
PluralTerm::Space => "spaces",
PluralTerm::SpaceAdmin => "space admins",
PluralTerm::Project => "projects",
PluralTerm::ProjectAdmin => "project admins",
PluralTerm::TcpInlet => "tcp inlets",
PluralTerm::TcpOutlet => "tcp outlets",
PluralTerm::KafkaInlet => "kafka inlets",
PluralTerm::KafkaOutlet => "kafka outlets",
PluralTerm::Policy => "policies",
PluralTerm::Member => "members",
}
}
}