// frontend/components/AdminTabs/SecurityTab.jsx import React, { useState } from "react"; import { scanWorkspace } from "../../utils/api.js"; /** * Security tab — runs a workspace scan via /api/security/scan-workspace * and renders findings grouped by severity. * * Best practices applied: * - Custom path input (defaults to ".") * - Loading spinner while scanning * - Error state with retry * - Empty state ("No findings") with green checkmark * - Findings grouped by severity (critical → info) * - Each finding shows file, line, CWE, recommendation * - Color-coded severity badges */ const SEVERITY_ORDER = ["critical", "high", "medium", "low", "info"]; const SEVERITY_COLORS = { critical: { bg: "#7f1d1d", text: "#fecaca", border: "#991b1b" }, high: { bg: "#9a3412", text: "#fed7aa", border: "#c2410c" }, medium: { bg: "#78350f", text: "#fde68a", border: "#a16207" }, low: { bg: "#164e63", text: "#a5f3fc", border: "#0e7490" }, info: { bg: "#1e3a5f", text: "#93c5fd", border: "#3B82F6" }, }; function SeverityBadge({ severity }) { const c = SEVERITY_COLORS[severity] || SEVERITY_COLORS.info; return ( {severity} ); } export default function SecurityTab({ showToast }) { const [path, setPath] = useState("."); const [scanning, setScanning] = useState(false); const [result, setResult] = useState(null); const [error, setError] = useState(null); const handleScan = async () => { setScanning(true); setError(null); setResult(null); try { const data = await scanWorkspace(path.trim() || "."); setResult(data); const findingsCount = data.findings?.length || 0; showToast?.( "Scan complete", findingsCount === 0 ? "No security findings." : `Found ${findingsCount} issue${findingsCount !== 1 ? "s" : ""}.` ); } catch (err) { setError(err?.message || "Scan failed"); } finally { setScanning(false); } }; // Group findings by severity const grouped = React.useMemo(() => { const out = {}; if (result?.findings) { for (const f of result.findings) { const sev = f.severity || "info"; if (!out[sev]) out[sev] = []; out[sev].push(f); } } return out; }, [result]); const totalFindings = result?.findings?.length || 0; return (

Security Scanning

Scan your workspace for vulnerabilities, secrets, and insecure patterns (OWASP Top 10).

{/* Scan controls */}
setPath(e.target.value)} disabled={scanning} placeholder="." style={{ width: "100%", padding: "8px 10px", background: "#0d0e15", border: "1px solid #2a2b36", borderRadius: "4px", color: "#fff", fontSize: "12px", fontFamily: "monospace", }} />
{/* Error state */} {error && (
Scan failed: {error}
)} {/* Results summary */} {result && (
Files Scanned
{result.files_scanned ?? 0}
Total Findings
{totalFindings}
Duration
{result.scan_duration_ms ?? 0}ms
)} {/* Empty state — no findings */} {result && totalFindings === 0 && (
No security issues found
Your workspace passed all {result.files_scanned ?? 0} file checks.
)} {/* Findings grouped by severity */} {totalFindings > 0 && SEVERITY_ORDER.filter((sev) => grouped[sev]?.length > 0).map((sev) => (

{grouped[sev].length} {sev} issue{grouped[sev].length !== 1 ? "s" : ""}

{grouped[sev].map((f, idx) => (
{f.title}
{f.cwe_id && ( {f.cwe_id} )}
{f.file_path}:{f.line_number}
{f.snippet && (
                      {f.snippet}
                    
)} {f.recommendation && (
Fix: {f.recommendation}
)}
))}
))}
); }