Add DAST scanning and code knowledge graph features across the stack: - compliance-dast and compliance-graph workspace crates - Agent API handlers and routes for DAST targets/scans and graph builds - Core models and traits for DAST and graph domains - Dashboard pages for DAST targets/findings/overview and graph explorer/impact - Toast notification system with auto-dismiss for async action feedback - Button click animations and disabled states for better UX Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
146 lines
7.0 KiB
Rust
146 lines
7.0 KiB
Rust
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::<Toasts>();
|
|
|
|
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..." } },
|
|
}
|
|
}
|
|
}
|
|
}
|