auto_sigma_rule_generator/frontend/src/App.js

1252 lines
53 KiB
JavaScript

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 = () => (
<div className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-5 gap-6">
<div className="bg-white p-6 rounded-lg shadow">
<h3 className="text-lg font-medium text-gray-900">Total CVEs</h3>
<p className="text-3xl font-bold text-blue-600">{stats.total_cves || 0}</p>
<p className="text-sm text-gray-500">Bulk: {stats.bulk_processed_cves || 0}</p>
</div>
<div className="bg-white p-6 rounded-lg shadow">
<h3 className="text-lg font-medium text-gray-900">SIGMA Rules</h3>
<p className="text-3xl font-bold text-green-600">{stats.total_sigma_rules || 0}</p>
<p className="text-sm text-gray-500">Nomi-sec: {stats.nomi_sec_rules || 0}</p>
<p className="text-sm text-gray-500">GitHub PoCs: {gitHubPocStats.github_poc_rules || 0}</p>
<p className={`text-sm ${llmStatus.status === 'ready' ? 'text-green-600' : 'text-red-500'}`}>
LLM: {llmStatus.current_provider?.provider || 'Not Available'}
</p>
</div>
<div className="bg-white p-6 rounded-lg shadow">
<h3 className="text-lg font-medium text-gray-900">CVEs with PoCs</h3>
<p className="text-3xl font-bold text-purple-600">{stats.cves_with_pocs || 0}</p>
<p className="text-sm text-gray-500">{(stats.poc_coverage || 0).toFixed(1)}% coverage</p>
<p className="text-sm text-gray-500">GitHub PoCs: {gitHubPocStats.cves_with_github_pocs || 0}</p>
<p className="text-sm text-gray-500">ExploitDB: {exploitdbStats.total_exploitdb_cves || 0}</p>
<p className="text-sm text-gray-500">CISA KEV: {cisaKevStats.total_kev_cves || 0}</p>
</div>
<div className="bg-white p-6 rounded-lg shadow">
<h3 className="text-lg font-medium text-gray-900">Recent CVEs (7d)</h3>
<p className="text-3xl font-bold text-orange-600">{stats.recent_cves_7_days || 0}</p>
</div>
<div className="bg-white p-6 rounded-lg shadow">
<h3 className="text-lg font-medium text-gray-900">High Quality PoCs</h3>
<p className="text-3xl font-bold text-indigo-600">{pocStats.high_quality_cves || 0}</p>
<p className="text-sm text-gray-500">Avg: {(pocStats.avg_poc_count || 0).toFixed(1)}</p>
<p className="text-sm text-gray-500">GitHub: {(gitHubPocStats.average_quality_score || 0).toFixed(1)}</p>
<p className="text-sm text-gray-500">ExploitDB: {exploitdbStats.total_exploits || 0} exploits</p>
<p className="text-sm text-gray-500">CISA KEV: {(cisaKevStats.average_threat_score || 0).toFixed(1)} threat</p>
</div>
</div>
{/* Data Synchronization Controls */}
<div className="bg-white rounded-lg shadow p-6">
<h2 className="text-xl font-bold text-gray-900 mb-6">Data Synchronization</h2>
{/* Phase 1: CVE Data Syncing */}
<div className="mb-6">
<h3 className="text-lg font-medium text-gray-900 mb-3">Phase 1: CVE Data Syncing</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<button
onClick={() => startBulkSeed(2002)}
disabled={isBulkSeedRunning()}
className={`px-4 py-2 rounded-md text-white ${
isBulkSeedRunning()
? 'bg-gray-400 cursor-not-allowed'
: 'bg-blue-600 hover:bg-blue-700'
}`}
>
{isBulkSeedRunning() ? 'Processing...' : 'Sync NVD CVEs'}
</button>
<button
onClick={startIncrementalUpdate}
disabled={isIncrementalUpdateRunning()}
className={`px-4 py-2 rounded-md text-white ${
isIncrementalUpdateRunning()
? 'bg-gray-400 cursor-not-allowed'
: 'bg-green-600 hover:bg-green-700'
}`}
>
{isIncrementalUpdateRunning() ? 'Processing...' : 'Incremental Update'}
</button>
</div>
</div>
{/* Phase 2: Exploit Data Syncing */}
<div className="mb-6">
<h3 className="text-lg font-medium text-gray-900 mb-3">Phase 2: Exploit Data Syncing</h3>
<div className="relative inline-block text-left">
<button
onClick={() => setExploitSyncDropdownOpen(!exploitSyncDropdownOpen)}
className={`inline-flex items-center justify-center w-full rounded-md border shadow-sm px-4 py-2 text-sm font-medium ${
areAnyExploitSyncsRunning()
? 'border-blue-300 bg-blue-50 text-blue-700'
: 'border-gray-300 bg-white text-gray-700 hover:bg-gray-50'
}`}
>
{areAnyExploitSyncsRunning() ? 'Exploit Syncs Running...' : 'Sync Exploit Data'}
<svg className="-mr-1 ml-2 h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clipRule="evenodd" />
</svg>
</button>
{exploitSyncDropdownOpen && (
<div className="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-10">
<div className="py-1">
<button
onClick={() => {
if (!isNomiSecSyncRunning()) {
syncNomiSec();
setExploitSyncDropdownOpen(false);
}
}}
disabled={isNomiSecSyncRunning()}
className={`block w-full text-left px-4 py-2 text-sm ${
isNomiSecSyncRunning()
? 'text-gray-400 cursor-not-allowed bg-gray-50'
: 'text-gray-700 hover:bg-purple-50 hover:text-purple-900'
}`}
>
<span className={`inline-block w-3 h-3 rounded-full mr-2 ${
isNomiSecSyncRunning() ? 'bg-gray-400' : 'bg-purple-600'
}`}></span>
{isNomiSecSyncRunning() ? 'Syncing nomi-sec PoCs...' : 'Sync nomi-sec PoCs'}
</button>
<button
onClick={() => {
if (!isGitHubPocSyncRunning()) {
syncGitHubPocs();
setExploitSyncDropdownOpen(false);
}
}}
disabled={isGitHubPocSyncRunning()}
className={`block w-full text-left px-4 py-2 text-sm ${
isGitHubPocSyncRunning()
? 'text-gray-400 cursor-not-allowed bg-gray-50'
: 'text-gray-700 hover:bg-green-50 hover:text-green-900'
}`}
>
<span className={`inline-block w-3 h-3 rounded-full mr-2 ${
isGitHubPocSyncRunning() ? 'bg-gray-400' : 'bg-green-600'
}`}></span>
{isGitHubPocSyncRunning() ? 'Syncing GitHub PoCs...' : 'Sync GitHub PoCs'}
</button>
<button
onClick={() => {
if (!isExploitDBSyncRunning()) {
syncExploitDB();
setExploitSyncDropdownOpen(false);
}
}}
disabled={isExploitDBSyncRunning()}
className={`block w-full text-left px-4 py-2 text-sm ${
isExploitDBSyncRunning()
? 'text-gray-400 cursor-not-allowed bg-gray-50'
: 'text-gray-700 hover:bg-red-50 hover:text-red-900'
}`}
>
<span className={`inline-block w-3 h-3 rounded-full mr-2 ${
isExploitDBSyncRunning() ? 'bg-gray-400' : 'bg-red-600'
}`}></span>
{isExploitDBSyncRunning() ? 'Syncing ExploitDB...' : 'Sync ExploitDB'}
</button>
<button
onClick={() => {
if (!isCISAKEVSyncRunning()) {
syncCISAKEV();
setExploitSyncDropdownOpen(false);
}
}}
disabled={isCISAKEVSyncRunning()}
className={`block w-full text-left px-4 py-2 text-sm ${
isCISAKEVSyncRunning()
? 'text-gray-400 cursor-not-allowed bg-gray-50'
: 'text-gray-700 hover:bg-yellow-50 hover:text-yellow-900'
}`}
>
<span className={`inline-block w-3 h-3 rounded-full mr-2 ${
isCISAKEVSyncRunning() ? 'bg-gray-400' : 'bg-yellow-600'
}`}></span>
{isCISAKEVSyncRunning() ? 'Syncing CISA KEV...' : 'Sync CISA KEV'}
</button>
</div>
</div>
)}
</div>
</div>
{/* Phase 3: Reference Data Syncing */}
<div className="mb-6">
<h3 className="text-lg font-medium text-gray-900 mb-3">Phase 3: Reference Data Syncing</h3>
<button
onClick={syncReferences}
disabled={hasRunningJobs}
className={`px-4 py-2 rounded-md text-white ${
hasRunningJobs
? 'bg-gray-400 cursor-not-allowed'
: 'bg-orange-600 hover:bg-orange-700'
}`}
>
{hasRunningJobs ? 'Processing...' : 'Sync References (Coming Soon)'}
</button>
</div>
{/* Phase 4: Rule Generation */}
<div>
<h3 className="text-lg font-medium text-gray-900 mb-3">Phase 4: Rule Generation</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<button
onClick={() => regenerateRules()}
disabled={isRuleGenerationRunning()}
className={`px-4 py-2 rounded-md text-white ${
isRuleGenerationRunning()
? 'bg-gray-400 cursor-not-allowed'
: 'bg-indigo-600 hover:bg-indigo-700'
}`}
>
{isRuleGenerationRunning() ? 'Processing...' : 'Regenerate Rules'}
</button>
<button
onClick={() => generateLlmRules()}
disabled={isRuleGenerationRunning() || llmStatus.status !== 'ready'}
className={`px-4 py-2 rounded-md text-white ${
isRuleGenerationRunning() || llmStatus.status !== 'ready'
? 'bg-gray-400 cursor-not-allowed'
: 'bg-violet-600 hover:bg-violet-700'
}`}
title={llmStatus.status !== 'ready' ? 'LLM not configured' : ''}
>
{isRuleGenerationRunning() ? 'Processing...' : 'Generate LLM Rules'}
</button>
</div>
</div>
</div>
{/* LLM Configuration */}
<div className="bg-white rounded-lg shadow p-6">
<h2 className="text-xl font-bold text-gray-900 mb-4">LLM Configuration</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<h3 className="text-lg font-medium text-gray-900 mb-2">Current Provider</h3>
<div className="space-y-2">
<p className="text-sm text-gray-600">
Provider: <span className="font-medium">{llmStatus.current_provider?.provider || 'Not configured'}</span>
</p>
<p className="text-sm text-gray-600">
Model: <span className="font-medium">{llmStatus.current_provider?.model || 'Not configured'}</span>
</p>
<p className={`text-sm ${llmStatus.status === 'ready' ? 'text-green-600' : 'text-red-500'}`}>
Status: <span className="font-medium">{llmStatus.status || 'Unknown'}</span>
</p>
</div>
</div>
<div>
<h3 className="text-lg font-medium text-gray-900 mb-2">Available Providers</h3>
<div className="space-y-2">
{llmStatus.available_providers?.map(provider => (
<div key={provider.name} className="flex items-center justify-between p-2 bg-gray-50 rounded">
<div>
<span className="font-medium">{provider.name}</span>
<span className={`ml-2 text-xs px-2 py-1 rounded ${
provider.available ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
}`}>
{provider.available ? 'Available' : 'Not configured'}
</span>
</div>
{provider.available && provider.name !== llmStatus.current_provider?.provider && (
<button
onClick={() => switchLlmProvider(provider.name, provider.default_model)}
className="text-xs bg-blue-600 hover:bg-blue-700 text-white px-2 py-1 rounded"
>
Switch
</button>
)}
</div>
))}
</div>
</div>
</div>
</div>
<div className="bg-white rounded-lg shadow p-6">
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-bold text-gray-900">Recent CVEs</h2>
<div className="flex space-x-3">
<button
onClick={testNvdConnection}
className="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-md text-sm"
>
Test NVD API
</button>
<button
onClick={handleFetchCves}
disabled={fetchingCves}
className={`px-4 py-2 rounded-md text-white ${
fetchingCves
? 'bg-gray-400 cursor-not-allowed'
: 'bg-blue-600 hover:bg-blue-700'
}`}
>
{fetchingCves ? 'Fetching...' : 'Fetch New CVEs'}
</button>
</div>
</div>
{testResult && (
<div className={`mb-4 p-4 rounded-md ${
testResult.status === 'success' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
}`}>
<div className="flex items-center">
<span className="font-medium">NVD API Test: </span>
<span className="ml-2">{testResult.message}</span>
</div>
{testResult.status === 'success' && (
<div className="mt-2 text-sm">
<p> API Key: {testResult.has_api_key ? 'Present' : 'Not configured'}</p>
<p> Available results: {testResult.total_results || 0}</p>
</div>
)}
</div>
)}
{fetchingCves && (
<div className="mb-4 p-4 bg-blue-100 text-blue-800 rounded-md">
<div className="flex items-center">
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-blue-600 mr-2"></div>
<span>Fetching CVEs from NVD API (30-day lookback)... This may take 1-2 minutes.</span>
</div>
</div>
)}
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
CVE ID
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Severity
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
CVSS Score
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Published
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Actions
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{cves.slice(0, 10).map((cve) => (
<tr key={cve.id}>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
{cve.cve_id}
</td>
<td className="px-6 py-4 whitespace-nowrap">
<span className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${getSeverityColor(cve.severity)}`}>
{cve.severity || 'N/A'}
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{cve.cvss_score || 'N/A'}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{cve.published_date ? formatDate(cve.published_date) : 'N/A'}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
<button
onClick={() => setSelectedCve(cve)}
className="text-blue-600 hover:text-blue-900"
>
View Details
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
);
const CVEList = () => (
<div className="bg-white rounded-lg shadow">
<div className="px-6 py-4 border-b border-gray-200">
<h2 className="text-xl font-bold text-gray-900">All CVEs</h2>
</div>
<div className="divide-y divide-gray-200">
{cves.map((cve) => (
<div key={cve.id} className="p-6 hover:bg-gray-50">
<div className="flex items-center justify-between">
<div className="flex-1">
<h3 className="text-lg font-medium text-gray-900">{cve.cve_id}</h3>
<p className="text-sm text-gray-600 mt-1 line-clamp-2">
{cve.description}
</p>
<div className="flex items-center mt-2 space-x-4">
<span className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${getSeverityColor(cve.severity)}`}>
{cve.severity || 'N/A'}
</span>
<span className="text-sm text-gray-500">
CVSS: {cve.cvss_score || 'N/A'}
</span>
<span className="text-sm text-gray-500">
{cve.published_date ? formatDate(cve.published_date) : 'N/A'}
</span>
</div>
</div>
<button
onClick={() => setSelectedCve(cve)}
className="ml-4 bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md"
>
View Details
</button>
</div>
</div>
))}
</div>
</div>
);
const SigmaRulesList = () => (
<div className="bg-white rounded-lg shadow">
<div className="px-6 py-4 border-b border-gray-200">
<h2 className="text-xl font-bold text-gray-900">Generated SIGMA Rules</h2>
</div>
<div className="divide-y divide-gray-200">
{sigmaRules.map((rule) => (
<div key={rule.id} className="p-6">
<div className="flex items-center justify-between mb-4">
<div>
<h3 className="text-lg font-medium text-gray-900">{rule.rule_name}</h3>
<p className="text-sm text-gray-600">CVE: {rule.cve_id}</p>
<div className="flex items-center mt-2 space-x-4">
<span className="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-blue-100 text-blue-800">
{rule.detection_type}
</span>
<span className="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-green-100 text-green-800">
{rule.confidence_level}
</span>
{rule.auto_generated && (
<span className="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-purple-100 text-purple-800">
Auto-generated
</span>
)}
{rule.exploit_based && (
<span className="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-red-100 text-red-800">
🔍 Exploit-Based
</span>
)}
</div>
{rule.github_repos && rule.github_repos.length > 0 && (
<div className="mt-2">
<p className="text-xs text-gray-500">
Based on {rule.github_repos.length} GitHub repository{rule.github_repos.length > 1 ? 's' : ''}:
</p>
<div className="flex flex-wrap gap-1 mt-1">
{rule.github_repos.slice(0, 3).map((repo, index) => (
<a
key={index}
href={repo}
target="_blank"
rel="noopener noreferrer"
className="text-xs text-blue-600 hover:text-blue-800 underline"
>
{repo.split('/').slice(-2).join('/')}
</a>
))}
{rule.github_repos.length > 3 && (
<span className="text-xs text-gray-500">
+{rule.github_repos.length - 3} more
</span>
)}
</div>
</div>
)}
</div>
<span className="text-sm text-gray-500">
{formatDate(rule.created_at)}
</span>
</div>
<div className="mt-4">
<SyntaxHighlighter
language="yaml"
style={tomorrow}
className="rounded-md"
customStyle={{ fontSize: '12px' }}
>
{rule.rule_content}
</SyntaxHighlighter>
</div>
{rule.exploit_indicators && (
<div className="mt-4 p-3 bg-gray-50 rounded-md">
<h4 className="text-sm font-medium text-gray-700 mb-2">Exploit Indicators Found:</h4>
<ExploitIndicators indicators={rule.exploit_indicators} />
</div>
)}
</div>
))}
</div>
</div>
);
const ExploitIndicators = ({ indicators }) => {
try {
const parsed = JSON.parse(indicators);
return (
<div className="space-y-2">
{Object.entries(parsed).map(([category, items]) => (
items.length > 0 && (
<div key={category} className="flex flex-wrap items-center gap-2">
<span className="text-xs font-medium text-gray-600 capitalize min-w-0">
{category}:
</span>
<div className="flex flex-wrap gap-1">
{items.slice(0, 5).map((item, index) => (
<span
key={index}
className="inline-flex px-2 py-1 text-xs rounded bg-gray-200 text-gray-700 font-mono"
>
{typeof item === 'string' && item.length > 30 ? item.substring(0, 30) + '...' : item}
</span>
))}
{items.length > 5 && (
<span className="text-xs text-gray-500">+{items.length - 5} more</span>
)}
</div>
</div>
)
))}
</div>
);
} catch (e) {
return <p className="text-xs text-gray-500">Invalid indicator data</p>;
}
};
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 (
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
<div className="relative top-20 mx-auto p-5 border w-11/12 max-w-4xl shadow-lg rounded-md bg-white">
<div className="flex justify-between items-center mb-4">
<h2 className="text-2xl font-bold text-gray-900">{cve.cve_id}</h2>
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-600"
>
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div className="space-y-6">
<div>
<h3 className="text-lg font-medium text-gray-900 mb-2">Details</h3>
<div className="bg-gray-50 p-4 rounded-md">
<div className="grid grid-cols-2 gap-4">
<div>
<span className="text-sm font-medium text-gray-500">Severity:</span>
<span className={`ml-2 inline-flex px-2 py-1 text-xs font-semibold rounded-full ${getSeverityColor(cve.severity)}`}>
{cve.severity || 'N/A'}
</span>
</div>
<div>
<span className="text-sm font-medium text-gray-500">CVSS Score:</span>
<span className="ml-2 text-sm text-gray-900">{cve.cvss_score || 'N/A'}</span>
</div>
<div>
<span className="text-sm font-medium text-gray-500">Published:</span>
<span className="ml-2 text-sm text-gray-900">
{cve.published_date ? formatDate(cve.published_date) : 'N/A'}
</span>
</div>
</div>
</div>
</div>
<div>
<h3 className="text-lg font-medium text-gray-900 mb-2">Description</h3>
<p className="text-sm text-gray-700 bg-gray-50 p-4 rounded-md">
{cve.description}
</p>
</div>
{cve.affected_products && cve.affected_products.length > 0 && (
<div>
<h3 className="text-lg font-medium text-gray-900 mb-2">Affected Products</h3>
<div className="bg-gray-50 p-4 rounded-md">
<ul className="list-disc list-inside space-y-1">
{cve.affected_products.slice(0, 5).map((product, index) => (
<li key={index} className="text-sm text-gray-700">{product}</li>
))}
{cve.affected_products.length > 5 && (
<li className="text-sm text-gray-500">... and {cve.affected_products.length - 5} more</li>
)}
</ul>
</div>
</div>
)}
<div>
<h3 className="text-lg font-medium text-gray-900 mb-2">Generated SIGMA Rules ({cveRules.length})</h3>
{cveRules.length > 0 ? (
<div className="space-y-4">
{cveRules.map((rule) => (
<div key={rule.id} className="border border-gray-200 rounded-md p-4">
<div className="flex justify-between items-center mb-2">
<h4 className="font-medium text-gray-900">{rule.rule_name}</h4>
<div className="flex space-x-2">
<span className="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-blue-100 text-blue-800">
{rule.detection_type}
</span>
<span className="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-green-100 text-green-800">
{rule.confidence_level}
</span>
{rule.exploit_based && (
<span className="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-red-100 text-red-800">
🔍 Exploit-Based
</span>
)}
</div>
</div>
{rule.github_repos && rule.github_repos.length > 0 && (
<div className="mb-3 p-2 bg-blue-50 rounded">
<p className="text-xs text-blue-700 font-medium mb-1">
Based on GitHub exploit analysis:
</p>
<div className="flex flex-wrap gap-1">
{rule.github_repos.slice(0, 2).map((repo, index) => (
<a
key={index}
href={repo}
target="_blank"
rel="noopener noreferrer"
className="text-xs text-blue-600 hover:text-blue-800 underline"
>
{repo.split('/').slice(-2).join('/')}
</a>
))}
{rule.github_repos.length > 2 && (
<span className="text-xs text-blue-600">
+{rule.github_repos.length - 2} more
</span>
)}
</div>
</div>
)}
<SyntaxHighlighter
language="yaml"
style={tomorrow}
className="rounded-md"
customStyle={{ fontSize: '12px' }}
>
{rule.rule_content}
</SyntaxHighlighter>
{rule.exploit_indicators && (
<div className="mt-3 p-2 bg-gray-50 rounded">
<p className="text-xs font-medium text-gray-700 mb-1">Exploit Indicators:</p>
<ExploitIndicators indicators={rule.exploit_indicators} />
</div>
)}
</div>
))}
</div>
) : (
<p className="text-gray-500 bg-gray-50 p-4 rounded-md">No SIGMA rules generated for this CVE yet.</p>
)}
</div>
</div>
</div>
</div>
);
};
const BulkJobsList = () => (
<div className="space-y-6">
<div className="flex justify-between items-center">
<h1 className="text-2xl font-bold text-gray-900">Bulk Processing Jobs</h1>
<button
onClick={fetchData}
className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md text-sm"
>
Refresh
</button>
</div>
{/* Bulk Status Overview */}
<div className="bg-white rounded-lg shadow p-6">
<h2 className="text-lg font-bold text-gray-900 mb-4">System Status</h2>
{bulkStatus.database_stats && (
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<div className="text-center">
<div className="text-2xl font-bold text-blue-600">{bulkStatus.database_stats.total_cves}</div>
<div className="text-sm text-gray-500">Total CVEs</div>
</div>
<div className="text-center">
<div className="text-2xl font-bold text-green-600">{bulkStatus.database_stats.bulk_processed_cves}</div>
<div className="text-sm text-gray-500">Bulk Processed</div>
</div>
<div className="text-center">
<div className="text-2xl font-bold text-purple-600">{bulkStatus.database_stats.cves_with_pocs}</div>
<div className="text-sm text-gray-500">With PoCs</div>
</div>
<div className="text-center">
<div className="text-2xl font-bold text-indigo-600">{bulkStatus.database_stats.nomi_sec_rules}</div>
<div className="text-sm text-gray-500">Enhanced Rules</div>
</div>
</div>
)}
</div>
{/* Running Jobs */}
{bulkJobs.some(job => job.status === 'running' || job.status === 'pending') && (
<div className="bg-white rounded-lg shadow">
<div className="px-6 py-4 border-b border-gray-200">
<h2 className="text-lg font-bold text-gray-900">Running Jobs</h2>
</div>
<div className="divide-y divide-gray-200">
{bulkJobs
.filter(job => job.status === 'running' || job.status === 'pending')
.map((job) => (
<div key={job.id} className="px-6 py-4 bg-blue-50">
<div className="flex items-center justify-between">
<div className="flex-1">
<div className="flex items-center space-x-3">
<h3 className="text-lg font-medium text-gray-900">{job.job_type}</h3>
<span className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${
job.status === 'running' ? 'bg-blue-100 text-blue-800' :
'bg-gray-100 text-gray-800'
}`}>
{job.status}
</span>
</div>
<div className="mt-2 flex items-center space-x-6 text-sm text-gray-500">
<span>Started: {formatDate(job.started_at)}</span>
{job.year && <span>Year: {job.year}</span>}
</div>
{job.total_items > 0 && (
<div className="mt-2">
<div className="flex items-center space-x-4 text-sm text-gray-600">
<span>Progress: {job.processed_items}/{job.total_items}</span>
{job.failed_items > 0 && (
<span className="text-red-600">Failed: {job.failed_items}</span>
)}
</div>
<div className="mt-1 w-full bg-gray-200 rounded-full h-2">
<div
className="bg-blue-600 h-2 rounded-full"
style={{ width: `${(job.processed_items / job.total_items) * 100}%` }}
></div>
</div>
</div>
)}
</div>
<div className="flex-shrink-0 ml-4">
<button
onClick={() => cancelJob(job.id)}
className="bg-red-600 hover:bg-red-700 text-white px-3 py-1 rounded-md text-sm font-medium"
>
Cancel
</button>
</div>
</div>
</div>
))}
</div>
</div>
)}
{/* Recent Jobs */}
<div className="bg-white rounded-lg shadow">
<div className="px-6 py-4 border-b border-gray-200">
<h2 className="text-lg font-bold text-gray-900">Recent Jobs</h2>
</div>
<div className="divide-y divide-gray-200">
{bulkJobs.length === 0 ? (
<div className="px-6 py-8 text-center text-gray-500">
No bulk processing jobs found
</div>
) : (
bulkJobs.map((job) => (
<div key={job.id} className="px-6 py-4">
<div className="flex items-center justify-between">
<div className="flex-1">
<div className="flex items-center space-x-3">
<h3 className="text-lg font-medium text-gray-900">{job.job_type}</h3>
<span className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${
job.status === 'completed' ? 'bg-green-100 text-green-800' :
job.status === 'running' ? 'bg-blue-100 text-blue-800' :
job.status === 'failed' ? 'bg-red-100 text-red-800' :
job.status === 'cancelled' ? 'bg-orange-100 text-orange-800' :
'bg-gray-100 text-gray-800'
}`}>
{job.status}
</span>
</div>
<div className="mt-2 flex items-center space-x-6 text-sm text-gray-500">
<span>Started: {formatDate(job.started_at)}</span>
{job.completed_at && (
<span>Completed: {formatDate(job.completed_at)}</span>
)}
{job.year && (
<span>Year: {job.year}</span>
)}
</div>
{job.total_items > 0 && (
<div className="mt-2">
<div className="flex items-center space-x-4 text-sm text-gray-600">
<span>Progress: {job.processed_items}/{job.total_items}</span>
{job.failed_items > 0 && (
<span className="text-red-600">Failed: {job.failed_items}</span>
)}
</div>
<div className="mt-1 w-full bg-gray-200 rounded-full h-2">
<div
className="bg-blue-600 h-2 rounded-full"
style={{ width: `${(job.processed_items / job.total_items) * 100}%` }}
></div>
</div>
</div>
)}
{job.error_message && (
<div className="mt-2 p-2 bg-red-50 border border-red-200 rounded text-sm text-red-700">
{job.error_message}
</div>
)}
</div>
<div className="flex-shrink-0 ml-4">
{(job.status === 'running' || job.status === 'pending') && (
<button
onClick={() => cancelJob(job.id)}
className="bg-red-600 hover:bg-red-700 text-white px-3 py-1 rounded-md text-sm font-medium"
>
Cancel
</button>
)}
</div>
</div>
</div>
))
)}
</div>
</div>
</div>
);
if (loading) {
return (
<div className="min-h-screen bg-gray-100 flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-blue-600"></div>
<p className="mt-4 text-gray-600">Loading...</p>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-gray-100">
<nav className="bg-white shadow">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between h-16">
<div className="flex">
<div className="flex-shrink-0 flex items-center">
<h1 className="text-xl font-bold text-gray-900">CVE-SIGMA Auto Generator</h1>
</div>
<div className="ml-6 flex space-x-8">
<button
onClick={() => setActiveTab('dashboard')}
className={`inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium ${
activeTab === 'dashboard'
? 'border-blue-500 text-gray-900'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}`}
>
Dashboard
</button>
<button
onClick={() => setActiveTab('cves')}
className={`inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium ${
activeTab === 'cves'
? 'border-blue-500 text-gray-900'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}`}
>
CVEs
</button>
<button
onClick={() => setActiveTab('rules')}
className={`inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium ${
activeTab === 'rules'
? 'border-blue-500 text-gray-900'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}`}
>
SIGMA Rules
</button>
<button
onClick={() => setActiveTab('bulk-jobs')}
className={`inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium ${
activeTab === 'bulk-jobs'
? 'border-blue-500 text-gray-900'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}`}
>
Bulk Jobs
</button>
</div>
</div>
</div>
</div>
</nav>
<main className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
<div className="px-4 py-6 sm:px-0">
{activeTab === 'dashboard' && <Dashboard />}
{activeTab === 'cves' && <CVEList />}
{activeTab === 'rules' && <SigmaRulesList />}
{activeTab === 'bulk-jobs' && <BulkJobsList />}
</div>
</main>
{selectedCve && (
<CVEDetail cve={selectedCve} onClose={() => setSelectedCve(null)} />
)}
</div>
);
}
export default App;