feat: findings refinement, new scanners, and deployment tooling (#6)
Some checks failed
CI / Format (push) Successful in 3s
CI / Clippy (push) Successful in 4m3s
CI / Security Audit (push) Successful in 1m38s
CI / Tests (push) Successful in 4m44s
CI / Detect Changes (push) Successful in 2s
CI / Deploy Agent (push) Successful in 2s
CI / Deploy Dashboard (push) Successful in 2s
CI / Deploy Docs (push) Has been skipped
CI / Deploy MCP (push) Failing after 2s

This commit was merged in pull request #6.
This commit is contained in:
2026-03-09 12:53:12 +00:00
parent 32e5fc21e7
commit 46bf9de549
40 changed files with 2048 additions and 118 deletions

View File

@@ -8,7 +8,7 @@ use crate::components::severity_badge::SeverityBadge;
pub fn FindingDetailPage(id: String) -> Element {
let finding_id = id.clone();
let finding = use_resource(move || {
let mut finding = use_resource(move || {
let fid = finding_id.clone();
async move {
crate::infrastructure::findings::fetch_finding_detail(fid)
@@ -22,6 +22,8 @@ pub fn FindingDetailPage(id: String) -> Element {
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(),
@@ -39,6 +41,9 @@ pub fn FindingDetailPage(id: String) -> Element {
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",
@@ -46,6 +51,19 @@ pub fn FindingDetailPage(id: String) -> Element {
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" }
@@ -99,6 +117,7 @@ pub fn FindingDetailPage(id: String) -> Element {
spawn(async move {
let _ = crate::infrastructure::findings::update_finding_status(id, s).await;
});
finding.restart();
},
"{status}"
}
@@ -107,6 +126,25 @@ pub fn FindingDetailPage(id: String) -> Element {
}
}
}
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! {