Files
compliance-scanner-agent/compliance-dashboard/src/pages/dast_finding_detail.rs
T
sharang 0065c7c4b2
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

125 lines
6.0 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::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! {
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: "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..." } },
}
}
}
}