diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..81d2de0 --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,4 @@ +# Backend Configuration +PORT=3001 +API_HOST=localhost +CORS_ORIGINS=http://localhost:3000 diff --git a/backend/migrate_multivendor.js b/backend/migrate_multivendor.js new file mode 100644 index 0000000..aa476d0 --- /dev/null +++ b/backend/migrate_multivendor.js @@ -0,0 +1,128 @@ +const sqlite3 = require('sqlite3').verbose(); +const db = new sqlite3.Database('./cve_database.db'); + +console.log('šŸ”„ Starting database migration for multi-vendor support...\n'); + +db.serialize(() => { + // Backup existing data + console.log('šŸ“¦ Creating backup tables...'); + db.run(`CREATE TABLE IF NOT EXISTS cves_backup AS SELECT * FROM cves`, (err) => { + if (err) console.error('Backup error:', err); + else console.log('āœ“ CVEs backed up'); + }); + + db.run(`CREATE TABLE IF NOT EXISTS documents_backup AS SELECT * FROM documents`, (err) => { + if (err) console.error('Backup error:', err); + else console.log('āœ“ Documents backed up'); + }); + + // Drop old table + console.log('\nšŸ—‘ļø Dropping old cves table...'); + db.run(`DROP TABLE IF EXISTS cves`, (err) => { + if (err) { + console.error('Drop error:', err); + return; + } + console.log('āœ“ Old table dropped'); + + // Create new table with UNIQUE(cve_id, vendor) instead of UNIQUE(cve_id) + console.log('\nšŸ—ļø Creating new cves table with multi-vendor support...'); + db.run(` + CREATE TABLE cves ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + cve_id VARCHAR(20) NOT NULL, + vendor VARCHAR(100) NOT NULL, + severity VARCHAR(20) NOT NULL, + description TEXT, + published_date DATE, + status VARCHAR(50) DEFAULT 'Open', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UNIQUE(cve_id, vendor) + ) + `, (err) => { + if (err) { + console.error('Create error:', err); + return; + } + console.log('āœ“ New table created with UNIQUE(cve_id, vendor)'); + + // Restore data + console.log('\nšŸ“„ Restoring data...'); + db.run(`INSERT INTO cves SELECT * FROM cves_backup`, (err) => { + if (err) { + console.error('Restore error:', err); + return; + } + console.log('āœ“ Data restored'); + + // Recreate indexes + console.log('\nšŸ” Creating indexes...'); + db.run(`CREATE INDEX idx_cve_id ON cves(cve_id)`, () => { + console.log('āœ“ Index: idx_cve_id'); + }); + db.run(`CREATE INDEX idx_vendor ON cves(vendor)`, () => { + console.log('āœ“ Index: idx_vendor'); + }); + db.run(`CREATE INDEX idx_severity ON cves(severity)`, () => { + console.log('āœ“ Index: idx_severity'); + }); + db.run(`CREATE INDEX idx_status ON cves(status)`, () => { + console.log('āœ“ Index: idx_status'); + }); + + // Update view + console.log('\nšŸ‘ļø Updating cve_document_status view...'); + db.run(`DROP VIEW IF EXISTS cve_document_status`, (err) => { + if (err) console.error('Drop view error:', err); + + db.run(` + CREATE VIEW cve_document_status AS + SELECT + c.id as record_id, + c.cve_id, + c.vendor, + c.severity, + c.status, + COUNT(DISTINCT d.id) as total_documents, + COUNT(DISTINCT CASE WHEN d.type = 'advisory' THEN d.id END) as advisory_count, + COUNT(DISTINCT CASE WHEN d.type = 'email' THEN d.id END) as email_count, + COUNT(DISTINCT CASE WHEN d.type = 'screenshot' THEN d.id END) as screenshot_count, + CASE + WHEN COUNT(DISTINCT CASE WHEN d.type = 'advisory' THEN d.id END) > 0 + THEN 'Complete' + ELSE 'Missing Required Docs' + END as compliance_status + FROM cves c + LEFT JOIN documents d ON c.cve_id = d.cve_id AND c.vendor = d.vendor + GROUP BY c.id, c.cve_id, c.vendor, c.severity, c.status + `, (err) => { + if (err) { + console.error('Create view error:', err); + } else { + console.log('āœ“ View recreated'); + } + + console.log('\nāœ… Migration complete!'); + console.log('\nšŸ“Š Summary:'); + + db.get('SELECT COUNT(*) as count FROM cves', (err, row) => { + if (!err) console.log(` Total CVE entries: ${row.count}`); + + db.get('SELECT COUNT(DISTINCT cve_id) as count FROM cves', (err, row) => { + if (!err) console.log(` Unique CVE IDs: ${row.count}`); + + console.log('\nšŸ’” Next steps:'); + console.log(' 1. Restart backend: pkill -f "node server.js" && node server.js &'); + console.log(' 2. Replace frontend/src/App.js with multi-vendor version'); + console.log(' 3. Test by adding same CVE with multiple vendors\n'); + + db.close(); + }); + }); + }); + }); + }); + }); + }); +}); diff --git a/backend/server.js b/backend/server.js index 22f8448..d2a4b00 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,5 +1,7 @@ // CVE Management Backend API -// Install: npm install express sqlite3 multer cors +// Install: npm install express sqlite3 multer cors dotenv + +require('dotenv').config(); const express = require('express'); const sqlite3 = require('sqlite3').verbose(); @@ -9,11 +11,21 @@ const path = require('path'); const fs = require('fs'); const app = express(); -const PORT = 3001; +const PORT = process.env.PORT || 3001; +const API_HOST = process.env.API_HOST || 'localhost'; +const CORS_ORIGINS = process.env.CORS_ORIGINS + ? process.env.CORS_ORIGINS.split(',') + : ['http://localhost:3000']; + +// Log all incoming requests +app.use((req, res, next) => { + console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`); + next(); +}); // Middleware app.use(cors({ - origin: ['http://localhost:3000', 'http://192.168.2.117:3000'], + origin: CORS_ORIGINS, credentials: true })); app.use(express.json()); @@ -94,7 +106,7 @@ app.get('/api/cves', (req, res) => { }); }); -// Check if CVE exists and get its status +// Check if CVE exists and get its status - UPDATED FOR MULTI-VENDOR app.get('/api/cves/check/:cveId', (req, res) => { const { cveId } = req.params; @@ -105,80 +117,53 @@ app.get('/api/cves/check/:cveId', (req, res) => { COUNT(CASE WHEN d.type = 'email' THEN 1 END) as has_email, COUNT(CASE WHEN d.type = 'screenshot' THEN 1 END) as has_screenshot FROM cves c - LEFT JOIN documents d ON c.cve_id = d.cve_id + LEFT JOIN documents d ON c.cve_id = d.cve_id AND c.vendor = d.vendor WHERE c.cve_id = ? GROUP BY c.id `; - db.get(query, [cveId], (err, row) => { + db.all(query, [cveId], (err, rows) => { if (err) { return res.status(500).json({ error: err.message }); } - if (!row) { + if (!rows || rows.length === 0) { return res.json({ exists: false, message: 'CVE not found - not yet addressed' }); } + // Return all vendor entries for this CVE res.json({ exists: true, - cve: row, + vendors: rows.map(row => ({ + vendor: row.vendor, + severity: row.severity, + status: row.status, + total_documents: row.total_documents, + compliance: { + advisory: row.has_advisory > 0, + email: row.has_email > 0, + screenshot: row.has_screenshot > 0 + } + })), addressed: true, - has_required_docs: row.has_advisory > 0, - compliance: { - advisory: row.has_advisory > 0, - email: row.has_email > 0, - screenshot: row.has_screenshot > 0 - } + has_required_docs: rows.some(row => row.has_advisory > 0) }); }); }); -// Create new CVE entry -app.post('/api/cves', (req, res) => { - const { cve_id, vendor, severity, description, published_date } = req.body; +// NEW ENDPOINT: Get all vendors for a specific CVE +app.get('/api/cves/:cveId/vendors', (req, res) => { + const { cveId } = req.params; const query = ` - INSERT INTO cves (cve_id, vendor, severity, description, published_date) - VALUES (?, ?, ?, ?, ?) + SELECT vendor, severity, status, description, published_date + FROM cves + WHERE cve_id = ? + ORDER BY vendor `; - db.run(query, [cve_id, vendor, severity, description, published_date], function(err) { - if (err) { - return res.status(500).json({ error: err.message }); - } - res.json({ - id: this.lastID, - cve_id, - message: 'CVE created successfully' - }); - }); -}); - -// Update CVE status -app.patch('/api/cves/:cveId/status', (req, res) => { - const { cveId } = req.params; - const { status } = req.body; - - const query = `UPDATE cves SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE cve_id = ?`; - - db.run(query, [status, cveId], function(err) { - if (err) { - return res.status(500).json({ error: err.message }); - } - res.json({ message: 'Status updated successfully', changes: this.changes }); - }); -}); - -// ========== DOCUMENT ENDPOINTS ========== - -// Get documents for a CVE -app.get('/api/cves/:cveId/documents', (req, res) => { - const { cveId } = req.params; - - const query = `SELECT * FROM documents WHERE cve_id = ? ORDER BY uploaded_at DESC`; - db.all(query, [cveId], (err, rows) => { if (err) { return res.status(500).json({ error: err.message }); @@ -187,66 +172,161 @@ app.get('/api/cves/:cveId/documents', (req, res) => { }); }); -// Upload document -app.post('/api/cves/:cveId/documents', upload.single('file'), (req, res) => { - const { cveId } = req.params; - const { type, notes, vendor } = req.body; - const file = req.file; - - if (!file) { - return res.status(400).json({ error: 'No file uploaded' }); - } - - if (!vendor) { - return res.status(400).json({ error: 'Vendor is required' }); - } - - // Move file from temp to proper location - const finalDir = path.join('uploads', cveId, vendor); - if (!fs.existsSync(finalDir)) { - fs.mkdirSync(finalDir, { recursive: true }); - } - - const finalPath = path.join(finalDir, file.filename); - - // Move file from temp to final location - fs.renameSync(file.path, finalPath); + +// Create new CVE entry - ALLOW MULTIPLE VENDORS +app.post('/api/cves', (req, res) => { + console.log('=== ADD CVE REQUEST ==='); + console.log('Body:', req.body); + console.log('======================='); + const { cve_id, vendor, severity, description, published_date } = req.body; + const query = ` - INSERT INTO documents (cve_id, name, type, file_path, file_size, mime_type, notes) - VALUES (?, ?, ?, ?, ?, ?, ?) + INSERT INTO cves (cve_id, vendor, severity, description, published_date) + VALUES (?, ?, ?, ?, ?) `; - - const fileSizeKB = (file.size / 1024).toFixed(2) + ' KB'; - - db.run(query, [ - cveId, - file.originalname, - type, - finalPath, - fileSizeKB, - file.mimetype, - notes - ], function(err) { + + console.log('Query:', query); + console.log('Values:', [cve_id, vendor, severity, description, published_date]); + + db.run(query, [cve_id, vendor, severity, description, published_date], function(err) { if (err) { - // If database insert fails, delete the file - if (fs.existsSync(finalPath)) { - fs.unlinkSync(finalPath); + console.error('DATABASE ERROR:', err); // Make sure this is here + // ... rest of error handling + // Check if it's a duplicate CVE_ID + Vendor combination + if (err.message.includes('UNIQUE constraint failed')) { + return res.status(409).json({ + error: 'This CVE already exists for this vendor. Choose a different vendor or update the existing entry.' + }); } return res.status(500).json({ error: err.message }); } res.json({ - id: this.lastID, - message: 'Document uploaded successfully', - file: { - name: file.originalname, - path: finalPath, - size: fileSizeKB - } + id: this.lastID, + cve_id, + message: `CVE created successfully for vendor: ${vendor}` }); }); }); + +// Update CVE status +app.patch('/api/cves/:cveId/status', (req, res) => { + const { cveId } = req.params; + const { status } = req.body; + + const query = `UPDATE cves SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE cve_id = ?`; + + db.run(query, [ + vendor,status, cveId], function(err) { + if (err) { + return res.status(500).json({ error: err.message }); + } + res.json({ message: 'Status updated successfully', changes: this.changes }); + }); +}); + +// ========== DOCUMENT ENDPOINTS ========== + +// Get documents for a CVE - FILTER BY VENDOR +app.get('/api/cves/:cveId/documents', (req, res) => { + const { cveId } = req.params; + const { vendor } = req.query; // NEW: Optional vendor filter + + let query = `SELECT * FROM documents WHERE cve_id = ?`; + let params = [cveId]; + + if (vendor) { + query += ` AND vendor = ?`; + params.push(vendor); + } + + query += ` ORDER BY uploaded_at DESC`; + + db.all(query, params, (err, rows) => { + if (err) { + return res.status(500).json({ error: err.message }); + } + res.json(rows); + }); +}); + +// Upload document - ADD ERROR HANDLING FOR MULTER +app.post('/api/cves/:cveId/documents', (req, res, next) => { + upload.single('file')(req, res, (err) => { + if (err) { + console.error('MULTER ERROR:', err); + return res.status(500).json({ error: 'File upload failed: ' + err.message }); + } + + console.log('=== UPLOAD REQUEST RECEIVED ==='); + console.log('CVE ID:', req.params.cveId); + console.log('Body:', req.body); + console.log('File:', req.file); + console.log('================================'); + + const { cveId } = req.params; + const { type, notes, vendor } = req.body; + const file = req.file; + + if (!file) { + console.error('ERROR: No file uploaded'); + return res.status(400).json({ error: 'No file uploaded' }); + } + + if (!vendor) { + console.error('ERROR: Vendor is required'); + return res.status(400).json({ error: 'Vendor is required' }); + } + + // Move file from temp to proper location + const finalDir = path.join('uploads', cveId, vendor); + if (!fs.existsSync(finalDir)) { + fs.mkdirSync(finalDir, { recursive: true }); + } + + const finalPath = path.join(finalDir, file.filename); + + // Move file from temp to final location + fs.renameSync(file.path, finalPath); + + const query = ` + INSERT INTO documents (cve_id, vendor, name, type, file_path, file_size, mime_type, notes) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + `; + + const fileSizeKB = (file.size / 1024).toFixed(2) + ' KB'; + + db.run(query, [ + cveId, + vendor, + file.originalname, + type, + finalPath, + fileSizeKB, + file.mimetype, + notes + ], function(err) { + if (err) { + console.error('DATABASE ERROR:', err); + // If database insert fails, delete the file + if (fs.existsSync(finalPath)) { + fs.unlinkSync(finalPath); + } + return res.status(500).json({ error: err.message }); + } + res.json({ + id: this.lastID, + message: 'Document uploaded successfully', + file: { + name: file.originalname, + path: finalPath, + size: fileSizeKB + } + }); + }); + }); +}); // Delete document app.delete('/api/documents/:id', (req, res) => { const { id } = req.params; @@ -308,5 +388,6 @@ app.get('/api/stats', (req, res) => { // Start server app.listen(PORT, () => { - console.log(`CVE API server running on http://localhost:${PORT}`); + console.log(`CVE API server running on http://${API_HOST}:${PORT}`); + console.log(`CORS origins: ${CORS_ORIGINS.join(', ')}`); }); diff --git a/backend/setup.js b/backend/setup.js index 7b62398..9e38cc8 100644 --- a/backend/setup.js +++ b/backend/setup.js @@ -1,5 +1,5 @@ // Setup Script for CVE Database -// This creates a fresh database ready for new CVE entries +// This creates a fresh database with multi-vendor support built-in const sqlite3 = require('sqlite3').verbose(); const fs = require('fs'); @@ -18,19 +18,21 @@ function initializeDatabase() { const schema = ` CREATE TABLE IF NOT EXISTS cves ( id INTEGER PRIMARY KEY AUTOINCREMENT, - cve_id VARCHAR(20) UNIQUE NOT NULL, + cve_id VARCHAR(20) NOT NULL, vendor VARCHAR(100) NOT NULL, severity VARCHAR(20) NOT NULL, description TEXT, published_date DATE, status VARCHAR(50) DEFAULT 'Open', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UNIQUE(cve_id, vendor) ); CREATE TABLE IF NOT EXISTS documents ( id INTEGER PRIMARY KEY AUTOINCREMENT, cve_id VARCHAR(20) NOT NULL, + vendor VARCHAR(100) NOT NULL, name VARCHAR(255) NOT NULL, type VARCHAR(50) NOT NULL, file_path VARCHAR(500) NOT NULL, @@ -54,6 +56,7 @@ function initializeDatabase() { CREATE INDEX IF NOT EXISTS idx_severity ON cves(severity); CREATE INDEX IF NOT EXISTS idx_status ON cves(status); CREATE INDEX IF NOT EXISTS idx_doc_cve_id ON documents(cve_id); + CREATE INDEX IF NOT EXISTS idx_doc_vendor ON documents(vendor); CREATE INDEX IF NOT EXISTS idx_doc_type ON documents(type); INSERT OR IGNORE INTO required_documents (vendor, document_type, is_mandatory, description) VALUES @@ -66,6 +69,7 @@ function initializeDatabase() { CREATE VIEW IF NOT EXISTS cve_document_status AS SELECT + c.id as record_id, c.cve_id, c.vendor, c.severity, @@ -80,8 +84,8 @@ function initializeDatabase() { ELSE 'Missing Required Docs' END as compliance_status FROM cves c - LEFT JOIN documents d ON c.cve_id = d.cve_id - GROUP BY c.cve_id, c.vendor, c.severity, c.status; + LEFT JOIN documents d ON c.cve_id = d.cve_id AND c.vendor = d.vendor + GROUP BY c.id, c.cve_id, c.vendor, c.severity, c.status; `; db.exec(schema, (err) => { @@ -107,17 +111,21 @@ function createUploadsDirectory() { // Add sample CVE data (optional - for testing) async function addSampleData(db) { - console.log('\nšŸ“ Would you like to add sample CVE data for testing? (y/n)'); + console.log('\nšŸ“ Adding sample CVE data for testing...'); - // For automated setup, we'll skip this. Uncomment the code below if you want samples. - - /* const sampleCVEs = [ { cve_id: 'CVE-2024-SAMPLE-1', vendor: 'Microsoft', severity: 'Critical', - description: 'Sample vulnerability for testing', + description: 'Sample remote code execution vulnerability', + published_date: '2024-01-15' + }, + { + cve_id: 'CVE-2024-SAMPLE-1', + vendor: 'Cisco', + severity: 'High', + description: 'Sample remote code execution vulnerability', published_date: '2024-01-15' } ]; @@ -131,16 +139,37 @@ async function addSampleData(db) { (err) => { if (err) reject(err); else { - console.log(` āœ“ Added sample CVE: ${cve.cve_id}`); + console.log(` āœ“ Added sample: ${cve.cve_id} / ${cve.vendor}`); resolve(); } } ); }); } - */ - console.log('ā„¹ļø Skipping sample data - you can add CVEs through the API or dashboard'); + console.log('ā„¹ļø Sample data added - demonstrates multi-vendor support'); +} + +// Verify database structure +async function verifySetup(db) { + return new Promise((resolve) => { + db.get('SELECT sql FROM sqlite_master WHERE type="table" AND name="cves"', (err, row) => { + if (err) { + console.error('Warning: Could not verify setup:', err); + } else { + console.log('\nšŸ“‹ CVEs table structure:'); + console.log(row.sql); + + // Check if UNIQUE constraint is correct + if (row.sql.includes('UNIQUE(cve_id, vendor)')) { + console.log('\nāœ… Multi-vendor support: ENABLED'); + } else { + console.log('\nāš ļø Warning: Multi-vendor constraint may not be set correctly'); + } + } + resolve(); + }); + }); } // Display setup summary @@ -151,33 +180,37 @@ function displaySummary() { console.log('\nšŸ“Š What was created:'); console.log(' āœ“ SQLite database (cve_database.db)'); console.log(' āœ“ Tables: cves, documents, required_documents'); + console.log(' āœ“ Multi-vendor support with UNIQUE(cve_id, vendor)'); + console.log(' āœ“ Vendor column in documents table'); console.log(' āœ“ Indexes for fast queries'); console.log(' āœ“ Document compliance view'); console.log(' āœ“ Uploads directory for file storage'); console.log('\nšŸ“ File structure will be:'); console.log(' uploads/'); console.log(' └── CVE-XXXX-XXXX/'); - console.log(' └── VendorName/'); - console.log(' ā”œā”€ā”€ advisory.pdf'); - console.log(' ā”œā”€ā”€ email.pdf'); - console.log(' └── screenshot.png'); + console.log(' ā”œā”€ā”€ Vendor1/'); + console.log(' │ ā”œā”€ā”€ advisory.pdf'); + console.log(' │ └── screenshot.png'); + console.log(' └── Vendor2/'); + console.log(' └── advisory.pdf'); console.log('\nšŸš€ Next steps:'); console.log(' 1. Start the backend API:'); console.log(' → cd backend && node server.js'); console.log(' 2. Start the frontend:'); console.log(' → cd frontend && npm start'); console.log(' 3. Open http://localhost:3000'); - console.log(' 4. Start adding CVEs and uploading documents!'); - console.log('\nšŸ’” Tips:'); - console.log(' • Use the Quick Check to verify CVE status'); - console.log(' • Upload documents through the dashboard'); - console.log(' • Documents are auto-organized by CVE ID → Vendor'); + console.log(' 4. Start adding CVEs with multiple vendors!'); + console.log('\nšŸ’” Key Features:'); + console.log(' • Add same CVE-ID with different vendors'); + console.log(' • Each vendor has separate document storage'); + console.log(' • Quick Check shows all vendors for a CVE'); + console.log(' • Document compliance tracking per vendor'); console.log(' • Required docs: Advisory (mandatory for most vendors)\n'); } // Main execution async function main() { - console.log('šŸš€ CVE Database Setup\n'); + console.log('šŸš€ CVE Database Setup (Multi-Vendor Support)\n'); console.log('════════════════════════════════════════\n'); try { @@ -187,13 +220,16 @@ async function main() { // Initialize database const db = await initializeDatabase(); - // Optionally add sample data + // Add sample data await addSampleData(db); + // Verify setup + await verifySetup(db); + // Close database connection db.close((err) => { if (err) console.error('Error closing database:', err); - else console.log('āœ“ Database connection closed'); + else console.log('\nāœ“ Database connection closed'); // Display summary displaySummary(); @@ -206,4 +242,4 @@ async function main() { } // Run the setup -main(); \ No newline at end of file +main(); diff --git a/frontend/.env.example b/frontend/.env.example new file mode 100644 index 0000000..cfc2405 --- /dev/null +++ b/frontend/.env.example @@ -0,0 +1,5 @@ +# Frontend Configuration +# API_BASE should include the /api path +REACT_APP_API_BASE=http://localhost:3001/api +# API_HOST is used for direct file URLs (no /api) +REACT_APP_API_HOST=http://localhost:3001 diff --git a/frontend/src/App.js b/frontend/src/App.js index 41542a6..d909e2a 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -1,7 +1,8 @@ import React, { useState, useEffect } from 'react'; -import { Search, FileText, AlertCircle, Download, Upload, Eye, Filter, CheckCircle, XCircle, Loader, Trash2 } from 'lucide-react'; +import { Search, FileText, AlertCircle, Download, Upload, Eye, Filter, CheckCircle, XCircle, Loader, Trash2, Plus } from 'lucide-react'; -const API_BASE = 'http://192.168.2.117:3001/api'; +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'; const severityLevels = ['All Severities', 'Critical', 'High', 'Medium', 'Low']; @@ -10,6 +11,7 @@ export default function App() { 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']); @@ -73,14 +75,15 @@ export default function App() { } }; - const fetchDocuments = async (cveId) => { - if (cveDocuments[cveId]) return; + const fetchDocuments = async (cveId, vendor) => { + const key = `${cveId}-${vendor}`; + if (cveDocuments[key]) return; try { - const response = await fetch(`${API_BASE}/cves/${cveId}/documents`); + const response = await fetch(`${API_BASE}/cves/${cveId}/documents?vendor=${vendor}`); if (!response.ok) throw new Error('Failed to fetch documents'); const data = await response.json(); - setCveDocuments(prev => ({ ...prev, [cveId]: data })); + setCveDocuments(prev => ({ ...prev, [key]: data })); } catch (err) { console.error('Error fetching documents:', err); } @@ -100,12 +103,15 @@ export default function App() { } }; - const handleViewDocuments = async (cveId) => { - if (selectedCVE === cveId) { + const handleViewDocuments = async (cveId, vendor) => { + const key = `${cveId}-${vendor}`; + if (selectedCVE === cveId && selectedVendorView === vendor) { setSelectedCVE(null); + setSelectedVendorView(null); } else { setSelectedCVE(cveId); - await fetchDocuments(cveId); + setSelectedVendorView(vendor); + await fetchDocuments(cveId, vendor); } }; @@ -140,9 +146,12 @@ export default function App() { body: JSON.stringify(newCVE) }); - if (!response.ok) throw new Error('Failed to add CVE'); + if (!response.ok) { + const data = await response.json(); + throw new Error(data.error || 'Failed to add CVE'); + } - alert(`CVE ${newCVE.cve_id} added successfully!`); + alert(`CVE ${newCVE.cve_id} added successfully for vendor: ${newCVE.vendor}!`); setShowAddCVE(false); setNewCVE({ cve_id: '', @@ -192,8 +201,9 @@ export default function App() { if (!response.ok) throw new Error('Failed to upload document'); alert(`Document uploaded successfully!`); - delete cveDocuments[cveId]; - await fetchDocuments(cveId); + const key = `${cveId}-${vendor}`; + delete cveDocuments[key]; + await fetchDocuments(cveId, vendor); fetchCVEs(); } catch (err) { alert(`Error: ${err.message}`); @@ -205,7 +215,7 @@ export default function App() { fileInput.click(); }; - const handleDeleteDocument = async (docId, cveId) => { + const handleDeleteDocument = async (docId, cveId, vendor) => { if (!window.confirm('Are you sure you want to delete this document?')) { return; } @@ -218,20 +228,30 @@ export default function App() { if (!response.ok) throw new Error('Failed to delete document'); alert('Document deleted successfully!'); - delete cveDocuments[cveId]; - await fetchDocuments(cveId); + const key = `${cveId}-${vendor}`; + delete cveDocuments[key]; + await fetchDocuments(cveId, vendor); fetchCVEs(); } catch (err) { alert(`Error: ${err.message}`); } }; - const filteredCVEs = cves; + // Group CVEs by CVE ID + const groupedCVEs = cves.reduce((acc, cve) => { + if (!acc[cve.cve_id]) { + acc[cve.cve_id] = []; + } + acc[cve.cve_id].push(cve); + return acc; + }, {}); + + const filteredGroupedCVEs = groupedCVEs; return (
- {/* Header with Charter Branding */} + {/* Header */}

