Added NVD lookup features and optional NVD API key in .env file
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Search, FileText, AlertCircle, Download, Upload, Eye, Filter, CheckCircle, XCircle, Loader, Trash2, Plus } from 'lucide-react';
|
||||
import { Search, FileText, AlertCircle, Download, Upload, Eye, Filter, CheckCircle, XCircle, Loader, Trash2, Plus, RefreshCw } 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';
|
||||
@@ -29,6 +30,7 @@ export default function App() {
|
||||
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: '',
|
||||
@@ -37,6 +39,42 @@ export default function App() {
|
||||
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 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);
|
||||
@@ -163,6 +201,9 @@ export default function App() {
|
||||
description: '',
|
||||
published_date: new Date().toISOString().split('T')[0]
|
||||
});
|
||||
setNvdLoading(false);
|
||||
setNvdError(null);
|
||||
setNvdAutoFilled(false);
|
||||
fetchCVEs();
|
||||
} catch (err) {
|
||||
alert(`Error: ${err.message}`);
|
||||
@@ -297,6 +338,15 @@ export default function App() {
|
||||
<p className="text-gray-600">Query vulnerabilities, manage vendors, and attach documentation</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
{canWrite() && (
|
||||
<button
|
||||
onClick={() => setShowNvdSync(true)}
|
||||
className="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors flex items-center gap-2 shadow-md"
|
||||
>
|
||||
<RefreshCw className="w-5 h-5" />
|
||||
Sync with NVD
|
||||
</button>
|
||||
)}
|
||||
{canWrite() && (
|
||||
<button
|
||||
onClick={() => setShowAddCVE(true)}
|
||||
@@ -320,6 +370,11 @@ export default function App() {
|
||||
<AuditLog onClose={() => setShowAuditLog(false)} />
|
||||
)}
|
||||
|
||||
{/* NVD Sync Modal */}
|
||||
{showNvdSync && (
|
||||
<NvdSyncModal onClose={() => setShowNvdSync(false)} onSyncComplete={() => fetchCVEs()} />
|
||||
)}
|
||||
|
||||
{/* Add CVE Modal */}
|
||||
{showAddCVE && (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
|
||||
@@ -328,7 +383,7 @@ export default function App() {
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h2 className="text-2xl font-bold text-gray-900">Add CVE Entry</h2>
|
||||
<button
|
||||
onClick={() => setShowAddCVE(false)}
|
||||
onClick={() => { setShowAddCVE(false); setNvdLoading(false); setNvdError(null); setNvdAutoFilled(false); }}
|
||||
className="text-gray-400 hover:text-gray-600"
|
||||
>
|
||||
<XCircle className="w-6 h-6" />
|
||||
@@ -347,15 +402,33 @@ export default function App() {
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
CVE ID *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
placeholder="CVE-2024-1234"
|
||||
value={newCVE.cve_id}
|
||||
onChange={(e) => 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"
|
||||
/>
|
||||
<div className="relative">
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
placeholder="CVE-2024-1234"
|
||||
value={newCVE.cve_id}
|
||||
onChange={(e) => { setNewCVE({...newCVE, cve_id: e.target.value.toUpperCase()}); setNvdAutoFilled(false); setNvdError(null); }}
|
||||
onBlur={(e) => lookupNVD(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"
|
||||
/>
|
||||
{nvdLoading && (
|
||||
<Loader className="absolute right-3 top-2.5 w-5 h-5 text-[#0476D9] animate-spin" />
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mt-1">Can be the same as existing CVE if adding another vendor</p>
|
||||
{nvdAutoFilled && (
|
||||
<p className="text-xs text-green-600 mt-1 flex items-center gap-1">
|
||||
<CheckCircle className="w-3 h-3" />
|
||||
Auto-filled from NVD
|
||||
</p>
|
||||
)}
|
||||
{nvdError && (
|
||||
<p className="text-xs text-amber-600 mt-1 flex items-center gap-1">
|
||||
<AlertCircle className="w-3 h-3" />
|
||||
{nvdError}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -425,7 +498,7 @@ export default function App() {
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowAddCVE(false)}
|
||||
onClick={() => { setShowAddCVE(false); setNvdLoading(false); setNvdError(null); setNvdAutoFilled(false); }}
|
||||
className="px-4 py-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300 transition-colors"
|
||||
>
|
||||
Cancel
|
||||
|
||||
Reference in New Issue
Block a user