"use client"; import { useEffect, useState, useCallback } from "react"; import { useAuth } from "@/hooks/useAuth"; import { api, type ScanStatus, type AssetHealth, type ScanFinding } from "@/lib/api"; import Sidebar from "@/components/Sidebar"; import Link from "next/link"; import { Radar, Play, Server, Globe, Cloud, AlertCircle, RefreshCw, CheckCircle2, Clock } from "lucide-react"; const ASSET_ICONS: Record = { domain: Globe, server: Server, cloud: Cloud, }; function healthColor(score: number) { if (score >= 80) return "text-emerald-400"; if (score >= 60) return "text-amber-400"; if (score >= 30) return "text-orange-400"; return "text-red-400"; } function healthBar(score: number) { if (score >= 80) return "bg-vault-emerald"; if (score >= 60) return "bg-vault-amber"; return "bg-vault-crimson"; } export default function ScansPage() { const { tenantId, role, ready } = useAuth(); const [status, setStatus] = useState(null); const [assets, setAssets] = useState([]); const [recent, setRecent] = useState([]); const [loading, setLoading] = useState(true); const [scanning, setScanning] = useState(false); const [message, setMessage] = useState(""); const load = useCallback(() => { if (!tenantId) return; Promise.all([ api.scanStatus(tenantId).then(setStatus).catch(() => {}), api.assetHealth(tenantId).then(setAssets).catch(() => {}), api.recentScanFindings(tenantId).then(setRecent).catch(() => {}), ]).finally(() => setLoading(false)); }, [tenantId]); useEffect(() => { if (!ready || !tenantId) return; load(); }, [ready, tenantId, load]); async function handleStartScan() { if (!tenantId) return; setScanning(true); setMessage(""); try { const res = await api.startScan(tenantId); setMessage(`Scan started on ${res.assets_scanned} asset${res.assets_scanned === 1 ? "" : "s"}. Results will appear below as they complete.`); // Refresh after a delay to pick up new findings setTimeout(load, 8000); } catch (e: any) { setMessage(e.message || "Failed to start scan"); } finally { setScanning(false); } } const canScan = role === "it_admin" || role === "trustos_admin"; return (

Continuous Scanning

Asset health, automated scans, and freshly discovered exposures

{canScan && ( )}
{message && (
{message}
)} {loading ? (
) : ( <> {/* Scan status summary */}
{[ { label: "Last 24h Findings", value: status?.findings_found ?? 0, icon: AlertCircle, color: "text-vault-text", }, { label: "Critical", value: status?.critical_count ?? 0, icon: AlertCircle, color: (status?.critical_count ?? 0) > 0 ? "text-red-400" : "text-emerald-400", }, { label: "High", value: status?.high_count ?? 0, icon: AlertCircle, color: (status?.high_count ?? 0) > 0 ? "text-orange-400" : "text-emerald-400", }, { label: "Last Scan", value: status?.last_scan ? new Date(status.last_scan).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : "—", icon: Clock, color: "text-vault-subtle", }, ].map(({ label, value, icon: Icon, color }) => (

{label}

{value}

))}
{/* Asset health */}

Asset Health

{assets.length === 0 ? (

No assets enrolled yet. Assets are added during Vault onboarding.

) : (
{assets.map(a => { const Icon = ASSET_ICONS[a.asset_type] ?? Server; return (

{a.asset_name}

{a.asset_value}

{a.health_score}
{a.findings_count} finding{a.findings_count === 1 ? "" : "s"} {a.critical_count > 0 ? ( {a.critical_count} critical ) : ( no criticals )}
); })}
)}
{/* Recent scanner findings */}

Recent Scanner Findings

{recent.length === 0 ? (

No automated scanner findings yet.

{canScan &&

Run a full scan to populate this feed.

}
) : (
{recent.map(f => ( {f.severity.toUpperCase()}

{f.title}

{f.affected_component && (

{f.affected_component}

)}
{new Date(f.found_at).toLocaleString([], { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" })} ))}
)}
)}
); }