use dioxus::prelude::*; use crate::components::page_header::PageHeader; use crate::components::toast::{ToastType, Toasts}; use crate::infrastructure::dast::{add_dast_target, fetch_dast_targets, trigger_dast_scan}; #[component] pub fn DastTargetsPage() -> Element { let mut targets = use_resource(|| async { fetch_dast_targets().await.ok() }); let mut toasts = use_context::(); let mut show_form = use_signal(|| false); let mut new_name = use_signal(String::new); let mut new_url = use_signal(String::new); rsx! { PageHeader { title: "DAST Targets", description: "Configure target applications for dynamic security testing", } div { class: "mb-4", button { class: "btn btn-primary", onclick: move |_| show_form.set(!show_form()), if show_form() { "Cancel" } else { "Add Target" } } } if show_form() { div { class: "card mb-4", h3 { "Add New Target" } div { class: "form-group", label { "Name" } input { class: "input", r#type: "text", placeholder: "My Web App", value: "{new_name}", oninput: move |e| new_name.set(e.value()), } } div { class: "form-group", label { "Base URL" } input { class: "input", r#type: "text", placeholder: "https://example.com", value: "{new_url}", oninput: move |e| new_url.set(e.value()), } } button { class: "btn btn-primary", onclick: move |_| { let name = new_name(); let url = new_url(); spawn(async move { match add_dast_target(name, url).await { Ok(_) => { toasts.push(ToastType::Success, "Target created"); targets.restart(); } Err(e) => toasts.push(ToastType::Error, e.to_string()), } }); show_form.set(false); new_name.set(String::new()); new_url.set(String::new()); }, "Create Target" } } } div { class: "card", h3 { "Configured Targets" } match &*targets.read() { Some(Some(data)) => { let target_list = &data.data; if target_list.is_empty() { rsx! { p { "No DAST targets configured. Add one to get started." } } } else { rsx! { table { class: "table", thead { tr { th { "Name" } th { "URL" } th { "Type" } th { "Rate Limit" } th { "Destructive" } th { "Actions" } } } tbody { for target in target_list { { let target_id = target.get("_id").and_then(|v| v.get("$oid")).and_then(|v| v.as_str()).unwrap_or("").to_string(); rsx! { tr { td { "{target.get(\"name\").and_then(|v| v.as_str()).unwrap_or(\"-\")}" } td { code { "{target.get(\"base_url\").and_then(|v| v.as_str()).unwrap_or(\"-\")}" } } td { "{target.get(\"target_type\").and_then(|v| v.as_str()).unwrap_or(\"-\")}" } td { "{target.get(\"rate_limit\").and_then(|v| v.as_u64()).unwrap_or(0)} req/s" } td { if target.get("allow_destructive").and_then(|v| v.as_bool()).unwrap_or(false) { span { class: "badge badge-danger", "Yes" } } else { span { class: "badge badge-success", "No" } } } td { button { class: "btn btn-sm", onclick: { let tid = target_id.clone(); move |_| { let tid = tid.clone(); spawn(async move { match trigger_dast_scan(tid).await { Ok(_) => toasts.push(ToastType::Success, "DAST scan triggered"), Err(e) => toasts.push(ToastType::Error, e.to_string()), } }); } }, "Scan" } } } } } } } } } } }, Some(None) => rsx! { p { "Failed to load targets." } }, None => rsx! { p { "Loading..." } }, } } } }