import React, { useState, useEffect } from 'react'; import { Search, FileText, AlertCircle, Download, Upload, Eye, Filter, CheckCircle, XCircle, Loader, Trash2, Plus, RefreshCw, Edit2, ChevronDown } from 'lucide-react'; import { useAuth } from './contexts/AuthContext'; import LoginForm from './components/LoginForm'; import UserMenu from './components/UserMenu'; import UserManagement from './components/UserManagement'; import AuditLog from './components/AuditLog'; import NvdSyncModal from './components/NvdSyncModal'; 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 { isAuthenticated, loading: authLoading, canWrite, isAdmin } = useAuth(); 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 [showUserManagement, setShowUserManagement] = useState(false); const [showAuditLog, setShowAuditLog] = useState(false); const [showNvdSync, setShowNvdSync] = 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); const [nvdLoading, setNvdLoading] = useState(false); const [nvdError, setNvdError] = useState(null); const [nvdAutoFilled, setNvdAutoFilled] = useState(false); const [showEditCVE, setShowEditCVE] = useState(false); const [editingCVE, setEditingCVE] = useState(null); const [editForm, setEditForm] = useState({ cve_id: '', vendor: '', severity: 'Medium', description: '', published_date: '', status: 'Open' }); const [editNvdLoading, setEditNvdLoading] = useState(false); const [editNvdError, setEditNvdError] = useState(null); const [editNvdAutoFilled, setEditNvdAutoFilled] = useState(false); const [expandedCVEs, setExpandedCVEs] = useState({}); const [jiraTickets, setJiraTickets] = useState([]); const [showAddTicket, setShowAddTicket] = useState(false); const [showEditTicket, setShowEditTicket] = useState(false); const [editingTicket, setEditingTicket] = useState(null); const [ticketForm, setTicketForm] = useState({ cve_id: '', vendor: '', ticket_key: '', url: '', summary: '', status: 'Open' }); // For adding ticket from within a CVE card const [addTicketContext, setAddTicketContext] = useState(null); // { cve_id, vendor } const toggleCVEExpand = (cveId) => { setExpandedCVEs(prev => ({ ...prev, [cveId]: !prev[cveId] })); }; const lookupNVD = async (cveId) => { const trimmed = cveId.trim(); if (!/^CVE-\d{4}-\d{4,}$/.test(trimmed)) return; setNvdLoading(true); setNvdError(null); setNvdAutoFilled(false); try { const response = await fetch(`${API_BASE}/nvd/lookup/${encodeURIComponent(trimmed)}`, { credentials: 'include' }); if (!response.ok) { const data = await response.json(); throw new Error(data.error || 'NVD lookup failed'); } const data = await response.json(); setNewCVE(prev => ({ ...prev, description: prev.description || data.description, severity: data.severity, published_date: data.published_date || prev.published_date })); setNvdAutoFilled(true); } catch (err) { setNvdError(err.message); } finally { setNvdLoading(false); } }; 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}`, { credentials: 'include' }); 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`, { credentials: 'include' }); 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 fetchJiraTickets = async () => { try { const response = await fetch(`${API_BASE}/jira-tickets`, { credentials: 'include' }); if (!response.ok) throw new Error('Failed to fetch JIRA tickets'); const data = await response.json(); setJiraTickets(data); } catch (err) { console.error('Error fetching JIRA tickets:', 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}`, { credentials: 'include' }); 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()}`, { credentials: 'include' }); 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) => { if (selectedCVE === cveId && selectedVendorView === vendor) { setSelectedCVE(null); setSelectedVendorView(null); } else { setSelectedCVE(cveId); setSelectedVendorView(vendor); await fetchDocuments(cveId, vendor); } }; 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' }, credentials: 'include', 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] }); setNvdLoading(false); setNvdError(null); setNvdAutoFilled(false); 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,.gif,.bmp,.tiff,.tif,.txt,.csv,.log,.msg,.eml,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.odt,.ods,.odp,.rtf,.html,.htm,.xml,.json,.yaml,.yml,.zip,.gz,.tar,.7z'; 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', credentials: 'include', 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', credentials: 'include' }); 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}`); } }; const handleEditCVE = (cve) => { setEditingCVE(cve); setEditForm({ cve_id: cve.cve_id, vendor: cve.vendor, severity: cve.severity, description: cve.description || '', published_date: cve.published_date || '', status: cve.status || 'Open' }); setEditNvdLoading(false); setEditNvdError(null); setEditNvdAutoFilled(false); setShowEditCVE(true); }; const handleEditCVESubmit = async (e) => { e.preventDefault(); if (!editingCVE) return; try { const body = {}; if (editForm.cve_id !== editingCVE.cve_id) body.cve_id = editForm.cve_id; if (editForm.vendor !== editingCVE.vendor) body.vendor = editForm.vendor; if (editForm.severity !== editingCVE.severity) body.severity = editForm.severity; if (editForm.description !== (editingCVE.description || '')) body.description = editForm.description; if (editForm.published_date !== (editingCVE.published_date || '')) body.published_date = editForm.published_date; if (editForm.status !== (editingCVE.status || 'Open')) body.status = editForm.status; if (Object.keys(body).length === 0) { alert('No changes detected.'); return; } const response = await fetch(`${API_BASE}/cves/${editingCVE.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify(body) }); if (!response.ok) { const data = await response.json(); throw new Error(data.error || 'Failed to update CVE'); } alert('CVE updated successfully!'); setShowEditCVE(false); setEditingCVE(null); fetchCVEs(); fetchVendors(); } catch (err) { alert(`Error: ${err.message}`); } }; const lookupNVDForEdit = async (cveId) => { const trimmed = cveId.trim(); if (!/^CVE-\d{4}-\d{4,}$/.test(trimmed)) return; setEditNvdLoading(true); setEditNvdError(null); setEditNvdAutoFilled(false); try { const response = await fetch(`${API_BASE}/nvd/lookup/${encodeURIComponent(trimmed)}`, { credentials: 'include' }); if (!response.ok) { const data = await response.json(); throw new Error(data.error || 'NVD lookup failed'); } const data = await response.json(); setEditForm(prev => ({ ...prev, description: data.description || prev.description, severity: data.severity || prev.severity, published_date: data.published_date || prev.published_date })); setEditNvdAutoFilled(true); } catch (err) { setEditNvdError(err.message); } finally { setEditNvdLoading(false); } }; const handleDeleteCVEEntry = async (cve) => { if (!window.confirm(`Are you sure you want to delete the "${cve.vendor}" entry for ${cve.cve_id}? This will also delete all associated documents.`)) { return; } try { const url = `${API_BASE}/cves/${cve.id}`; console.log('DELETE request to:', url); const response = await fetch(url, { method: 'DELETE', credentials: 'include' }); if (!response.ok) { const contentType = response.headers.get('content-type'); if (contentType && contentType.includes('application/json')) { const data = await response.json(); throw new Error(data.error || 'Failed to delete CVE entry'); } else { throw new Error(`Server returned ${response.status} ${response.statusText}. Check API_BASE configuration.`); } } alert(`Deleted ${cve.vendor} entry for ${cve.cve_id}`); fetchCVEs(); fetchVendors(); } catch (err) { alert(`Error: ${err.message}`); } }; const handleDeleteEntireCVE = async (cveId, vendorCount) => { if (!window.confirm(`Are you sure you want to delete ALL ${vendorCount} vendor entries for ${cveId}? This will permanently remove all associated documents and files.`)) { return; } try { const url = `${API_BASE}/cves/by-cve-id/${encodeURIComponent(cveId)}`; console.log('DELETE request to:', url); const response = await fetch(url, { method: 'DELETE', credentials: 'include' }); if (!response.ok) { const contentType = response.headers.get('content-type'); if (contentType && contentType.includes('application/json')) { const data = await response.json(); throw new Error(data.error || 'Failed to delete CVE'); } else { throw new Error(`Server returned ${response.status} ${response.statusText}. Check API_BASE configuration.`); } } alert(`Deleted all entries for ${cveId}`); fetchCVEs(); fetchVendors(); } catch (err) { alert(`Error: ${err.message}`); } }; const handleAddTicket = async (e) => { e.preventDefault(); try { const response = await fetch(`${API_BASE}/jira-tickets`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify(ticketForm) }); if (!response.ok) { const data = await response.json(); throw new Error(data.error || 'Failed to create ticket'); } alert('JIRA ticket added successfully!'); setShowAddTicket(false); setAddTicketContext(null); setTicketForm({ cve_id: '', vendor: '', ticket_key: '', url: '', summary: '', status: 'Open' }); fetchJiraTickets(); } catch (err) { alert(`Error: ${err.message}`); } }; const handleEditTicket = (ticket) => { setEditingTicket(ticket); setTicketForm({ cve_id: ticket.cve_id, vendor: ticket.vendor, ticket_key: ticket.ticket_key, url: ticket.url || '', summary: ticket.summary || '', status: ticket.status }); setShowEditTicket(true); }; const handleUpdateTicket = async (e) => { e.preventDefault(); try { const response = await fetch(`${API_BASE}/jira-tickets/${editingTicket.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ ticket_key: ticketForm.ticket_key, url: ticketForm.url, summary: ticketForm.summary, status: ticketForm.status }) }); if (!response.ok) { const data = await response.json(); throw new Error(data.error || 'Failed to update ticket'); } alert('JIRA ticket updated!'); setShowEditTicket(false); setEditingTicket(null); setTicketForm({ cve_id: '', vendor: '', ticket_key: '', url: '', summary: '', status: 'Open' }); fetchJiraTickets(); } catch (err) { alert(`Error: ${err.message}`); } }; const handleDeleteTicket = async (ticket) => { if (!window.confirm(`Delete ticket ${ticket.ticket_key}?`)) return; try { const response = await fetch(`${API_BASE}/jira-tickets/${ticket.id}`, { method: 'DELETE', credentials: 'include' }); if (!response.ok) throw new Error('Failed to delete ticket'); alert('Ticket deleted'); fetchJiraTickets(); } catch (err) { alert(`Error: ${err.message}`); } }; const openAddTicketForCVE = (cve_id, vendor) => { setAddTicketContext({ cve_id, vendor }); setTicketForm({ cve_id, vendor, ticket_key: '', url: '', summary: '', status: 'Open' }); setShowAddTicket(true); }; // Fetch CVEs from API when authenticated useEffect(() => { if (isAuthenticated) { fetchCVEs(); fetchVendors(); fetchJiraTickets(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [isAuthenticated]); // Refetch when filters change useEffect(() => { if (isAuthenticated) { fetchCVEs(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [searchQuery, selectedVendor, selectedSeverity]); // Show loading while checking auth if (authLoading) { return (

