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'; import './App.css'; const API_BASE = process.env.REACT_APP_API_BASE || 'http://localhost:3001/api'; // ============================================ // INLINE STYLES - NUCLEAR OPTION FOR VISIBILITY // ============================================ const STYLES = { // Main container with visible background mainContainer: { minHeight: '100vh', background: 'linear-gradient(135deg, #0A0E27 0%, #131937 50%, #0A0E27 100%)', padding: '1.5rem', position: 'relative', overflow: 'hidden', }, // Stat cards with BRIGHT CYAN borders statCard: { background: 'linear-gradient(135deg, rgba(19, 25, 55, 1) 0%, rgba(30, 39, 73, 0.95) 100%)', border: '3px solid #00D9FF', borderRadius: '0.5rem', padding: '1rem', boxShadow: '0 8px 24px rgba(0, 0, 0, 0.6), 0 0 30px rgba(0, 217, 255, 0.3), inset 0 2px 0 rgba(0, 217, 255, 0.2)', position: 'relative', overflow: 'hidden', }, // Intel card with thick glowing border intelCard: { background: 'linear-gradient(135deg, rgba(19, 25, 55, 1) 0%, rgba(30, 39, 73, 0.95) 50%, rgba(19, 25, 55, 1) 100%)', border: '3px solid rgba(0, 217, 255, 0.6)', borderRadius: '0.5rem', boxShadow: '0 12px 32px rgba(0, 0, 0, 0.7), 0 0 40px rgba(0, 217, 255, 0.25), inset 0 2px 0 rgba(0, 217, 255, 0.15)', position: 'relative', overflow: 'hidden', }, // Vendor card with depth vendorCard: { background: 'linear-gradient(135deg, rgba(10, 14, 39, 0.95) 0%, rgba(19, 25, 55, 0.9) 100%)', border: '2px solid rgba(0, 217, 255, 0.4)', borderRadius: '0.5rem', padding: '1rem', boxShadow: '0 6px 16px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(0, 217, 255, 0.1)', marginBottom: '0.75rem', }, // CRITICAL severity badge - BRIGHT RED with WHITE text badgeCritical: { display: 'inline-flex', alignItems: 'center', gap: '0.5rem', background: 'linear-gradient(135deg, rgba(255, 51, 102, 0.4) 0%, rgba(255, 51, 102, 0.3) 100%)', border: '2px solid #FF3366', borderRadius: '0.375rem', padding: '0.375rem 0.875rem', color: '#FFFFFF', fontWeight: '700', fontSize: '0.75rem', textTransform: 'uppercase', letterSpacing: '0.5px', textShadow: '0 0 10px rgba(255, 51, 102, 0.9)', boxShadow: '0 0 20px rgba(255, 51, 102, 0.5), 0 4px 8px rgba(0, 0, 0, 0.4)', }, // HIGH severity badge - BRIGHT ORANGE/YELLOW with WHITE text badgeHigh: { display: 'inline-flex', alignItems: 'center', gap: '0.5rem', background: 'linear-gradient(135deg, rgba(255, 184, 0, 0.4) 0%, rgba(255, 184, 0, 0.3) 100%)', border: '2px solid #FFB800', borderRadius: '0.375rem', padding: '0.375rem 0.875rem', color: '#FFFFFF', fontWeight: '700', fontSize: '0.75rem', textTransform: 'uppercase', letterSpacing: '0.5px', textShadow: '0 0 10px rgba(255, 184, 0, 0.9)', boxShadow: '0 0 20px rgba(255, 184, 0, 0.5), 0 4px 8px rgba(0, 0, 0, 0.4)', }, // MEDIUM severity badge - BRIGHT CYAN with WHITE text badgeMedium: { display: 'inline-flex', alignItems: 'center', gap: '0.5rem', background: 'linear-gradient(135deg, rgba(0, 217, 255, 0.4) 0%, rgba(0, 217, 255, 0.3) 100%)', border: '2px solid #00D9FF', borderRadius: '0.375rem', padding: '0.375rem 0.875rem', color: '#FFFFFF', fontWeight: '700', fontSize: '0.75rem', textTransform: 'uppercase', letterSpacing: '0.5px', textShadow: '0 0 10px rgba(0, 217, 255, 0.9)', boxShadow: '0 0 20px rgba(0, 217, 255, 0.5), 0 4px 8px rgba(0, 0, 0, 0.4)', }, // LOW severity badge - BRIGHT GREEN with WHITE text badgeLow: { display: 'inline-flex', alignItems: 'center', gap: '0.5rem', background: 'linear-gradient(135deg, rgba(0, 255, 136, 0.4) 0%, rgba(0, 255, 136, 0.3) 100%)', border: '2px solid #00FF88', borderRadius: '0.375rem', padding: '0.375rem 0.875rem', color: '#FFFFFF', fontWeight: '700', fontSize: '0.75rem', textTransform: 'uppercase', letterSpacing: '0.5px', textShadow: '0 0 10px rgba(0, 255, 136, 0.9)', boxShadow: '0 0 20px rgba(0, 255, 136, 0.5), 0 4px 8px rgba(0, 0, 0, 0.4)', }, // Glowing dot for badges glowDot: (color) => ({ width: '8px', height: '8px', borderRadius: '50%', background: color, boxShadow: `0 0 12px ${color}, 0 0 6px ${color}`, animation: 'pulse 2s ease-in-out infinite', }), }; // Helper function to get severity badge style const getSeverityBadgeStyle = (severity) => { switch (severity?.toLowerCase()) { case 'critical': return STYLES.badgeCritical; case 'high': return STYLES.badgeHigh; case 'medium': return STYLES.badgeMedium; case 'low': return STYLES.badgeLow; default: return STYLES.badgeMedium; } }; // Helper function to get severity dot color const getSeverityDotColor = (severity) => { switch (severity?.toLowerCase()) { case 'critical': return '#FF3366'; case 'high': return '#FFB800'; case 'medium': return '#00D9FF'; case 'low': return '#00FF88'; default: return '#00D9FF'; } }; 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...
Threat Intelligence & Vulnerability Command Center
Tip: You can add the same CVE-ID multiple times with different vendors. Each vendor will have its own documents folder.
Note: Changing CVE ID or Vendor will move associated documents to the new path.
Standard operating procedures for vulnerability response and escalation...
Last updated: 2024-02-08Emergency contacts and escalation paths for security vendors...
Last updated: 2024-02-05Guidelines for assessing and classifying vulnerability severity levels...
Last updated: 2024-01-28Enterprise patch management timelines and approval workflow...
Last updated: 2024-01-15Required documentation for vulnerability tracking and audit compliance...
Last updated: 2024-01-10Error
{quickCheckResult.error}
✓ CVE Addressed ({quickCheckResult.vendors.length} vendor{quickCheckResult.vendors.length > 1 ? 's' : ''})
{vendorInfo.vendor}
Severity: {vendorInfo.severity}
Status: {vendorInfo.status}
Documents: {vendorInfo.total_documents} attached
Not Found
This CVE has not been addressed yet. No entry exists in the database.
{Object.keys(filteredGroupedCVEs).length} CVE{Object.keys(filteredGroupedCVEs).length !== 1 ? 's' : ''} • {cves.length} vendor entr{cves.length !== 1 ? 'ies' : 'y'}
{selectedDocuments.length > 0 && ( )}Scanning Vulnerabilities...
{error}
{vendorEntries[0].description}
{vendorEntries[0].description}
{doc.name}
{doc.type} • {doc.file_size} {doc.notes && • {doc.notes}}
No documents attached
)} {canWrite() && ( )}No JIRA tickets linked
)}Try adjusting your search criteria or filters
No open tickets