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:
Sharang Parnerkar
2026-03-02 13:30:17 +01:00
commit 0867e401bc
97 changed files with 11750 additions and 0 deletions

View 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..." }
},
}
}
}