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:
124
compliance-dashboard/src/pages/findings.rs
Normal file
124
compliance-dashboard/src/pages/findings.rs
Normal file
@@ -0,0 +1,124 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::app::Route;
|
||||
use crate::components::page_header::PageHeader;
|
||||
use crate::components::pagination::Pagination;
|
||||
use crate::components::severity_badge::SeverityBadge;
|
||||
|
||||
#[component]
|
||||
pub fn FindingsPage() -> Element {
|
||||
let mut page = use_signal(|| 1u64);
|
||||
let mut severity_filter = use_signal(String::new);
|
||||
let mut type_filter = use_signal(String::new);
|
||||
let mut status_filter = use_signal(String::new);
|
||||
|
||||
let findings = use_resource(move || {
|
||||
let p = page();
|
||||
let sev = severity_filter();
|
||||
let typ = type_filter();
|
||||
let stat = status_filter();
|
||||
async move {
|
||||
crate::infrastructure::findings::fetch_findings(p, sev, typ, stat, String::new()).await.ok()
|
||||
}
|
||||
});
|
||||
|
||||
rsx! {
|
||||
PageHeader {
|
||||
title: "Findings",
|
||||
description: "Security and compliance findings across all repositories",
|
||||
}
|
||||
|
||||
div { class: "filter-bar",
|
||||
select {
|
||||
onchange: move |e| { severity_filter.set(e.value()); page.set(1); },
|
||||
option { value: "", "All Severities" }
|
||||
option { value: "critical", "Critical" }
|
||||
option { value: "high", "High" }
|
||||
option { value: "medium", "Medium" }
|
||||
option { value: "low", "Low" }
|
||||
option { value: "info", "Info" }
|
||||
}
|
||||
select {
|
||||
onchange: move |e| { type_filter.set(e.value()); page.set(1); },
|
||||
option { value: "", "All Types" }
|
||||
option { value: "sast", "SAST" }
|
||||
option { value: "sbom", "SBOM" }
|
||||
option { value: "cve", "CVE" }
|
||||
option { value: "gdpr", "GDPR" }
|
||||
option { value: "oauth", "OAuth" }
|
||||
}
|
||||
select {
|
||||
onchange: move |e| { status_filter.set(e.value()); page.set(1); },
|
||||
option { value: "", "All Statuses" }
|
||||
option { value: "open", "Open" }
|
||||
option { value: "triaged", "Triaged" }
|
||||
option { value: "resolved", "Resolved" }
|
||||
option { value: "false_positive", "False Positive" }
|
||||
option { value: "ignored", "Ignored" }
|
||||
}
|
||||
}
|
||||
|
||||
match &*findings.read() {
|
||||
Some(Some(resp)) => {
|
||||
let total_pages = resp.total.unwrap_or(0).div_ceil(20).max(1);
|
||||
rsx! {
|
||||
div { class: "card",
|
||||
div { class: "table-wrapper",
|
||||
table {
|
||||
thead {
|
||||
tr {
|
||||
th { "Severity" }
|
||||
th { "Title" }
|
||||
th { "Type" }
|
||||
th { "Scanner" }
|
||||
th { "File" }
|
||||
th { "Status" }
|
||||
}
|
||||
}
|
||||
tbody {
|
||||
for finding in &resp.data {
|
||||
{
|
||||
let id = finding.id.as_ref().map(|id| id.to_hex()).unwrap_or_default();
|
||||
rsx! {
|
||||
tr {
|
||||
td { SeverityBadge { severity: finding.severity.to_string() } }
|
||||
td {
|
||||
Link {
|
||||
to: Route::FindingDetailPage { id: id },
|
||||
style: "color: var(--accent); text-decoration: none;",
|
||||
"{finding.title}"
|
||||
}
|
||||
}
|
||||
td { "{finding.scan_type}" }
|
||||
td { "{finding.scanner}" }
|
||||
td {
|
||||
style: "font-family: monospace; font-size: 12px;",
|
||||
"{finding.file_path.as_deref().unwrap_or(\"-\")}"
|
||||
}
|
||||
td {
|
||||
span { class: "badge badge-info", "{finding.status}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Pagination {
|
||||
current_page: page(),
|
||||
total_pages: total_pages,
|
||||
on_page_change: move |p| page.set(p),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Some(None) => rsx! {
|
||||
div { class: "card", p { "Failed to load findings." } }
|
||||
},
|
||||
None => rsx! {
|
||||
div { class: "loading", "Loading findings..." }
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user