Initial commit: Compliance Scanner Agent
Autonomous security and compliance scanning agent for git repositories. Features: SAST (Semgrep), SBOM (Syft), CVE monitoring (OSV.dev/NVD), GDPR/OAuth pattern detection, LLM triage, issue creation (GitHub/GitLab/Jira), PR reviews, and Dioxus fullstack dashboard. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
117
compliance-dashboard/src/pages/finding_detail.rs
Normal file
117
compliance-dashboard/src/pages/finding_detail.rs
Normal file
@@ -0,0 +1,117 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::components::code_snippet::CodeSnippet;
|
||||
use crate::components::page_header::PageHeader;
|
||||
use crate::components::severity_badge::SeverityBadge;
|
||||
|
||||
#[component]
|
||||
pub fn FindingDetailPage(id: String) -> Element {
|
||||
let finding_id = id.clone();
|
||||
|
||||
let finding = use_resource(move || {
|
||||
let fid = finding_id.clone();
|
||||
async move {
|
||||
crate::infrastructure::findings::fetch_finding_detail(fid).await.ok()
|
||||
}
|
||||
});
|
||||
|
||||
let snapshot = finding.read().clone();
|
||||
|
||||
match snapshot {
|
||||
Some(Some(f)) => {
|
||||
let finding_id_for_status = id.clone();
|
||||
rsx! {
|
||||
PageHeader {
|
||||
title: f.title.clone(),
|
||||
description: format!("{} | {} | {}", f.scanner, f.scan_type, f.status),
|
||||
}
|
||||
|
||||
div { style: "display: flex; gap: 8px; margin-bottom: 16px;",
|
||||
SeverityBadge { severity: f.severity.to_string() }
|
||||
if let Some(cwe) = &f.cwe {
|
||||
span { class: "badge badge-info", "{cwe}" }
|
||||
}
|
||||
if let Some(cve) = &f.cve {
|
||||
span { class: "badge badge-high", "{cve}" }
|
||||
}
|
||||
if let Some(score) = f.cvss_score {
|
||||
span { class: "badge badge-medium", "CVSS: {score}" }
|
||||
}
|
||||
}
|
||||
|
||||
div { class: "card",
|
||||
div { class: "card-header", "Description" }
|
||||
p { style: "line-height: 1.6;", "{f.description}" }
|
||||
}
|
||||
|
||||
if let Some(code) = &f.code_snippet {
|
||||
div { class: "card",
|
||||
div { class: "card-header", "Code Evidence" }
|
||||
CodeSnippet {
|
||||
code: code.clone(),
|
||||
file_path: f.file_path.clone().unwrap_or_default(),
|
||||
line_number: f.line_number.unwrap_or(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(remediation) = &f.remediation {
|
||||
div { class: "card",
|
||||
div { class: "card-header", "Remediation" }
|
||||
p { style: "line-height: 1.6;", "{remediation}" }
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(fix) = &f.suggested_fix {
|
||||
div { class: "card",
|
||||
div { class: "card-header", "Suggested Fix" }
|
||||
CodeSnippet { code: fix.clone() }
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(url) = &f.tracker_issue_url {
|
||||
div { class: "card",
|
||||
div { class: "card-header", "Linked Issue" }
|
||||
a {
|
||||
href: "{url}",
|
||||
target: "_blank",
|
||||
style: "color: var(--accent);",
|
||||
"{url}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div { class: "card",
|
||||
div { class: "card-header", "Update Status" }
|
||||
div { style: "display: flex; gap: 8px;",
|
||||
for status in ["open", "triaged", "resolved", "false_positive", "ignored"] {
|
||||
{
|
||||
let status_str = status.to_string();
|
||||
let id_clone = finding_id_for_status.clone();
|
||||
rsx! {
|
||||
button {
|
||||
class: "btn btn-ghost",
|
||||
onclick: move |_| {
|
||||
let s = status_str.clone();
|
||||
let id = id_clone.clone();
|
||||
spawn(async move {
|
||||
let _ = crate::infrastructure::findings::update_finding_status(id, s).await;
|
||||
});
|
||||
},
|
||||
"{status}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Some(None) => rsx! {
|
||||
div { class: "card", p { "Finding not found." } }
|
||||
},
|
||||
None => rsx! {
|
||||
div { class: "loading", "Loading finding..." }
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user