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:
Sharang Parnerkar
2026-03-04 13:53:50 +01:00
parent 03ee69834d
commit cea8f59e10
69 changed files with 8745 additions and 54 deletions

View File

@@ -0,0 +1,113 @@
use dioxus::prelude::*;
use crate::components::page_header::PageHeader;
use crate::components::severity_badge::SeverityBadge;
use crate::infrastructure::dast::fetch_dast_finding_detail;
#[component]
pub fn DastFindingDetailPage(id: String) -> Element {
let finding = use_resource(move || {
let fid = id.clone();
async move { fetch_dast_finding_detail(fid).await.ok() }
});
rsx! {
PageHeader {
title: "DAST Finding Detail",
description: "Full evidence and details for a dynamic security finding",
}
div { class: "card",
match &*finding.read() {
Some(Some(resp)) => {
let f = resp.data.clone();
let severity = f.get("severity").and_then(|v| v.as_str()).unwrap_or("info").to_string();
rsx! {
div { class: "flex items-center gap-4 mb-4",
SeverityBadge { severity: severity }
h2 { "{f.get(\"title\").and_then(|v| v.as_str()).unwrap_or(\"Unknown Finding\")}" }
}
div { class: "grid grid-cols-2 gap-4 mb-4",
div {
strong { "Vulnerability Type: " }
span { class: "badge", "{f.get(\"vuln_type\").and_then(|v| v.as_str()).unwrap_or(\"-\")}" }
}
div {
strong { "CWE: " }
span { "{f.get(\"cwe\").and_then(|v| v.as_str()).unwrap_or(\"N/A\")}" }
}
div {
strong { "Endpoint: " }
code { "{f.get(\"endpoint\").and_then(|v| v.as_str()).unwrap_or(\"-\")}" }
}
div {
strong { "Method: " }
span { "{f.get(\"method\").and_then(|v| v.as_str()).unwrap_or(\"-\")}" }
}
div {
strong { "Parameter: " }
code { "{f.get(\"parameter\").and_then(|v| v.as_str()).unwrap_or(\"N/A\")}" }
}
div {
strong { "Exploitable: " }
if f.get("exploitable").and_then(|v| v.as_bool()).unwrap_or(false) {
span { class: "badge badge-danger", "Confirmed" }
} else {
span { class: "badge", "Unconfirmed" }
}
}
}
h3 { "Description" }
p { "{f.get(\"description\").and_then(|v| v.as_str()).unwrap_or(\"-\")}" }
if let Some(remediation) = f.get("remediation").and_then(|v| v.as_str()) {
h3 { class: "mt-4", "Remediation" }
p { "{remediation}" }
}
h3 { class: "mt-4", "Evidence" }
if let Some(evidence_list) = f.get("evidence").and_then(|v| v.as_array()) {
for (i, evidence) in evidence_list.iter().enumerate() {
div { class: "card mb-3",
h4 { "Evidence #{i + 1}" }
div { class: "grid grid-cols-2 gap-2",
div {
strong { "Request: " }
code { "{evidence.get(\"request_method\").and_then(|v| v.as_str()).unwrap_or(\"-\")} {evidence.get(\"request_url\").and_then(|v| v.as_str()).unwrap_or(\"-\")}" }
}
div {
strong { "Response Status: " }
span { "{evidence.get(\"response_status\").and_then(|v| v.as_u64()).unwrap_or(0)}" }
}
}
if let Some(payload) = evidence.get("payload").and_then(|v| v.as_str()) {
div { class: "mt-2",
strong { "Payload: " }
code { class: "block bg-gray-900 text-green-400 p-2 rounded mt-1",
"{payload}"
}
}
}
if let Some(snippet) = evidence.get("response_snippet").and_then(|v| v.as_str()) {
div { class: "mt-2",
strong { "Response Snippet: " }
pre { class: "block bg-gray-900 text-gray-300 p-2 rounded mt-1 overflow-x-auto text-sm",
"{snippet}"
}
}
}
}
}
} else {
p { "No evidence collected." }
}
}
},
Some(None) => rsx! { p { "Finding not found." } },
None => rsx! { p { "Loading..." } },
}
}
}
}