Files
compliance-scanner-agent/compliance-dashboard/src/pages/finding_detail.rs
Sharang Parnerkar 23ba52276b
Some checks failed
CI / Deploy Agent (push) Has been skipped
CI / Deploy Dashboard (push) Has been skipped
CI / Deploy Docs (push) Has been skipped
CI / Deploy MCP (push) Has been skipped
CI / Deploy Agent (pull_request) Has been skipped
CI / Deploy Dashboard (pull_request) Has been skipped
CI / Deploy Docs (pull_request) Has been skipped
CI / Format (push) Failing after 3s
CI / Clippy (push) Failing after 2m44s
CI / Security Audit (push) Has been skipped
CI / Tests (push) Has been skipped
CI / Format (pull_request) Failing after 3s
CI / Clippy (pull_request) Failing after 2m51s
CI / Security Audit (pull_request) Has been skipped
CI / Tests (pull_request) Has been skipped
CI / Detect Changes (push) Has been skipped
CI / Detect Changes (pull_request) Has been skipped
CI / Deploy MCP (pull_request) Has been skipped
feat: add new scanners, enhanced triage, findings refinement, and deployment tooling
- Add gitleaks secret detection, lint scanning (clippy/eslint/ruff), and LLM code review scanners
- Enhance LLM triage with multi-action support (confirm/downgrade/upgrade/dismiss),
  surrounding code context, and file-path classification confidence adjustment
- Add text search, column sorting, and bulk status update to findings dashboard
- Fix finding detail page status refresh and add developer feedback field
- Fix BSON DateTime deserialization across all models with shared serde helpers
- Add scan progress spinner with polling to repositories page
- Batch OSV.dev queries to avoid "Too many queries" errors
- Add gitleaks, semgrep, and ruff to Dockerfile.agent for deployment

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:05:31 +01:00

158 lines
6.5 KiB
Rust

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 mut 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();
let finding_id_for_feedback = id.clone();
let existing_feedback = f.developer_feedback.clone().unwrap_or_default();
rsx! {
PageHeader {
title: f.title.clone(),
description: format!("{} | {} | {}", f.scanner, f.scan_type, f.status),
}
div { class: "flex gap-2 mb-4",
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}" }
}
if let Some(confidence) = f.confidence {
span { class: "badge badge-info", "Confidence: {confidence:.1}" }
}
}
div { class: "card",
div { class: "card-header", "Description" }
p { "{f.description}" }
}
if let Some(rationale) = &f.triage_rationale {
div { class: "card",
div { class: "card-header", "Triage Rationale" }
div {
style: "display: flex; align-items: center; gap: 8px; margin-bottom: 8px;",
if let Some(action) = &f.triage_action {
span { class: "badge badge-info", "{action}" }
}
}
p { style: "color: var(--text-secondary); font-size: 14px;", "{rationale}" }
}
}
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 { "{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 { class: "flex gap-2",
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;
});
finding.restart();
},
"{status}"
}
}
}
}
}
}
div { class: "card",
div { class: "card-header", "Developer Feedback" }
p {
style: "font-size: 13px; color: var(--text-secondary); margin-bottom: 8px;",
"Share your assessment of this finding (e.g. false positive, actionable, needs context)"
}
textarea {
style: "width: 100%; min-height: 80px; background: var(--bg-primary); border: 1px solid var(--border); border-radius: 8px; padding: 10px 14px; color: var(--text-primary); font-size: 14px; resize: vertical;",
value: "{existing_feedback}",
oninput: move |e| {
let feedback = e.value();
let id = finding_id_for_feedback.clone();
spawn(async move {
let _ = crate::infrastructure::findings::update_finding_feedback(id, feedback).await;
});
},
}
}
}
}
Some(None) => rsx! {
div { class: "card", p { "Finding not found." } }
},
None => rsx! {
div { class: "loading", "Loading finding..." }
},
}
}