Skip to content

Commit

Permalink
feat(rust): show interactive menu to select one or more TCP inlent wh…
Browse files Browse the repository at this point in the history
…en user dosen't specifies

 Make ockam tcp-inlet delete (no args) interactive by asking the user to choose from a list of `tcp-inlet` aliases to delete using tuify

 - Closes: /issues/6466
  • Loading branch information
0x61nas committed Oct 29, 2023
1 parent 6faecaf commit 179ae09
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -158,4 +158,9 @@ impl BackgroundNode {
let route = self.create_route().await?;
Ok(Client::new(&route, timeout))
}

/// Get the node name
pub fn name(&self) -> &str {
&self.node_name
}
}
4 changes: 2 additions & 2 deletions implementations/rust/ockam/ockam_command/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,13 @@ ockam_vault_aws = { path = "../ockam_vault_aws", version = "^0.12.0" }
once_cell = "1.18"
open = "5.0.0"
pem-rfc7468 = { version = "0.7.0", features = ["std"] }
r3bl_rs_utils_core = "0.9.7"
r3bl_tuify = "0.1.19"
rand = "0.8"
regex = "1.10.2"
reqwest = { version = "0.11", default-features = false, features = ["json", "rustls-tls-native-roots"] }
rustls = "0.21.7"
rustls-native-certs = "0.6.3"
r3bl_tuify = "0.1.17"
r3bl_rs_utils_core = "0.9.5"
serde = { version = "1", features = ["derive"] }
serde_bare = { version = "0.5.0", default-features = false, features = ["alloc"] }
serde_json = "1"
Expand Down
96 changes: 66 additions & 30 deletions implementations/rust/ockam/ockam_command/src/tcp/inlet/delete.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
use clap::Args;
use colorful::Colorful;
use indoc::formatdoc;
use miette::{miette, IntoDiagnostic};

use ockam::Context;
use ockam_api::nodes::models::portal::InletStatus;
use ockam_api::nodes::models::portal::{InletList, InletStatus};
use ockam_api::nodes::BackgroundNode;
use ockam_core::api::Request;

use crate::fmt_ok;
use crate::node::{get_node_name, initialize_node_if_default, NodeOpts};
use crate::tcp::util::alias_parser;
use crate::tcp::util::{alias_parser, fetch_list};
use crate::util::{node_rpc, parse_node_name};
use crate::{docs, CommandGlobalOpts};
use crate::{fmt_err, fmt_ok};

const AFTER_LONG_HELP: &str = include_str!("./static/delete/after_long_help.txt");

