Files
compliance-scanner-agent/compliance-dashboard/src/pages/dast_overview.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

108 lines
4.4 KiB
Rust

use dioxus::prelude::*;
use crate::app::Route;
use crate::components::page_header::PageHeader;
use crate::infrastructure::dast::{fetch_dast_findings, fetch_dast_scan_runs};
#[component]
pub fn DastOverviewPage() -> Element {
let scan_runs = use_resource(|| async { fetch_dast_scan_runs().await.ok() });
let findings = use_resource(|| async { fetch_dast_findings().await.ok() });
rsx! {
PageHeader {
title: "DAST Overview",
description: "Dynamic Application Security Testing — scan running applications for vulnerabilities",
}
div { class: "grid grid-cols-3 gap-4 mb-6",
div { class: "stat-card",
div { class: "stat-value",
match &*scan_runs.read() {
Some(Some(data)) => {
let count = data.total.unwrap_or(0);
rsx! { "{count}" }
},
_ => rsx! { "" },
}
}
div { class: "stat-label", "Total Scans" }
}
div { class: "stat-card",
div { class: "stat-value",
match &*findings.read() {
Some(Some(data)) => {
let count = data.total.unwrap_or(0);
rsx! { "{count}" }
},
_ => rsx! { "" },
}
}
div { class: "stat-label", "DAST Findings" }
}
div { class: "stat-card",
div { class: "stat-value", "" }
div { class: "stat-label", "Active Targets" }
}
}
div { class: "flex gap-4 mb-4",
Link {
to: Route::DastTargetsPage {},
class: "btn btn-primary",
"Manage Targets"
}
Link {
to: Route::DastFindingsPage {},
class: "btn btn-secondary",
"View Findings"
}
}
div { class: "card",
h3 { "Recent Scan Runs" }
match &*scan_runs.read() {
Some(Some(data)) => {
let runs = &data.data;
if runs.is_empty() {
rsx! { p { "No scan runs yet." } }
} else {
rsx! {
table { class: "table",
thead {
tr {
th { "Target" }
th { "Status" }
th { "Phase" }
th { "Findings" }
th { "Exploitable" }
th { "Started" }
}
}
tbody {
for run in runs {
tr {
td { "{run.get(\"target_id\").and_then(|v| v.as_str()).unwrap_or(\"-\")}" }
td {
span { class: "badge",
"{run.get(\"status\").and_then(|v| v.as_str()).unwrap_or(\"unknown\")}"
}
}
td { "{run.get(\"current_phase\").and_then(|v| v.as_str()).unwrap_or(\"-\")}" }
td { "{run.get(\"findings_count\").and_then(|v| v.as_u64()).unwrap_or(0)}" }
td { "{run.get(\"exploitable_count\").and_then(|v| v.as_u64()).unwrap_or(0)}" }
td { "{run.get(\"started_at\").and_then(|v| v.as_str()).unwrap_or(\"-\")}" }
}
}
}
}
}
}
},
Some(None) => rsx! { p { "Failed to load scan runs." } },
None => rsx! { p { "Loading..." } },
}
}
}
}