Loading...

); } // Show login if not authenticated if (!isAuthenticated) { return ; } // 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 (
{/* Scanning line effect */}
{/* Header */}

CVE INTEL

Threat Intelligence & Vulnerability Command Center

{canWrite() && ( )} {canWrite() && ( )} setShowUserManagement(true)} onAuditLog={() => setShowAuditLog(true)} />
{/* Stats Bar */}
Total CVEs
{Object.keys(filteredGroupedCVEs).length}
Vendor Entries
{cves.length}
Open Tickets
{jiraTickets.filter(t => t.status !== 'Closed').length}
Critical
{cves.filter(c => c.severity === 'Critical').length}
{/* User Management Modal */} {showUserManagement && ( setShowUserManagement(false)} /> )} {/* Audit Log Modal */} {showAuditLog && ( setShowAuditLog(false)} /> )} {/* NVD Sync Modal */} {showNvdSync && ( setShowNvdSync(false)} onSyncComplete={() => fetchCVEs()} /> )} {/* 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()}); setNvdAutoFilled(false); setNvdError(null); }} onBlur={(e) => lookupNVD(e.target.value)} className="intel-input w-full" /> {nvdLoading && ( )}

Can be the same as existing CVE if adding another vendor

{nvdAutoFilled && (

Auto-filled from NVD

)} {nvdError && (

{nvdError}

)}
setNewCVE({...newCVE, vendor: e.target.value})} className="intel-input w-full" />

Must be unique for this CVE-ID