added required code changes, components, and packages for login feature

This commit is contained in:
2026-01-28 14:36:33 -07:00
parent 1d2a6b2e72
commit da14c92d98
13 changed files with 1370 additions and 63 deletions

View File

@@ -1,5 +1,9 @@
import React, { useState, useEffect } from 'react';
import { Search, FileText, AlertCircle, Download, Upload, Eye, Filter, CheckCircle, XCircle, Loader, Trash2, Plus } from 'lucide-react';
import { useAuth } from './contexts/AuthContext';
import LoginForm from './components/LoginForm';
import UserMenu from './components/UserMenu';
import UserManagement from './components/UserManagement';
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';
@@ -7,6 +11,7 @@ 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');
@@ -21,6 +26,7 @@ export default function App() {
const [quickCheckCVE, setQuickCheckCVE] = useState('');
const [quickCheckResult, setQuickCheckResult] = useState(null);
const [showAddCVE, setShowAddCVE] = useState(false);
const [showUserManagement, setShowUserManagement] = useState(false);
const [newCVE, setNewCVE] = useState({
cve_id: '',
vendor: '',
@@ -30,19 +36,6 @@ export default function App() {
});
const [uploadingFile, setUploadingFile] = useState(false);
// Fetch CVEs from API
useEffect(() => {
fetchCVEs();
fetchVendors();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// Refetch when filters change
useEffect(() => {
fetchCVEs();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchQuery, selectedVendor, selectedSeverity]);
const fetchCVEs = async () => {
setLoading(true);
setError(null);
@@ -52,7 +45,9 @@ export default function App() {
if (selectedVendor !== 'All Vendors') params.append('vendor', selectedVendor);
if (selectedSeverity !== 'All Severities') params.append('severity', selectedSeverity);
const response = await fetch(`${API_BASE}/cves?${params}`);
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);
@@ -66,7 +61,9 @@ export default function App() {
const fetchVendors = async () => {
try {
const response = await fetch(`${API_BASE}/vendors`);
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]);
@@ -78,9 +75,11 @@ export default function App() {
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}`);
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 }));
@@ -91,9 +90,11 @@ export default function App() {
const quickCheckCVEStatus = async () => {
if (!quickCheckCVE.trim()) return;
try {
const response = await fetch(`${API_BASE}/cves/check/${quickCheckCVE.trim()}`);
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);
@@ -104,7 +105,6 @@ export default function App() {
};
const handleViewDocuments = async (cveId, vendor) => {
const key = `${cveId}-${vendor}`;
if (selectedCVE === cveId && selectedVendorView === vendor) {
setSelectedCVE(null);
setSelectedVendorView(null);
@@ -143,6 +143,7 @@ export default function App() {
const response = await fetch(`${API_BASE}/cves`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify(newCVE)
});
@@ -195,6 +196,7 @@ export default function App() {
try {
const response = await fetch(`${API_BASE}/cves/${cveId}/documents`, {
method: 'POST',
credentials: 'include',
body: formData
});
@@ -219,14 +221,15 @@ export default function App() {
if (!window.confirm('Are you sure you want to delete this document?')) {
return;
}
try {
const response = await fetch(`${API_BASE}/documents/${docId}`, {
method: 'DELETE'
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];
@@ -237,6 +240,40 @@ export default function App() {
}
};
// Fetch CVEs from API when authenticated
useEffect(() => {
if (isAuthenticated) {
fetchCVEs();
fetchVendors();
}
// 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 (
<div className="min-h-screen bg-gray-100 flex items-center justify-center">
<div className="text-center">
<Loader className="w-12 h-12 text-[#0476D9] mx-auto animate-spin" />
<p className="text-gray-600 mt-4">Loading...</p>
</div>
</div>
);
}
// Show login if not authenticated
if (!isAuthenticated) {
return <LoginForm />;
}
// Group CVEs by CVE ID
const groupedCVEs = cves.reduce((acc, cve) => {
if (!acc[cve.cve_id]) {
@@ -257,15 +294,25 @@ export default function App() {
<h1 className="text-3xl font-bold text-gray-900 mb-2">CVE Dashboard</h1>
<p className="text-gray-600">Query vulnerabilities, manage vendors, and attach documentation</p>
</div>
<button
onClick={() => setShowAddCVE(true)}
className="px-4 py-2 bg-[#0476D9] text-white rounded-lg hover:bg-[#0360B8] transition-colors flex items-center gap-2 shadow-md"
>
<Plus className="w-5 h-5" />
Add CVE/Vendor
</button>
<div className="flex items-center gap-4">
{canWrite() && (
<button
onClick={() => setShowAddCVE(true)}
className="px-4 py-2 bg-[#0476D9] text-white rounded-lg hover:bg-[#0360B8] transition-colors flex items-center gap-2 shadow-md"
>
<Plus className="w-5 h-5" />
Add CVE/Vendor
</button>
)}
<UserMenu onManageUsers={() => setShowUserManagement(true)} />
</div>
</div>
{/* User Management Modal */}
{showUserManagement && (
<UserManagement onClose={() => setShowUserManagement(false)} />
)}
{/* Add CVE Modal */}
{showAddCVE && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
@@ -634,6 +681,7 @@ export default function App() {
>
View
</a>
{isAdmin() && (
<button
onClick={() => handleDeleteDocument(doc.id, cve.cve_id, cve.vendor)}
className="px-3 py-1 text-sm text-red-600 hover:bg-red-50 rounded transition-colors border border-red-600 flex items-center gap-1"
@@ -641,6 +689,7 @@ export default function App() {
<Trash2 className="w-3 h-3" />
Delete
</button>
)}
</div>
</div>
))}
@@ -648,14 +697,16 @@ export default function App() {
) : (
<p className="text-sm text-gray-500 italic">No documents attached yet</p>
)}
<button
onClick={() => handleFileUpload(cve.cve_id, cve.vendor)}
disabled={uploadingFile}
className="mt-3 px-4 py-2 text-sm text-gray-600 hover:bg-gray-100 rounded-lg transition-colors flex items-center gap-2 disabled:opacity-50 border border-gray-300"
>
<Upload className="w-4 h-4" />
{uploadingFile ? 'Uploading...' : 'Upload Document'}
</button>
{canWrite() && (
<button
onClick={() => handleFileUpload(cve.cve_id, cve.vendor)}
disabled={uploadingFile}
className="mt-3 px-4 py-2 text-sm text-gray-600 hover:bg-gray-100 rounded-lg transition-colors flex items-center gap-2 disabled:opacity-50 border border-gray-300"
>
<Upload className="w-4 h-4" />
{uploadingFile ? 'Uploading...' : 'Upload Document'}
</button>
)}
</div>
)}
</div>