Refactor home page: extract components, add toast system, debounce search
Major restructuring of the monolithic App.js (2484 lines) into focused, testable components: Architecture: - App.js is now a 189-line routing shell (header, nav, page switching) - HomePage.js orchestrates all home page state and layout - Each visual section is its own component with clear props API Extracted components: - StatsBar: clickable stat cards that filter by severity - QuickCVELookup: CVE existence check with inline results - CVEFilters: search + vendor/severity dropdowns - CVECard: expandable CVE with vendor entries, docs, tickets - OpenTicketsPanel: right sidebar open JIRA tickets - IvantiWorkflowPanel: right sidebar Ivanti workflow status + archive Extracted modals: - AddCVEModal: self-contained add form with NVD auto-fill - EditCVEModal: self-contained edit form with NVD update - JiraTicketModal: unified add/edit JIRA ticket modal - ArcherTicketModal: unified add/edit Archer ticket modal Performance optimizations: - Debounced search (300ms) via useDebounce hook — eliminates redundant API calls on every keystroke - Memoized groupedCVEs, openTicketCount, criticalCount via useMemo - Proper state updates (no direct mutation of cveDocuments) - useCallback on fetch functions to stabilize effect dependencies UX improvements: - Toast notification system replaces all alert() calls - Stat cards are now clickable to filter CVE list by severity - onKeyDown replaces deprecated onKeyPress - aria-labels added to interactive elements Infrastructure: - ToastContext with auto-dismiss, typed toasts (success/error/warning/info) - useDebounce custom hook for reuse across the app - Toast slide-in animation in App.css
This commit is contained in:
112
frontend/src/components/QuickCVELookup.js
Normal file
112
frontend/src/components/QuickCVELookup.js
Normal file
@@ -0,0 +1,112 @@
|
||||
import React, { useState } from 'react';
|
||||
import { CheckCircle, XCircle, AlertCircle } from 'lucide-react';
|
||||
import { useToast } from '../contexts/ToastContext';
|
||||
|
||||
const API_BASE = process.env.REACT_APP_API_BASE || 'http://localhost:3001/api';
|
||||
|
||||
const cardStyle = {
|
||||
background: 'linear-gradient(135deg, rgba(30, 41, 59, 0.95) 0%, rgba(51, 65, 85, 0.9) 50%, rgba(30, 41, 59, 0.95) 100%)',
|
||||
border: '2px solid rgba(14, 165, 233, 0.4)',
|
||||
borderRadius: '0.5rem',
|
||||
boxShadow: '0 8px 24px rgba(0, 0, 0, 0.6), 0 0 28px rgba(14, 165, 233, 0.15), inset 0 1px 0 rgba(14, 165, 233, 0.12)',
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
padding: '1.5rem',
|
||||
};
|
||||
|
||||
export default function QuickCVELookup() {
|
||||
const [query, setQuery] = useState('');
|
||||
const [result, setResult] = useState(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const toast = useToast();
|
||||
|
||||
const handleLookup = async () => {
|
||||
const trimmed = query.trim();
|
||||
if (!trimmed) return;
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/cves/check/${encodeURIComponent(trimmed)}`, {
|
||||
credentials: 'include'
|
||||
});
|
||||
if (!response.ok) throw new Error('Failed to check CVE');
|
||||
const data = await response.json();
|
||||
setResult(data);
|
||||
} catch (err) {
|
||||
toast.error(err.message);
|
||||
setResult({ error: err.message });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={cardStyle}>
|
||||
<div className="scan-line"></div>
|
||||
<h2 style={{ fontSize: '1.125rem', fontWeight: '600', color: '#0EA5E9', marginBottom: '0.75rem', fontFamily: 'monospace', textTransform: 'uppercase', letterSpacing: '0.1em', textShadow: '0 0 16px rgba(14, 165, 233, 0.4)' }}>
|
||||
Quick CVE Lookup
|
||||
</h2>
|
||||
<div className="flex gap-3">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Enter CVE ID (e.g., CVE-2024-1234)"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
onKeyDown={(e) => e.key === 'Enter' && handleLookup()}
|
||||
className="flex-1 intel-input"
|
||||
aria-label="CVE ID to look up"
|
||||
/>
|
||||
<button
|
||||
onClick={handleLookup}
|
||||
disabled={loading}
|
||||
className="intel-button intel-button-primary"
|
||||
>
|
||||
{loading ? 'Scanning...' : 'Scan'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{result && (
|
||||
<div className={`mt-4 p-4 rounded border ${result.exists ? 'bg-intel-success/10 border-intel-success/30' : 'bg-intel-warning/10 border-intel-warning/30'}`}>
|
||||
{result.error ? (
|
||||
<div className="flex items-start gap-3">
|
||||
<XCircle className="w-5 h-5 text-intel-danger mt-0.5" />
|
||||
<div>
|
||||
<p className="font-medium text-intel-danger font-mono">Error</p>
|
||||
<p className="text-sm text-gray-300">{result.error}</p>
|
||||
</div>
|
||||
</div>
|
||||
) : result.exists ? (
|
||||
<div className="flex items-start gap-3">
|
||||
<CheckCircle className="w-5 h-5 text-intel-success mt-0.5" />
|
||||
<div className="flex-1">
|
||||
<p className="font-medium text-intel-success font-mono">
|
||||
✓ CVE Addressed ({result.vendors.length} vendor{result.vendors.length > 1 ? 's' : ''})
|
||||
</p>
|
||||
<div className="mt-3 space-y-3">
|
||||
{result.vendors.map((vendorInfo, idx) => (
|
||||
<div key={idx} className="p-3 bg-intel-dark/70 rounded border border-intel-accent/30 shadow-lg">
|
||||
<p className="font-semibold text-white mb-2 font-sans">{vendorInfo.vendor}</p>
|
||||
<div className="grid grid-cols-2 gap-2 text-sm text-gray-300 mb-2 font-mono">
|
||||
<p><strong className="text-white">Severity:</strong> {vendorInfo.severity}</p>
|
||||
<p><strong className="text-white">Status:</strong> {vendorInfo.status}</p>
|
||||
<p><strong className="text-white">Documents:</strong> {vendorInfo.total_documents} attached</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-start gap-3">
|
||||
<AlertCircle className="w-5 h-5 text-intel-warning mt-0.5" />
|
||||
<div>
|
||||
<p className="font-medium text-intel-warning font-mono">Not Found</p>
|
||||
<p className="text-sm text-gray-300">This CVE has not been addressed yet. No entry exists in the database.</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user