Files
compliance-scanner-agent/compliance-dashboard/src/pages/findings.rs
Sharang Parnerkar 42cabf0582
All checks were successful
CI / Format (push) Successful in 2s
CI / Clippy (push) Successful in 2m56s
CI / Security Audit (push) Successful in 1m25s
CI / Tests (push) Successful in 3m57s
feat: rag-embedding-ai-chat (#1)
Co-authored-by: Sharang Parnerkar <parnerkarsharang@gmail.com>
Reviewed-on: #1
2026-03-06 21:54:15 +00:00

155 lines
6.5 KiB
Rust

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 mut repo_filter = use_signal(String::new);
let repos = use_resource(|| async {
crate::infrastructure::repositories::fetch_repositories(1)
.await
.ok()
});
let findings = use_resource(move || {
let p = page();
let sev = severity_filter();
let typ = type_filter();
let stat = status_filter();
let repo = repo_filter();
async move {
crate::infrastructure::findings::fetch_findings(p, sev, typ, stat, repo)
.await
.ok()
}
});
rsx! {
PageHeader {
title: "Findings",
description: "Security and compliance findings across all repositories",
}
div { class: "filter-bar",
select {
onchange: move |e| { repo_filter.set(e.value()); page.set(1); },
option { value: "", "All Repositories" }
{
match &*repos.read() {
Some(Some(resp)) => rsx! {
for repo in &resp.data {
{
let id = repo.id.as_ref().map(|id| id.to_hex()).unwrap_or_default();
let name = repo.name.clone();
rsx! {
option { value: "{id}", "{name}" }
}
}
}
},
_ => rsx! {},
}
}
}
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 },
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..." }
},
}
}
}