Files
compliance-scanner-agent/compliance-dashboard/src/pages/dast_targets.rs
Sharang Parnerkar cea8f59e10 Add DAST, graph modules, toast notifications, and dashboard enhancements
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>
2026-03-04 13:53:50 +01:00

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..." } },
}
}
}
}