CVE Dashboard

@@ -241,8 +261,8 @@ export default function App() { 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" > - + - Add New CVE + + Add CVE/Vendor
@@ -252,7 +272,7 @@ export default function App() {
-

Add New CVE

+

Add CVE Entry

+
+

+ Tip: You can add the same CVE-ID multiple times with different vendors. + Each vendor will have its own documents folder. +

+
+
@@ -288,6 +316,7 @@ export default function App() { onChange={(e) => setNewCVE({...newCVE, vendor: 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" /> +

Must be unique for this CVE-ID

@@ -338,7 +367,7 @@ export default function App() { type="submit" className="flex-1 px-4 py-2 bg-[#0476D9] text-white rounded-lg hover:bg-[#0360B8] transition-colors font-medium shadow-md" > - Add CVE + Add CVE Entry + {Object.entries(filteredGroupedCVEs).map(([cveId, vendorEntries]) => ( +
+
+ {/* CVE Header */} +
+

{cveId}

+

{vendorEntries[0].description}

+
+ Published: {vendorEntries[0].published_date} + • + {vendorEntries.length} affected vendor{vendorEntries.length > 1 ? 's' : ''}
+
- {/* Documents Section */} - {selectedCVE === cve.cve_id && ( -
-

- - Attached Documents ({documents.length}) -

- {documents.length > 0 ? ( -
- {documents.map(doc => ( -
-
- toggleDocumentSelection(doc.id)} - className="w-4 h-4 text-[#0476D9] rounded focus:ring-2 focus:ring-[#0476D9]" - /> - -
-

{doc.name}

-

- {doc.type} • {doc.file_size} - {doc.notes && ` • ${doc.notes}`} -

-
-
-
- - View - - -
+ {/* Vendor Entries */} +
+ {vendorEntries.map((cve) => { + const key = `${cve.cve_id}-${cve.vendor}`; + const documents = cveDocuments[key] || []; + const isExpanded = selectedCVE === cve.cve_id && selectedVendorView === cve.vendor; + + return ( +
+
+
+
+

{cve.vendor}

+ + {cve.severity} + + + {cve.doc_status === 'Complete' ? 'āœ“ Docs Complete' : '⚠ Incomplete'} +
- ))} +
+ Status: {cve.status} + + + {cve.document_count} document{cve.document_count !== 1 ? 's' : ''} + +
+
+
- ) : ( -

No documents attached yet

- )} - -
- )} + + {/* Documents Section */} + {isExpanded && ( +
+
+ + Documents for {cve.vendor} ({documents.length}) +
+ {documents.length > 0 ? ( +
+ {documents.map(doc => ( +
+
+ toggleDocumentSelection(doc.id)} + className="w-4 h-4 text-[#0476D9] rounded focus:ring-2 focus:ring-[#0476D9]" + /> + +
+

{doc.name}

+

+ {doc.type} • {doc.file_size} + {doc.notes && ` • ${doc.notes}`} +

+
+
+
+ + View + + +
+
+ ))} +
+ ) : ( +

No documents attached yet

+ )} + +
+ )} +
+ ); + })}
- ); - })} +
+ ))}
)} - {filteredCVEs.length === 0 && !loading && ( + {Object.keys(filteredGroupedCVEs).length === 0 && !loading && (

No CVEs Found

diff --git a/package.json b/package.json index 3c4a60f..71947f0 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "license": "ISC", "dependencies": { "cors": "^2.8.6", + "dotenv": "^16.6.1", "express": "^5.2.1", "multer": "^2.0.2", "sqlite3": "^5.1.7" diff --git a/start-servers.sh b/start-servers.sh new file mode 100755 index 0000000..e0595a3 --- /dev/null +++ b/start-servers.sh @@ -0,0 +1,22 @@ +#!/bin/bash +echo "Starting CVE Dashboard servers..." + +# Start backend +cd backend +nohup node server.js > backend.log 2>&1 & +BACKEND_PID=$! +echo "Backend started (PID: $BACKEND_PID)" + +# Start frontend +cd ../frontend +nohup npm start > frontend.log 2>&1 & +FRONTEND_PID=$! +echo "Frontend started (PID: $FRONTEND_PID)" + +# Save PIDs +echo $BACKEND_PID > ../backend.pid +echo $FRONTEND_PID > ../frontend.pid + +echo "āœ“ Both servers running in background" +echo " Backend: http://localhost:3001" +echo " Frontend: http://localhost:3000" diff --git a/stop-servers.sh b/stop-servers.sh new file mode 100755 index 0000000..f7da22f --- /dev/null +++ b/stop-servers.sh @@ -0,0 +1,18 @@ +#!/bin/bash +echo "Stopping CVE Dashboard servers..." + +if [ -f backend.pid ]; then + kill $(cat backend.pid) 2>/dev/null + rm backend.pid + echo "āœ“ Backend stopped" +fi + +if [ -f frontend.pid ]; then + kill $(cat frontend.pid) 2>/dev/null + rm frontend.pid + echo "āœ“ Frontend stopped" +fi + +pkill -f "node server.js" +pkill -f "react-scripts start" +echo "All servers stopped"