Expand All @@ -20,8 +21,8 @@ const AFTER_LONG_HELP: &str = include_str!("./static/delete/after_long_help.txt"
#[command(after_long_help = docs::after_help(AFTER_LONG_HELP))]
pub struct DeleteCommand {
/// Delete the inlet with this alias
#[arg(display_order = 900, required = true, id = "ALIAS", value_parser = alias_parser)]
alias: String,
#[arg(display_order = 900, id = "ALIAS", value_parser = alias_parser)]
alias: Option<String>,

/// Node on which to stop the tcp inlet. If none are provided, the default node will be used
#[command(flatten)]
Expand All @@ -43,37 +44,72 @@ pub async fn run_impl(
ctx: Context,
(opts, cmd): (CommandGlobalOpts, DeleteCommand),
) -> miette::Result<()> {
let mut selected_aliases = Vec::with_capacity(1);
let node_name = get_node_name(&opts.state, &cmd.node_opts.at_node);
let node_name = parse_node_name(&node_name)?;
let node = BackgroundNode::create(&ctx, &opts.state, &node_name).await?;

// Check if alias exists
let alias = cmd.alias;
node.ask_and_get_reply::<_, InletStatus>(&ctx, Request::get(format!("/node/inlet/{alias}")))
.await?
.found()
.into_diagnostic()?
.ok_or(miette!(
"TCP inlet with alias {alias} was not found on Node {node_name}"
))?;
if let Some(alias) = cmd.alias.as_ref() {
// Check if alias exists
let inlet_staus = node
.ask_and_get_reply::<_, InletStatus>(&ctx, Request::get(format!("/node/inlet/{alias}")))
.await?
.found()
.into_diagnostic()?
.ok_or(miette!(
"TCP inlet with alias {alias} was not found on Node {node_name}"
))?;
selected_aliases.push(inlet_staus.alias);
} else {
if !opts.terminal.can_ask_for_user_input() {
return Err(miette!("No alias was provided"));
}
// Get all the avilable aliases on the node
let aliases: Vec<String> =
fetch_list::<_, InletList>("/node/inlet", &ctx, &node, &opts.terminal)
.await?
.list
.into_iter()
.map(|status| status.alias)
.collect();
if aliases.is_empty() {
return Err(miette!("There's no TCP inlents to choose from"));
}
// Show the multible selection menu
selected_aliases = opts
.terminal
.select_multiple("Select an inlet/s to delete".to_string(), aliases);
if selected_aliases.is_empty() {
return Err(miette!("User did not select anything"));
};
}

// Proceed with the deletion
if opts
.terminal
.confirmed_with_flag_or_prompt(cmd.yes, "Are you sure you want to delete this TCP inlet?")?
{
node.tell(&ctx, Request::delete(format!("/node/inlet/{alias}")))
.await?;
// Confirm that the user know what he do
let know = if cmd.alias.is_none() {
opts.terminal.confirm_interactively(formatdoc!(
"Are you sure that you like to delete these items : {:?}?",
selected_aliases
))
} else {
opts.terminal.confirmed_with_flag_or_prompt(
cmd.yes,
"Are you sure that you want to delete this TCP inlet?",
)?
};

opts.terminal
.stdout()
.plain(fmt_ok!(
"TCP inlet with alias {alias} on Node {node_name} has been deleted"
))
.machine(&alias)
.json(serde_json::json!({ "alias": alias, "node": node_name }))
.write_line()
.unwrap();
if know {
for alias in selected_aliases {
let msg = match node
.tell(&ctx, Request::delete(format!("/node/inlet/{alias}")))
.await
{
Ok(_) => fmt_ok!(
"✅ TCP inlet with alias `{alias}` on Node {node_name} has been deleted"
),
Err(e) => fmt_err!("⚠️ Can't delete `{alias}` becuse: {e}"),
};
opts.terminal.clone().stdout().plain(msg).write_line()?;
}
}
Ok(())
}
36 changes: 36 additions & 0 deletions implementations/rust/ockam/ockam_command/src/tcp/util.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
use crate::terminal::{OckamColor, Terminal, TerminalWriter};
use crate::Result;
use colorful::Colorful;
use miette::miette;
use ockam::Context;
use ockam_api::nodes::BackgroundNode;
use ockam_core::api::Request;





use tokio::sync::Mutex;
use tokio::try_join;

pub fn alias_parser(arg: &str) -> Result<String> {
if arg.contains(':') {
Expand All @@ -8,3 +20,27 @@ pub fn alias_parser(arg: &str) -> Result<String> {
Ok(arg.to_string())
}
}

pub async fn fetch_list<T: TerminalWriter, L: for<'b> minicbor::Decode<'b, ()>>(
endpoint: &str,
ctx: &Context,
node: &BackgroundNode,
terminal: &Terminal<T>,
) -> miette::Result<L> {
let is_finished: Mutex<bool> = Mutex::new(false);

let get_list = async {
let items: L = node.ask(ctx, Request::get(endpoint)).await?;
*is_finished.lock().await = true;
Ok(items)
};
let output_messages = vec![format!(
"Listing TCP Inlets on {}...\n",
node.name().color(OckamColor::PrimaryResource.color())
)];

let progress_output = terminal.progress_output(&output_messages, &is_finished);

let (items, _) = try_join!(get_list, progress_output)?;
Ok(items)
}

0 comments on commit 179ae09

Please sign in to comment.