Files
compliance-scanner-agent/compliance-dashboard/src/pages/impact_analysis.rs
Sharang Parnerkar 0065c7c4b2
All checks were successful
CI / Format (push) Successful in 3s
CI / Clippy (push) Successful in 3m59s
CI / Security Audit (push) Successful in 1m44s
CI / Tests (push) Successful in 5m2s
CI / Detect Changes (push) Successful in 3s
CI / Deploy Agent (push) Has been skipped
CI / Deploy Dashboard (push) Successful in 2s
CI / Deploy Docs (push) Has been skipped
CI / Deploy MCP (push) Has been skipped
feat: UI improvements with icons, back navigation, and overview cards (#7)
2026-03-09 17:09:40 +00:00

109 lines
4.8 KiB
Rust

use dioxus::prelude::*;
use dioxus_free_icons::icons::bs_icons::*;
use dioxus_free_icons::Icon;
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! {
div { class: "back-nav",
button {
class: "btn btn-ghost btn-back",
onclick: move |_| { navigator().go_back(); },
Icon { icon: BsArrowLeft, width: 16, height: 16 }
"Back"
}
}
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..." }
},
}
}
}
}