import React, { useState, useEffect } from 'react'; import { Search, FileText, AlertCircle, Download, Upload, Eye, Filter, CheckCircle, XCircle, Loader, Trash2, Plus } from 'lucide-react'; const API_BASE = process.env.REACT_APP_API_BASE || 'http://localhost:3001/api'; const API_HOST = process.env.REACT_APP_API_HOST || 'http://localhost:3001'; const severityLevels = ['All Severities', 'Critical', 'High', 'Medium', 'Low']; export default function App() { const [searchQuery, setSearchQuery] = useState(''); const [selectedVendor, setSelectedVendor] = useState('All Vendors'); const [selectedSeverity, setSelectedSeverity] = useState('All Severities'); const [selectedCVE, setSelectedCVE] = useState(null); const [selectedVendorView, setSelectedVendorView] = useState(null); const [selectedDocuments, setSelectedDocuments] = useState([]); const [cves, setCves] = useState([]); const [vendors, setVendors] = useState(['All Vendors']); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [cveDocuments, setCveDocuments] = useState({}); const [quickCheckCVE, setQuickCheckCVE] = useState(''); const [quickCheckResult, setQuickCheckResult] = useState(null); const [showAddCVE, setShowAddCVE] = useState(false); const [newCVE, setNewCVE] = useState({ cve_id: '', vendor: '', severity: 'Medium', description: '', published_date: new Date().toISOString().split('T')[0] }); const [uploadingFile, setUploadingFile] = useState(false); // Fetch CVEs from API useEffect(() => { fetchCVEs(); fetchVendors(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // Refetch when filters change useEffect(() => { fetchCVEs(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [searchQuery, selectedVendor, selectedSeverity]); const fetchCVEs = async () => { setLoading(true); setError(null); try { const params = new URLSearchParams(); if (searchQuery) params.append('search', searchQuery); if (selectedVendor !== 'All Vendors') params.append('vendor', selectedVendor); if (selectedSeverity !== 'All Severities') params.append('severity', selectedSeverity); const response = await fetch(`${API_BASE}/cves?${params}`); if (!response.ok) throw new Error('Failed to fetch CVEs'); const data = await response.json(); setCves(data); } catch (err) { setError(err.message); console.error('Error fetching CVEs:', err); } finally { setLoading(false); } }; const fetchVendors = async () => { try { const response = await fetch(`${API_BASE}/vendors`); if (!response.ok) throw new Error('Failed to fetch vendors'); const data = await response.json(); setVendors(['All Vendors', ...data]); } catch (err) { console.error('Error fetching vendors:', err); } }; const fetchDocuments = async (cveId, vendor) => { const key = `${cveId}-${vendor}`; if (cveDocuments[key]) return; try { const response = await fetch(`${API_BASE}/cves/${cveId}/documents?vendor=${vendor}`); if (!response.ok) throw new Error('Failed to fetch documents'); const data = await response.json(); setCveDocuments(prev => ({ ...prev, [key]: data })); } catch (err) { console.error('Error fetching documents:', err); } }; const quickCheckCVEStatus = async () => { if (!quickCheckCVE.trim()) return; try { const response = await fetch(`${API_BASE}/cves/check/${quickCheckCVE.trim()}`); if (!response.ok) throw new Error('Failed to check CVE'); const data = await response.json(); setQuickCheckResult(data); } catch (err) { console.error('Error checking CVE:', err); setQuickCheckResult({ error: err.message }); } }; const handleViewDocuments = async (cveId, vendor) => { const key = `${cveId}-${vendor}`; if (selectedCVE === cveId && selectedVendorView === vendor) { setSelectedCVE(null); setSelectedVendorView(null); } else { setSelectedCVE(cveId); setSelectedVendorView(vendor); await fetchDocuments(cveId, vendor); } }; const getSeverityColor = (severity) => { const colors = { 'Critical': 'bg-red-100 text-red-800', 'High': 'bg-orange-100 text-orange-800', 'Medium': 'bg-yellow-100 text-yellow-800', 'Low': 'bg-blue-100 text-blue-800' }; return colors[severity] || 'bg-gray-100 text-gray-800'; }; const toggleDocumentSelection = (docId) => { setSelectedDocuments(prev => prev.includes(docId) ? prev.filter(id => id !== docId) : [...prev, docId] ); }; const exportSelectedDocuments = () => { alert(`Exporting ${selectedDocuments.length} documents for report attachment`); }; const handleAddCVE = async (e) => { e.preventDefault(); try { const response = await fetch(`${API_BASE}/cves`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(newCVE) }); if (!response.ok) { const data = await response.json(); throw new Error(data.error || 'Failed to add CVE'); } alert(`CVE ${newCVE.cve_id} added successfully for vendor: ${newCVE.vendor}!`); setShowAddCVE(false); setNewCVE({ cve_id: '', vendor: '', severity: 'Medium', description: '', published_date: new Date().toISOString().split('T')[0] }); fetchCVEs(); } catch (err) { alert(`Error: ${err.message}`); } }; const handleFileUpload = async (cveId, vendor) => { const fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.accept = '.pdf,.png,.jpg,.jpeg,.txt,.doc,.docx'; fileInput.onchange = async (e) => { const file = e.target.files[0]; if (!file) return; const docType = prompt( 'Document type (advisory, email, screenshot, patch, other):', 'advisory' ); if (!docType) return; const notes = prompt('Notes (optional):'); setUploadingFile(true); const formData = new FormData(); formData.append('file', file); formData.append('cveId', cveId); formData.append('vendor', vendor); formData.append('type', docType); if (notes) formData.append('notes', notes); try { const response = await fetch(`${API_BASE}/cves/${cveId}/documents`, { method: 'POST', body: formData }); if (!response.ok) throw new Error('Failed to upload document'); alert(`Document uploaded successfully!`); const key = `${cveId}-${vendor}`; delete cveDocuments[key]; await fetchDocuments(cveId, vendor); fetchCVEs(); } catch (err) { alert(`Error: ${err.message}`); } finally { setUploadingFile(false); } }; fileInput.click(); }; const handleDeleteDocument = async (docId, cveId, vendor) => { if (!window.confirm('Are you sure you want to delete this document?')) { return; } try { const response = await fetch(`${API_BASE}/documents/${docId}`, { method: 'DELETE' }); if (!response.ok) throw new Error('Failed to delete document'); alert('Document deleted successfully!'); const key = `${cveId}-${vendor}`; delete cveDocuments[key]; await fetchDocuments(cveId, vendor); fetchCVEs(); } catch (err) { alert(`Error: ${err.message}`); } }; // Group CVEs by CVE ID const groupedCVEs = cves.reduce((acc, cve) => { if (!acc[cve.cve_id]) { acc[cve.cve_id] = []; } acc[cve.cve_id].push(cve); return acc; }, {}); const filteredGroupedCVEs = groupedCVEs; return (
{/* Header */}

CVE Dashboard

Query vulnerabilities, manage vendors, and attach documentation

{/* Add CVE Modal */} {showAddCVE && (

Add CVE Entry

Tip: You can add the same CVE-ID multiple times with different vendors. Each vendor will have its own documents folder.

setNewCVE({...newCVE, cve_id: e.target.value.toUpperCase()})} className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#0476D9] focus:border-transparent" />

Can be the same as existing CVE if adding another vendor

setNewCVE({...newCVE, vendor: e.target.value})} className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#0476D9] focus:border-transparent" />

Must be unique for this CVE-ID