import React, { useState, useEffect } from 'react'; import axios from 'axios'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { tomorrow } from 'react-syntax-highlighter/dist/esm/styles/prism'; import './App.css'; const API_BASE_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000'; function App() { const [cves, setCves] = useState([]); const [sigmaRules, setSigmaRules] = useState([]); const [selectedCve, setSelectedCve] = useState(null); const [stats, setStats] = useState({}); const [loading, setLoading] = useState(true); const [activeTab, setActiveTab] = useState('dashboard'); const [fetchingCves, setFetchingCves] = useState(false); const [testResult, setTestResult] = useState(null); const [bulkJobs, setBulkJobs] = useState([]); const [bulkStatus, setBulkStatus] = useState({}); const [pocStats, setPocStats] = useState({}); const [gitHubPocStats, setGitHubPocStats] = useState({}); const [exploitdbStats, setExploitdbStats] = useState({}); const [cisaKevStats, setCisaKevStats] = useState({}); const [bulkProcessing, setBulkProcessing] = useState(false); const [hasRunningJobs, setHasRunningJobs] = useState(false); const [runningJobTypes, setRunningJobTypes] = useState(new Set()); const [llmStatus, setLlmStatus] = useState({}); const [exploitSyncDropdownOpen, setExploitSyncDropdownOpen] = useState(false); useEffect(() => { fetchData(); }, []); // Close dropdown when clicking outside useEffect(() => { const handleClickOutside = (event) => { if (exploitSyncDropdownOpen && !event.target.closest('.relative')) { setExploitSyncDropdownOpen(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => { document.removeEventListener('mousedown', handleClickOutside); }; }, [exploitSyncDropdownOpen]); // Helper functions to check if specific job types are running const isJobTypeRunning = (jobType) => { return runningJobTypes.has(jobType); }; const isBulkSeedRunning = () => { return isJobTypeRunning('nvd_bulk_seed') || isJobTypeRunning('bulk_seed'); }; const isIncrementalUpdateRunning = () => { return isJobTypeRunning('incremental_update'); }; const isNomiSecSyncRunning = () => { return isJobTypeRunning('nomi_sec_sync'); }; const isGitHubPocSyncRunning = () => { return isJobTypeRunning('github_poc_sync'); }; const isExploitDBSyncRunning = () => { return isJobTypeRunning('exploitdb_sync') || isJobTypeRunning('exploitdb_sync_local'); }; const isCISAKEVSyncRunning = () => { return isJobTypeRunning('cisa_kev_sync'); }; const isRuleGenerationRunning = () => { return isJobTypeRunning('rule_regeneration') || isJobTypeRunning('llm_rule_generation'); }; const areAnyExploitSyncsRunning = () => { return isNomiSecSyncRunning() || isGitHubPocSyncRunning() || isExploitDBSyncRunning() || isCISAKEVSyncRunning(); }; const fetchData = async () => { try { setLoading(true); const [cvesRes, rulesRes, statsRes, bulkJobsRes, bulkStatusRes, pocStatsRes, githubPocStatsRes, exploitdbStatsRes, cisaKevStatsRes, llmStatusRes] = await Promise.all([ axios.get(`${API_BASE_URL}/api/cves`), axios.get(`${API_BASE_URL}/api/sigma-rules`), axios.get(`${API_BASE_URL}/api/stats`), axios.get(`${API_BASE_URL}/api/bulk-jobs`), axios.get(`${API_BASE_URL}/api/bulk-status`), axios.get(`${API_BASE_URL}/api/poc-stats`), axios.get(`${API_BASE_URL}/api/github-poc-stats`).catch(err => ({ data: {} })), axios.get(`${API_BASE_URL}/api/exploitdb-stats`).catch(err => ({ data: {} })), axios.get(`${API_BASE_URL}/api/cisa-kev-stats`).catch(err => ({ data: {} })), axios.get(`${API_BASE_URL}/api/llm-status`).catch(err => ({ data: {} })) ]); setCves(cvesRes.data); setSigmaRules(rulesRes.data); setStats(statsRes.data); setBulkJobs(bulkJobsRes.data); setBulkStatus(bulkStatusRes.data); setPocStats(pocStatsRes.data); setGitHubPocStats(githubPocStatsRes.data); setExploitdbStats(exploitdbStatsRes.data); setCisaKevStats(cisaKevStatsRes.data); setLlmStatus(llmStatusRes.data); // Update running jobs state const runningJobs = bulkJobsRes.data.filter(job => job.status === 'running' || job.status === 'pending'); setHasRunningJobs(runningJobs.length > 0); // Update specific job types that are running const activeJobTypes = new Set(runningJobs.map(job => job.job_type)); setRunningJobTypes(activeJobTypes); } catch (error) { console.error('Error fetching data:', error); } finally { setLoading(false); } }; const cancelJob = async (jobId) => { try { const response = await axios.post(`${API_BASE_URL}/api/cancel-job/${jobId}`); console.log('Cancel job response:', response.data); // Refresh data after cancelling setTimeout(() => { fetchData(); }, 1000); } catch (error) { console.error('Error cancelling job:', error); alert('Failed to cancel job. Please try again.'); } }; const handleFetchCves = async () => { try { setFetchingCves(true); const response = await axios.post(`${API_BASE_URL}/api/fetch-cves`); console.log('Fetch response:', response.data); // Show success message and refresh after delay setTimeout(() => { fetchData(); setFetchingCves(false); }, 5000); // Wait a bit longer for background task to complete } catch (error) { console.error('Error fetching CVEs:', error); setFetchingCves(false); // Show error state setTestResult({ status: 'error', message: 'Failed to initiate CVE fetch. Check console logs.' }); } }; const testNvdConnection = async () => { try { const response = await axios.get(`${API_BASE_URL}/api/test-nvd`); setTestResult(response.data); } catch (error) { console.error('Error testing NVD connection:', error); setTestResult({ status: 'error', message: 'Failed to test NVD connection' }); } }; const startBulkSeed = async (startYear = 2020, endYear = null) => { try { const response = await axios.post(`${API_BASE_URL}/api/bulk-seed`, { start_year: startYear, end_year: endYear, skip_nomi_sec: true }); console.log('Bulk seed response:', response.data); // Refresh data immediately to show job started fetchData(); } catch (error) { console.error('Error starting bulk seed:', error); } }; const startIncrementalUpdate = async () => { try { const response = await axios.post(`${API_BASE_URL}/api/incremental-update`); console.log('Incremental update response:', response.data); fetchData(); } catch (error) { console.error('Error starting incremental update:', error); } }; const syncNomiSec = async (cveId = null) => { try { const response = await axios.post(`${API_BASE_URL}/api/sync-nomi-sec`, { cve_id: cveId }); console.log('Nomi-sec sync response:', response.data); fetchData(); } catch (error) { console.error('Error syncing nomi-sec:', error); } }; const syncGitHubPocs = async (cveId = null) => { try { const response = await axios.post(`${API_BASE_URL}/api/sync-github-pocs`, { cve_id: cveId }); console.log('GitHub PoC sync response:', response.data); fetchData(); } catch (error) { console.error('Error syncing GitHub PoCs:', error); } }; const syncExploitDB = async (cveId = null) => { try { const response = await axios.post(`${API_BASE_URL}/api/sync-exploitdb`, { cve_id: cveId, batch_size: 30 }); console.log('ExploitDB sync response:', response.data); fetchData(); } catch (error) { console.error('Error syncing ExploitDB:', error); } }; const syncCISAKEV = async (cveId = null) => { try { const response = await axios.post(`${API_BASE_URL}/api/sync-cisa-kev`, { cve_id: cveId, batch_size: 100 }); console.log('CISA KEV sync response:', response.data); fetchData(); } catch (error) { console.error('Error syncing CISA KEV:', error); } }; const syncReferences = async () => { try { // Placeholder for future implementation console.log('Sync References - Not implemented yet'); alert('Sync References functionality will be implemented in a future update'); } catch (error) { console.error('Error syncing references:', error); } }; const regenerateRules = async (force = false) => { try { const response = await axios.post(`${API_BASE_URL}/api/regenerate-rules`, { force: force }); console.log('Rule regeneration response:', response.data); fetchData(); } catch (error) { console.error('Error regenerating rules:', error); } }; const generateLlmRules = async (force = false) => { try { const response = await axios.post(`${API_BASE_URL}/api/llm-enhanced-rules`, { force: force }); console.log('LLM rule generation response:', response.data); fetchData(); } catch (error) { console.error('Error generating LLM-enhanced rules:', error); } }; const switchLlmProvider = async (provider, model) => { try { const response = await axios.post(`${API_BASE_URL}/api/llm-switch`, { provider: provider, model: model }); console.log('LLM provider switch response:', response.data); fetchData(); // Refresh to get updated status } catch (error) { console.error('Error switching LLM provider:', error); alert('Failed to switch LLM provider. Please check configuration.'); } }; const getSeverityColor = (severity) => { switch (severity?.toLowerCase()) { case 'critical': return 'bg-red-100 text-red-800'; case 'high': return 'bg-orange-100 text-orange-800'; case 'medium': return 'bg-yellow-100 text-yellow-800'; case 'low': return 'bg-green-100 text-green-800'; default: return 'bg-gray-100 text-gray-800'; } }; const formatDate = (dateString) => { return new Date(dateString).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }); }; const Dashboard = () => (
{stats.total_cves || 0}
Bulk: {stats.bulk_processed_cves || 0}
{stats.total_sigma_rules || 0}
Nomi-sec: {stats.nomi_sec_rules || 0}
GitHub PoCs: {gitHubPocStats.github_poc_rules || 0}
LLM: {llmStatus.current_provider?.provider || 'Not Available'}
{stats.cves_with_pocs || 0}
{(stats.poc_coverage || 0).toFixed(1)}% coverage
GitHub PoCs: {gitHubPocStats.cves_with_github_pocs || 0}
ExploitDB: {exploitdbStats.total_exploitdb_cves || 0}
CISA KEV: {cisaKevStats.total_kev_cves || 0}
{stats.recent_cves_7_days || 0}
{pocStats.high_quality_cves || 0}
Avg: {(pocStats.avg_poc_count || 0).toFixed(1)}
GitHub: {(gitHubPocStats.average_quality_score || 0).toFixed(1)}
ExploitDB: {exploitdbStats.total_exploits || 0} exploits
CISA KEV: {(cisaKevStats.average_threat_score || 0).toFixed(1)} threat
Provider: {llmStatus.current_provider?.provider || 'Not configured'}
Model: {llmStatus.current_provider?.model || 'Not configured'}
Status: {llmStatus.status || 'Unknown'}
✅ API Key: {testResult.has_api_key ? 'Present' : 'Not configured'}
✅ Available results: {testResult.total_results || 0}
CVE ID | Severity | CVSS Score | Published | Actions |
---|---|---|---|---|
{cve.cve_id} | {cve.severity || 'N/A'} | {cve.cvss_score || 'N/A'} | {cve.published_date ? formatDate(cve.published_date) : 'N/A'} |
{cve.description}
CVE: {rule.cve_id}
Based on {rule.github_repos.length} GitHub repository{rule.github_repos.length > 1 ? 's' : ''}:
Invalid indicator data
; } }; const CVEDetail = ({ cve, onClose }) => { const [cveRules, setCveRules] = useState([]); useEffect(() => { if (cve) { fetchCveRules(cve.cve_id); } }, [cve]); const fetchCveRules = async (cveId) => { try { const response = await axios.get(`${API_BASE_URL}/api/sigma-rules/${cveId}`); setCveRules(response.data); } catch (error) { console.error('Error fetching CVE rules:', error); } }; return ({cve.description}
Based on GitHub exploit analysis:
Exploit Indicators:
No SIGMA rules generated for this CVE yet.
)}Loading...