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>
This commit is contained in:
145
compliance-dashboard/src/pages/dast_targets.rs
Normal file
145
compliance-dashboard/src/pages/dast_targets.rs
Normal file
@@ -0,0 +1,145 @@
|
||||
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..." } },
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user