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:
97
compliance-dashboard/src/pages/impact_analysis.rs
Normal file
97
compliance-dashboard/src/pages/impact_analysis.rs
Normal file
@@ -0,0 +1,97 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::components::page_header::PageHeader;
|
||||
use crate::infrastructure::graph::fetch_impact;
|
||||
|
||||
#[component]
|
||||
pub fn ImpactAnalysisPage(repo_id: String, finding_id: String) -> Element {
|
||||
let impact_data = use_resource(move || {
|
||||
let rid = repo_id.clone();
|
||||
let fid = finding_id.clone();
|
||||
async move { fetch_impact(rid, fid).await.ok() }
|
||||
});
|
||||
|
||||
rsx! {
|
||||
PageHeader {
|
||||
title: "Impact Analysis",
|
||||
description: "Blast radius and affected entry points for a security finding",
|
||||
}
|
||||
|
||||
div { class: "card",
|
||||
match &*impact_data.read() {
|
||||
Some(Some(resp)) => {
|
||||
let impact = resp.data.clone().unwrap_or_default();
|
||||
rsx! {
|
||||
div { class: "grid grid-cols-2 gap-4 mb-4",
|
||||
div { class: "stat-card",
|
||||
div { class: "stat-value",
|
||||
"{impact.get(\"blast_radius\").and_then(|v| v.as_u64()).unwrap_or(0)}"
|
||||
}
|
||||
div { class: "stat-label", "Blast Radius (nodes affected)" }
|
||||
}
|
||||
div { class: "stat-card",
|
||||
div { class: "stat-value",
|
||||
"{impact.get(\"affected_entry_points\").and_then(|v| v.as_array()).map(|a| a.len()).unwrap_or(0)}"
|
||||
}
|
||||
div { class: "stat-label", "Entry Points Affected" }
|
||||
}
|
||||
}
|
||||
|
||||
h3 { "Affected Entry Points" }
|
||||
if let Some(entries) = impact.get("affected_entry_points").and_then(|v| v.as_array()) {
|
||||
if entries.is_empty() {
|
||||
p { class: "text-muted", "No entry points affected." }
|
||||
} else {
|
||||
ul { class: "list",
|
||||
for entry in entries {
|
||||
li { "{entry.as_str().unwrap_or(\"-\")}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
h3 { class: "mt-4", "Call Chains" }
|
||||
if let Some(chains) = impact.get("call_chains").and_then(|v| v.as_array()) {
|
||||
if chains.is_empty() {
|
||||
p { class: "text-muted", "No call chains found." }
|
||||
} else {
|
||||
for (i, chain) in chains.iter().enumerate() {
|
||||
div { class: "card mb-2",
|
||||
strong { "Chain {i + 1}: " }
|
||||
if let Some(steps) = chain.as_array() {
|
||||
for (j, step) in steps.iter().enumerate() {
|
||||
span { "{step.as_str().unwrap_or(\"-\")}" }
|
||||
if j < steps.len() - 1 {
|
||||
span { class: "text-muted", " → " }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
h3 { class: "mt-4", "Direct Callers" }
|
||||
if let Some(callers) = impact.get("direct_callers").and_then(|v| v.as_array()) {
|
||||
if callers.is_empty() {
|
||||
p { class: "text-muted", "No direct callers." }
|
||||
} else {
|
||||
ul { class: "list",
|
||||
for caller in callers {
|
||||
li { code { "{caller.as_str().unwrap_or(\"-\")}" } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Some(None) => rsx! {
|
||||
p { "No impact analysis data available for this finding." }
|
||||
},
|
||||
None => rsx! {
|
||||
p { "Loading impact analysis..." }
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user