re-uploading work
This commit is contained in:
567
crates/cli/src/commands/rule.rs
Normal file
567
crates/cli/src/commands/rule.rs
Normal file
@@ -0,0 +1,567 @@
|
||||
use anyhow::Result;
|
||||
use clap::Subcommand;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::client::ApiClient;
|
||||
use crate::config::CliConfig;
|
||||
use crate::output::{self, OutputFormat};
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub enum RuleCommands {
|
||||
/// List all rules
|
||||
List {
|
||||
/// Filter by pack name
|
||||
#[arg(long)]
|
||||
pack: Option<String>,
|
||||
|
||||
/// Filter by enabled status
|
||||
#[arg(short, long)]
|
||||
enabled: Option<bool>,
|
||||
},
|
||||
/// Show details of a specific rule
|
||||
Show {
|
||||
/// Rule reference (pack.rule or ID)
|
||||
rule_ref: String,
|
||||
},
|
||||
/// Update a rule
|
||||
Update {
|
||||
/// Rule reference (pack.rule or ID)
|
||||
rule_ref: String,
|
||||
|
||||
/// Update label
|
||||
#[arg(long)]
|
||||
label: Option<String>,
|
||||
|
||||
/// Update description
|
||||
#[arg(long)]
|
||||
description: Option<String>,
|
||||
|
||||
/// Update conditions as JSON string
|
||||
#[arg(long)]
|
||||
conditions: Option<String>,
|
||||
|
||||
/// Update action parameters as JSON string
|
||||
#[arg(long)]
|
||||
action_params: Option<String>,
|
||||
|
||||
/// Update trigger parameters as JSON string
|
||||
#[arg(long)]
|
||||
trigger_params: Option<String>,
|
||||
|
||||
/// Update enabled status
|
||||
#[arg(long)]
|
||||
enabled: Option<bool>,
|
||||
},
|
||||
/// Enable a rule
|
||||
Enable {
|
||||
/// Rule reference (pack.rule or ID)
|
||||
rule_ref: String,
|
||||
},
|
||||
/// Disable a rule
|
||||
Disable {
|
||||
/// Rule reference (pack.rule or ID)
|
||||
rule_ref: String,
|
||||
},
|
||||
/// Create a new rule
|
||||
Create {
|
||||
/// Rule name
|
||||
#[arg(short, long)]
|
||||
name: String,
|
||||
|
||||
/// Pack ID or name
|
||||
#[arg(short, long)]
|
||||
pack: String,
|
||||
|
||||
/// Trigger reference
|
||||
#[arg(short, long)]
|
||||
trigger: String,
|
||||
|
||||
/// Action reference
|
||||
#[arg(short, long)]
|
||||
action: String,
|
||||
|
||||
/// Rule description
|
||||
#[arg(short, long)]
|
||||
description: Option<String>,
|
||||
|
||||
/// Rule criteria as JSON string
|
||||
#[arg(long)]
|
||||
criteria: Option<String>,
|
||||
|
||||
/// Enable the rule immediately
|
||||
#[arg(long)]
|
||||
enabled: bool,
|
||||
},
|
||||
/// Delete a rule
|
||||
Delete {
|
||||
/// Rule reference (pack.rule or ID)
|
||||
rule_ref: String,
|
||||
|
||||
/// Skip confirmation prompt
|
||||
#[arg(short = 'y', long)]
|
||||
yes: bool,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct Rule {
|
||||
id: i64,
|
||||
#[serde(rename = "ref")]
|
||||
rule_ref: String,
|
||||
#[serde(default)]
|
||||
pack: Option<i64>,
|
||||
pack_ref: String,
|
||||
label: String,
|
||||
description: String,
|
||||
#[serde(default)]
|
||||
trigger: Option<i64>,
|
||||
trigger_ref: String,
|
||||
#[serde(default)]
|
||||
action: Option<i64>,
|
||||
action_ref: String,
|
||||
enabled: bool,
|
||||
created: String,
|
||||
updated: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct RuleDetail {
|
||||
id: i64,
|
||||
#[serde(rename = "ref")]
|
||||
rule_ref: String,
|
||||
#[serde(default)]
|
||||
pack: Option<i64>,
|
||||
pack_ref: String,
|
||||
label: String,
|
||||
description: String,
|
||||
#[serde(default)]
|
||||
trigger: Option<i64>,
|
||||
trigger_ref: String,
|
||||
#[serde(default)]
|
||||
action: Option<i64>,
|
||||
action_ref: String,
|
||||
enabled: bool,
|
||||
#[serde(default)]
|
||||
conditions: Option<serde_json::Value>,
|
||||
#[serde(default)]
|
||||
action_params: Option<serde_json::Value>,
|
||||
#[serde(default)]
|
||||
trigger_params: Option<serde_json::Value>,
|
||||
created: String,
|
||||
updated: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct CreateRuleRequest {
|
||||
name: String,
|
||||
pack_id: String,
|
||||
trigger_id: String,
|
||||
action_id: String,
|
||||
description: Option<String>,
|
||||
criteria: Option<serde_json::Value>,
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct UpdateRuleRequest {
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
pub async fn handle_rule_command(
|
||||
profile: &Option<String>,
|
||||
command: RuleCommands,
|
||||
api_url: &Option<String>,
|
||||
output_format: OutputFormat,
|
||||
) -> Result<()> {
|
||||
match command {
|
||||
RuleCommands::List { pack, enabled } => {
|
||||
handle_list(profile, pack, enabled, api_url, output_format).await
|
||||
}
|
||||
RuleCommands::Show { rule_ref } => {
|
||||
handle_show(profile, rule_ref, api_url, output_format).await
|
||||
}
|
||||
RuleCommands::Update {
|
||||
rule_ref,
|
||||
label,
|
||||
description,
|
||||
conditions,
|
||||
action_params,
|
||||
trigger_params,
|
||||
enabled,
|
||||
} => {
|
||||
handle_update(
|
||||
profile,
|
||||
rule_ref,
|
||||
label,
|
||||
description,
|
||||
conditions,
|
||||
action_params,
|
||||
trigger_params,
|
||||
enabled,
|
||||
api_url,
|
||||
output_format,
|
||||
)
|
||||
.await
|
||||
}
|
||||
RuleCommands::Enable { rule_ref } => {
|
||||
handle_toggle(profile, rule_ref, true, api_url, output_format).await
|
||||
}
|
||||
RuleCommands::Disable { rule_ref } => {
|
||||
handle_toggle(profile, rule_ref, false, api_url, output_format).await
|
||||
}
|
||||
RuleCommands::Create {
|
||||
name,
|
||||
pack,
|
||||
trigger,
|
||||
action,
|
||||
description,
|
||||
criteria,
|
||||
enabled,
|
||||
} => {
|
||||
handle_create(
|
||||
profile,
|
||||
name,
|
||||
pack,
|
||||
trigger,
|
||||
action,
|
||||
description,
|
||||
criteria,
|
||||
enabled,
|
||||
api_url,
|
||||
output_format,
|
||||
)
|
||||
.await
|
||||
}
|
||||
RuleCommands::Delete { rule_ref, yes } => {
|
||||
handle_delete(profile, rule_ref, yes, api_url, output_format).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_list(
|
||||
profile: &Option<String>,
|
||||
pack: Option<String>,
|
||||
enabled: Option<bool>,
|
||||
api_url: &Option<String>,
|
||||
output_format: OutputFormat,
|
||||
) -> Result<()> {
|
||||
let config = CliConfig::load_with_profile(profile.as_deref())?;
|
||||
let mut client = ApiClient::from_config(&config, api_url);
|
||||
|
||||
let mut query_params = Vec::new();
|
||||
if let Some(pack_name) = pack {
|
||||
query_params.push(format!("pack={}", pack_name));
|
||||
}
|
||||
if let Some(is_enabled) = enabled {
|
||||
query_params.push(format!("enabled={}", is_enabled));
|
||||
}
|
||||
|
||||
let path = if query_params.is_empty() {
|
||||
"/rules".to_string()
|
||||
} else {
|
||||
format!("/rules?{}", query_params.join("&"))
|
||||
};
|
||||
|
||||
let rules: Vec<Rule> = client.get(&path).await?;
|
||||
|
||||
match output_format {
|
||||
OutputFormat::Json | OutputFormat::Yaml => {
|
||||
output::print_output(&rules, output_format)?;
|
||||
}
|
||||
OutputFormat::Table => {
|
||||
if rules.is_empty() {
|
||||
output::print_info("No rules found");
|
||||
} else {
|
||||
let mut table = output::create_table();
|
||||
output::add_header(
|
||||
&mut table,
|
||||
vec!["ID", "Pack", "Name", "Trigger", "Action", "Enabled"],
|
||||
);
|
||||
|
||||
for rule in rules {
|
||||
table.add_row(vec![
|
||||
rule.id.to_string(),
|
||||
rule.pack_ref.clone(),
|
||||
rule.label.clone(),
|
||||
rule.trigger_ref.clone(),
|
||||
rule.action_ref.clone(),
|
||||
output::format_bool(rule.enabled),
|
||||
]);
|
||||
}
|
||||
|
||||
println!("{}", table);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_show(
|
||||
profile: &Option<String>,
|
||||
rule_ref: String,
|
||||
api_url: &Option<String>,
|
||||
output_format: OutputFormat,
|
||||
) -> Result<()> {
|
||||
let config = CliConfig::load_with_profile(profile.as_deref())?;
|
||||
let mut client = ApiClient::from_config(&config, api_url);
|
||||
|
||||
let path = format!("/rules/{}", rule_ref);
|
||||
let rule: RuleDetail = client.get(&path).await?;
|
||||
|
||||
match output_format {
|
||||
OutputFormat::Json | OutputFormat::Yaml => {
|
||||
output::print_output(&rule, output_format)?;
|
||||
}
|
||||
OutputFormat::Table => {
|
||||
output::print_section(&format!("Rule: {}", rule.rule_ref));
|
||||
output::print_key_value_table(vec![
|
||||
("ID", rule.id.to_string()),
|
||||
("Ref", rule.rule_ref.clone()),
|
||||
("Pack", rule.pack_ref.clone()),
|
||||
("Label", rule.label.clone()),
|
||||
("Description", rule.description.clone()),
|
||||
("Trigger", rule.trigger_ref.clone()),
|
||||
("Action", rule.action_ref.clone()),
|
||||
("Enabled", output::format_bool(rule.enabled)),
|
||||
("Created", output::format_timestamp(&rule.created)),
|
||||
("Updated", output::format_timestamp(&rule.updated)),
|
||||
]);
|
||||
|
||||
if let Some(conditions) = rule.conditions {
|
||||
if !conditions.is_null() {
|
||||
output::print_section("Conditions");
|
||||
println!("{}", serde_json::to_string_pretty(&conditions)?);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(action_params) = rule.action_params {
|
||||
if !action_params.is_null() {
|
||||
output::print_section("Action Parameters");
|
||||
println!("{}", serde_json::to_string_pretty(&action_params)?);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(trigger_params) = rule.trigger_params {
|
||||
if !trigger_params.is_null() {
|
||||
output::print_section("Trigger Parameters");
|
||||
println!("{}", serde_json::to_string_pretty(&trigger_params)?);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_update(
|
||||
profile: &Option<String>,
|
||||
rule_ref: String,
|
||||
label: Option<String>,
|
||||
description: Option<String>,
|
||||
conditions: Option<String>,
|
||||
action_params: Option<String>,
|
||||
trigger_params: Option<String>,
|
||||
enabled: Option<bool>,
|
||||
api_url: &Option<String>,
|
||||
output_format: OutputFormat,
|
||||
) -> Result<()> {
|
||||
let config = CliConfig::load_with_profile(profile.as_deref())?;
|
||||
let mut client = ApiClient::from_config(&config, api_url);
|
||||
|
||||
// Check that at least one field is provided
|
||||
if label.is_none()
|
||||
&& description.is_none()
|
||||
&& conditions.is_none()
|
||||
&& action_params.is_none()
|
||||
&& trigger_params.is_none()
|
||||
&& enabled.is_none()
|
||||
{
|
||||
anyhow::bail!("At least one field must be provided to update");
|
||||
}
|
||||
|
||||
// Parse JSON fields
|
||||
let conditions_json = if let Some(cond) = conditions {
|
||||
Some(serde_json::from_str(&cond)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let action_params_json = if let Some(params) = action_params {
|
||||
Some(serde_json::from_str(¶ms)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let trigger_params_json = if let Some(params) = trigger_params {
|
||||
Some(serde_json::from_str(¶ms)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct UpdateRuleRequestCli {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
label: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
description: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
conditions: Option<serde_json::Value>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
action_params: Option<serde_json::Value>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
trigger_params: Option<serde_json::Value>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
enabled: Option<bool>,
|
||||
}
|
||||
|
||||
let request = UpdateRuleRequestCli {
|
||||
label,
|
||||
description,
|
||||
conditions: conditions_json,
|
||||
action_params: action_params_json,
|
||||
trigger_params: trigger_params_json,
|
||||
enabled,
|
||||
};
|
||||
|
||||
let path = format!("/rules/{}", rule_ref);
|
||||
let rule: RuleDetail = client.put(&path, &request).await?;
|
||||
|
||||
match output_format {
|
||||
OutputFormat::Json | OutputFormat::Yaml => {
|
||||
output::print_output(&rule, output_format)?;
|
||||
}
|
||||
OutputFormat::Table => {
|
||||
output::print_success(&format!("Rule '{}' updated successfully", rule.rule_ref));
|
||||
output::print_key_value_table(vec![
|
||||
("ID", rule.id.to_string()),
|
||||
("Ref", rule.rule_ref.clone()),
|
||||
("Pack", rule.pack_ref.clone()),
|
||||
("Label", rule.label.clone()),
|
||||
("Description", rule.description.clone()),
|
||||
("Trigger", rule.trigger_ref.clone()),
|
||||
("Action", rule.action_ref.clone()),
|
||||
("Enabled", output::format_bool(rule.enabled)),
|
||||
("Updated", output::format_timestamp(&rule.updated)),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_toggle(
|
||||
profile: &Option<String>,
|
||||
rule_ref: String,
|
||||
enabled: bool,
|
||||
api_url: &Option<String>,
|
||||
output_format: OutputFormat,
|
||||
) -> Result<()> {
|
||||
let config = CliConfig::load_with_profile(profile.as_deref())?;
|
||||
let mut client = ApiClient::from_config(&config, api_url);
|
||||
|
||||
let request = UpdateRuleRequest { enabled };
|
||||
let path = format!("/rules/{}", rule_ref);
|
||||
let rule: Rule = client.patch(&path, &request).await?;
|
||||
|
||||
match output_format {
|
||||
OutputFormat::Json | OutputFormat::Yaml => {
|
||||
output::print_output(&rule, output_format)?;
|
||||
}
|
||||
OutputFormat::Table => {
|
||||
let action = if enabled { "enabled" } else { "disabled" };
|
||||
output::print_success(&format!("Rule '{}' {}", rule.rule_ref, action));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_create(
|
||||
profile: &Option<String>,
|
||||
name: String,
|
||||
pack: String,
|
||||
trigger: String,
|
||||
action: String,
|
||||
description: Option<String>,
|
||||
criteria: Option<String>,
|
||||
enabled: bool,
|
||||
api_url: &Option<String>,
|
||||
output_format: OutputFormat,
|
||||
) -> Result<()> {
|
||||
let config = CliConfig::load_with_profile(profile.as_deref())?;
|
||||
let mut client = ApiClient::from_config(&config, api_url);
|
||||
|
||||
let criteria_value = if let Some(criteria_str) = criteria {
|
||||
Some(serde_json::from_str(&criteria_str)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let request = CreateRuleRequest {
|
||||
name: name.clone(),
|
||||
pack_id: pack,
|
||||
trigger_id: trigger,
|
||||
action_id: action,
|
||||
description,
|
||||
criteria: criteria_value,
|
||||
enabled,
|
||||
};
|
||||
|
||||
let rule: Rule = client.post("/rules", &request).await?;
|
||||
|
||||
match output_format {
|
||||
OutputFormat::Json | OutputFormat::Yaml => {
|
||||
output::print_output(&rule, output_format)?;
|
||||
}
|
||||
OutputFormat::Table => {
|
||||
output::print_success(&format!("Rule '{}' created successfully", rule.rule_ref));
|
||||
output::print_info(&format!("ID: {}", rule.id));
|
||||
output::print_info(&format!("Enabled: {}", rule.enabled));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_delete(
|
||||
profile: &Option<String>,
|
||||
rule_ref: String,
|
||||
yes: bool,
|
||||
api_url: &Option<String>,
|
||||
output_format: OutputFormat,
|
||||
) -> Result<()> {
|
||||
let config = CliConfig::load_with_profile(profile.as_deref())?;
|
||||
let mut client = ApiClient::from_config(&config, api_url);
|
||||
|
||||
// Confirm deletion unless --yes is provided
|
||||
if !yes && matches!(output_format, OutputFormat::Table) {
|
||||
let confirm = dialoguer::Confirm::new()
|
||||
.with_prompt(format!(
|
||||
"Are you sure you want to delete rule '{}'?",
|
||||
rule_ref
|
||||
))
|
||||
.default(false)
|
||||
.interact()?;
|
||||
|
||||
if !confirm {
|
||||
output::print_info("Deletion cancelled");
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
let path = format!("/rules/{}", rule_ref);
|
||||
client.delete_no_response(&path).await?;
|
||||
|
||||
match output_format {
|
||||
OutputFormat::Json | OutputFormat::Yaml => {
|
||||
let msg = serde_json::json!({"message": "Rule deleted successfully"});
|
||||
output::print_output(&msg, output_format)?;
|
||||
}
|
||||
OutputFormat::Table => {
|
||||
output::print_success(&format!("Rule '{}' deleted successfully", rule_ref));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user