"use client"; import { useEffect, useMemo, useState } from "react"; import { useAuth } from "@/hooks/useAuth"; import { api, type Finding } from "@/lib/api"; import Sidebar from "@/components/Sidebar"; import Link from "next/link"; import { Shield, Filter, ArrowUpDown, CheckCircle2, Search, Download } from "lucide-react"; const SEVERITY_ORDER: Record = { critical: 0, high: 1, medium: 2, low: 3, info: 4 }; const STATUS_COLORS: Record = { open: "vault-badge-critical", in_progress: "vault-badge-medium", resolved: "vault-badge-low", verified: "vault-badge-info", accepted_risk: "vault-badge-info", }; const CATEGORY_LABELS: Record = { external_exposure: "External Exposure", cloud_posture: "Cloud Posture", credential_exposure: "Credential Exposure", digital_footprint: "Digital Footprint", web_application: "Web Application", network: "Network", identity: "Identity", third_party: "Third Party", compliance: "Compliance", other: "Other", }; type SortKey = "severity" | "created" | "title"; export default function FindingsPage() { const { tenantId, ready } = useAuth(); const [findings, setFindings] = useState([]); const [loading, setLoading] = useState(true); const [filterSeverity, setFilterSeverity] = useState("all"); const [filterStatus, setFilterStatus] = useState("open"); const [query, setQuery] = useState(""); const [sortKey, setSortKey] = useState("severity"); useEffect(() => { if (!ready || !tenantId) return; const params = [ filterSeverity !== "all" ? `severity=${filterSeverity}` : "", filterStatus !== "all" ? `status=${filterStatus}` : "", ].filter(Boolean).join("&"); api.findings(tenantId, params) .then(setFindings) .finally(() => setLoading(false)); }, [ready, tenantId, filterSeverity, filterStatus]); const visible = useMemo(() => { let list = findings; if (query.trim()) { const q = query.toLowerCase(); list = list.filter(f => f.title.toLowerCase().includes(q) || (f.ai_summary ?? "").toLowerCase().includes(q) || (f.affected_component ?? "").toLowerCase().includes(q) || (f.cve_id ?? "").toLowerCase().includes(q) ); } return [...list].sort((a, b) => { if (sortKey === "severity") { return (SEVERITY_ORDER[a.severity] ?? 9) - (SEVERITY_ORDER[b.severity] ?? 9); } if (sortKey === "created") { return new Date(b.created_at).getTime() - new Date(a.created_at).getTime(); } return a.title.localeCompare(b.title); }); }, [findings, query, sortKey]); const counts = useMemo(() => { const c: Record = { critical: 0, high: 0, medium: 0, low: 0 }; for (const f of findings) if (f.severity in c) c[f.severity]++; return c; }, [findings]); function exportCsv() { const header = ["Title", "Severity", "Status", "Category", "CVE", "CVSS", "Component", "Created"]; const rows = visible.map(f => [ f.title, f.severity, f.status, f.category, f.cve_id ?? "", f.cvss_score ?? "", f.affected_component ?? "", new Date(f.created_at).toISOString().slice(0, 10), ]); const csv = [header, ...rows] .map(r => r.map(v => `"${String(v).replace(/"/g, '""')}"`).join(",")) .join("\n"); const blob = new Blob([csv], { type: "text/csv" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = "trustos_findings.csv"; a.click(); URL.revokeObjectURL(url); } return (

Findings

All security findings across your environment

{(["critical", "high", "medium", "low"] as const).map(s => ( counts[s] > 0 && ( {counts[s]} {s} ) ))}
{/* Search + sort */}
setQuery(e.target.value)} placeholder="Search title, CVE, component…" className="w-full pl-9 pr-3.5 py-2 rounded-lg bg-vault-dark border border-vault-border text-vault-text placeholder-vault-muted text-sm focus:outline-none focus:border-vault-sapphire" />
{/* Filters */}
Filter:
{["all", "critical", "high", "medium", "low"].map(s => ( ))}
{["all", "open", "in_progress", "resolved", "verified"].map(s => ( ))} {visible.length} results
{/* Findings table */}
{loading ? (
) : visible.length === 0 ? (

No findings match these filters

) : ( {["Severity", "Title", "Category", "Status", "Priority", ""].map(h => ( ))} {visible.map(f => ( ))}
{h}
{f.severity.toUpperCase()}

{f.title}

{f.ai_summary && (

{f.ai_summary}

)}
{CATEGORY_LABELS[f.category] ?? f.category} {f.status.replace("_", " ")} {f.ai_fix_priority && ( {f.ai_fix_priority} )} View →
)}
); }