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>
80 lines
4.1 KiB
Rust
80 lines
4.1 KiB
Rust
use dioxus::prelude::*;
|
|
|
|
use crate::app::Route;
|
|
use crate::components::page_header::PageHeader;
|
|
use crate::components::severity_badge::SeverityBadge;
|
|
use crate::infrastructure::dast::fetch_dast_findings;
|
|
|
|
#[component]
|
|
pub fn DastFindingsPage() -> Element {
|
|
let findings = use_resource(|| async { fetch_dast_findings().await.ok() });
|
|
|
|
rsx! {
|
|
PageHeader {
|
|
title: "DAST Findings",
|
|
description: "Vulnerabilities discovered through dynamic application security testing",
|
|
}
|
|
|
|
div { class: "card",
|
|
match &*findings.read() {
|
|
Some(Some(data)) => {
|
|
let finding_list = &data.data;
|
|
if finding_list.is_empty() {
|
|
rsx! { p { "No DAST findings yet. Run a scan to discover vulnerabilities." } }
|
|
} else {
|
|
rsx! {
|
|
table { class: "table",
|
|
thead {
|
|
tr {
|
|
th { "Severity" }
|
|
th { "Type" }
|
|
th { "Title" }
|
|
th { "Endpoint" }
|
|
th { "Method" }
|
|
th { "Exploitable" }
|
|
}
|
|
}
|
|
tbody {
|
|
for finding in finding_list {
|
|
{
|
|
let id = finding.get("_id").and_then(|v| v.get("$oid")).and_then(|v| v.as_str()).unwrap_or("").to_string();
|
|
let severity = finding.get("severity").and_then(|v| v.as_str()).unwrap_or("info").to_string();
|
|
rsx! {
|
|
tr {
|
|
td { SeverityBadge { severity: severity } }
|
|
td {
|
|
span { class: "badge",
|
|
"{finding.get(\"vuln_type\").and_then(|v| v.as_str()).unwrap_or(\"-\")}"
|
|
}
|
|
}
|
|
td {
|
|
Link {
|
|
to: Route::DastFindingDetailPage { id: id },
|
|
"{finding.get(\"title\").and_then(|v| v.as_str()).unwrap_or(\"-\")}"
|
|
}
|
|
}
|
|
td { code { class: "text-sm", "{finding.get(\"endpoint\").and_then(|v| v.as_str()).unwrap_or(\"-\")}" } }
|
|
td { "{finding.get(\"method\").and_then(|v| v.as_str()).unwrap_or(\"-\")}" }
|
|
td {
|
|
if finding.get("exploitable").and_then(|v| v.as_bool()).unwrap_or(false) {
|
|
span { class: "badge badge-danger", "Confirmed" }
|
|
} else {
|
|
span { class: "badge", "Unconfirmed" }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
Some(None) => rsx! { p { "Failed to load findings." } },
|
|
None => rsx! { p { "Loading..." } },
|
|
}
|
|
}
|
|
}
|
|
}
|