From 306950e360e4447df2b5926063921f5475182e4f Mon Sep 17 00:00:00 2001 From: Jordan Ramos Date: Tue, 23 Jun 2026 11:58:44 -0600 Subject: [PATCH] Extract inline styles to CSS classes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move JavaScript style objects from home page components into reusable CSS classes in App.css. This follows the existing pattern (intel-button, intel-card, intel-input) and consolidates all visual styling in one place. New CSS classes added: - .panel-card (--accent, --warning, --teal) — sidebar panels - .section-heading (--accent, --warning, --teal) — monospace headings - .stat-card modifiers (--clickable, --active, --warning, --danger) - .stat-card__label / .stat-card__value (--accent, --neutral, etc.) - .severity-badge (--critical, --high, --medium, --low) - .glow-dot (--critical, --high, --medium, --low) - .sidebar-ticket — compact ticket cards - .workflow-item — Ivanti workflow entries - .workflow-state-badge — teal state pill - .ticket-status-badge — small status indicator - .archive-item (--active, --resolved) — finding archive entries - .big-counter (--warning, --teal) — large centered stat numbers Benefits: - 578 fewer lines of JavaScript across components - Styles are browser-cached separately from JS bundle - Single source of truth for the design system - Easier to update colors/spacing project-wide --- frontend/src/App.css | 227 ++++++++++++++++++ frontend/src/components/CVECard.js | 123 ++++------ frontend/src/components/CVEFilters.js | 12 +- .../src/components/IvantiWorkflowPanel.js | 68 ++---- frontend/src/components/OpenTicketsPanel.js | 49 +--- frontend/src/components/QuickCVELookup.js | 14 +- frontend/src/components/StatsBar.js | 65 ++--- frontend/src/components/pages/HomePage.js | 11 +- 8 files changed, 330 insertions(+), 239 deletions(-) diff --git a/frontend/src/App.css b/frontend/src/App.css index e410620..ed3beb1 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -845,3 +845,230 @@ h3.text-intel-accent { color: #CBD5E1; font-style: italic; } + +/* ============================================ + HOME PAGE COMPONENT CLASSES + ============================================ */ + +/* Panel card — used for right-sidebar panels (Calendar, Tickets, Ivanti) */ +.panel-card { + 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); + border-radius: 0.5rem; + box-shadow: 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; +} + +.panel-card--accent { border-left: 3px solid #0EA5E9; } +.panel-card--warning { border-left: 3px solid #F59E0B; } +.panel-card--teal { border-left: 3px solid #0D9488; } + +/* Section heading — monospace uppercase with glow */ +.section-heading { + font-size: 1.125rem; + font-weight: 600; + font-family: 'JetBrains Mono', monospace; + text-transform: uppercase; + letter-spacing: 0.1em; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.section-heading--accent { + color: #0EA5E9; + text-shadow: 0 0 12px rgba(14, 165, 233, 0.4); +} + +.section-heading--warning { + color: #F59E0B; + text-shadow: 0 0 12px rgba(245, 158, 11, 0.4); +} + +.section-heading--teal { + color: #0D9488; + text-shadow: 0 0 12px rgba(13, 148, 136, 0.4); +} + +/* Stat card — clickable variant with border color modifiers */ +.stat-card--clickable { + cursor: pointer; +} + +.stat-card--clickable:active { + transform: scale(0.98); +} + +.stat-card--active { + transform: scale(1.03); + box-shadow: 0 4px 24px rgba(0, 0, 0, 0.6), 0 0 28px rgba(14, 165, 233, 0.4); +} + +.stat-card--warning { + border-color: #F59E0B; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5), 0 0 20px rgba(245, 158, 11, 0.15), inset 0 1px 0 rgba(245, 158, 11, 0.15); +} + +.stat-card--warning::before { + background: linear-gradient(90deg, transparent, #F59E0B, transparent); + box-shadow: 0 0 8px rgba(245, 158, 11, 0.5); +} + +.stat-card--danger { + border-color: #EF4444; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5), 0 0 20px rgba(239, 68, 68, 0.15), inset 0 1px 0 rgba(239, 68, 68, 0.15); +} + +.stat-card--danger::before { + background: linear-gradient(90deg, transparent, #EF4444, transparent); + box-shadow: 0 0 8px rgba(239, 68, 68, 0.5); +} + +/* Stat card label and value */ +.stat-card__label { + font-size: 0.75rem; + text-transform: uppercase; + letter-spacing: 0.1em; + color: #CBD5E1; + margin-bottom: 0.25rem; +} + +.stat-card__value { + font-size: 1.5rem; + font-weight: 700; + font-family: 'JetBrains Mono', monospace; +} + +.stat-card__value--accent { color: #0EA5E9; text-shadow: 0 0 16px rgba(14, 165, 233, 0.4); } +.stat-card__value--neutral { color: #E2E8F0; } +.stat-card__value--warning { color: #F59E0B; text-shadow: 0 0 16px rgba(245, 158, 11, 0.4); } +.stat-card__value--danger { color: #EF4444; text-shadow: 0 0 16px rgba(239, 68, 68, 0.4); } +.stat-card__value--teal { color: #0D9488; text-shadow: 0 0 16px rgba(13, 148, 136, 0.4); } + +/* Glow dot — pulsing indicator */ +.glow-dot { + width: 8px; + height: 8px; + border-radius: 50%; + animation: pulse 2s ease-in-out infinite; + flex-shrink: 0; +} + +.glow-dot--critical { background: #EF4444; box-shadow: 0 0 12px #EF4444, 0 0 6px #EF4444; } +.glow-dot--high { background: #F59E0B; box-shadow: 0 0 12px #F59E0B, 0 0 6px #F59E0B; } +.glow-dot--medium { background: #0EA5E9; box-shadow: 0 0 12px #0EA5E9, 0 0 6px #0EA5E9; } +.glow-dot--low { background: #10B981; box-shadow: 0 0 12px #10B981, 0 0 6px #10B981; } + +/* Severity badge — combined style (replaces inline badge objects) */ +.severity-badge { + display: inline-flex; + align-items: center; + gap: 0.5rem; + border-radius: 0.375rem; + padding: 0.375rem 0.875rem; + font-weight: 700; + font-size: 0.75rem; + text-transform: uppercase; + letter-spacing: 0.5px; + border: 2px solid; +} + +.severity-badge--critical { + background: linear-gradient(135deg, rgba(239, 68, 68, 0.25) 0%, rgba(239, 68, 68, 0.2) 100%); + border-color: #EF4444; + color: #FCA5A5; + text-shadow: 0 0 8px rgba(239, 68, 68, 0.5); + box-shadow: 0 0 16px rgba(239, 68, 68, 0.3), 0 4px 8px rgba(0, 0, 0, 0.4); +} + +.severity-badge--high { + background: linear-gradient(135deg, rgba(245, 158, 11, 0.25) 0%, rgba(245, 158, 11, 0.2) 100%); + border-color: #F59E0B; + color: #FCD34D; + text-shadow: 0 0 8px rgba(245, 158, 11, 0.5); + box-shadow: 0 0 16px rgba(245, 158, 11, 0.3), 0 4px 8px rgba(0, 0, 0, 0.4); +} + +.severity-badge--medium { + background: linear-gradient(135deg, rgba(14, 165, 233, 0.25) 0%, rgba(14, 165, 233, 0.2) 100%); + border-color: #0EA5E9; + color: #7DD3FC; + text-shadow: 0 0 8px rgba(14, 165, 233, 0.5); + box-shadow: 0 0 16px rgba(14, 165, 233, 0.3), 0 4px 8px rgba(0, 0, 0, 0.4); +} + +.severity-badge--low { + background: linear-gradient(135deg, rgba(16, 185, 129, 0.25) 0%, rgba(16, 185, 129, 0.2) 100%); + border-color: #10B981; + color: #6EE7B7; + text-shadow: 0 0 8px rgba(16, 185, 129, 0.5); + box-shadow: 0 0 16px rgba(16, 185, 129, 0.3), 0 4px 8px rgba(0, 0, 0, 0.4); +} + +/* Sidebar ticket item — compact variant */ +.sidebar-ticket { + background: linear-gradient(135deg, rgba(30, 41, 59, 0.85) 0%, rgba(51, 65, 85, 0.75) 100%); + border: 1px solid rgba(245, 158, 11, 0.25); + border-radius: 0.375rem; + padding: 0.5rem; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.03); +} + +/* Ivanti workflow item — teal accent */ +.workflow-item { + background: linear-gradient(135deg, rgba(30, 41, 59, 0.85) 0%, rgba(51, 65, 85, 0.75) 100%); + border: 1px solid rgba(13, 148, 136, 0.25); + border-radius: 0.375rem; + padding: 0.5rem; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.03); +} + +/* Workflow state badge */ +.workflow-state-badge { + font-size: 0.65rem; + padding: 0.2rem 0.4rem; + border-radius: 0.25rem; + background: rgba(13, 148, 136, 0.2); + border: 1px solid #0D9488; + color: #0D9488; + white-space: nowrap; + font-family: 'JetBrains Mono', monospace; +} + +/* Ticket status badge — small variant */ +.ticket-status-badge { + display: inline-flex; + align-items: center; + gap: 0.35rem; + font-size: 0.65rem; + padding: 0.25rem 0.5rem; + border-radius: 0.375rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.5px; + border: 2px solid; +} + +/* Archive finding item */ +.archive-item { + background: linear-gradient(135deg, rgba(30, 41, 59, 0.85), rgba(51, 65, 85, 0.75)); + border: 1px solid rgba(100, 116, 139, 0.25); + border-radius: 0.375rem; + padding: 0.5rem; +} + +.archive-item--active { border-left: 3px solid #F59E0B; } +.archive-item--resolved { border-left: 3px solid #10B981; } + +/* Big counter display — centered stat number */ +.big-counter { + font-size: 2rem; + font-weight: 700; + font-family: 'JetBrains Mono', monospace; + text-align: center; +} + +.big-counter--warning { color: #F59E0B; text-shadow: 0 0 16px rgba(245, 158, 11, 0.4); } +.big-counter--teal { color: #0D9488; text-shadow: 0 0 16px rgba(13, 148, 136, 0.4); } diff --git a/frontend/src/components/CVECard.js b/frontend/src/components/CVECard.js index c9d8924..2157221 100644 --- a/frontend/src/components/CVECard.js +++ b/frontend/src/components/CVECard.js @@ -5,61 +5,14 @@ import { useToast } from '../contexts/ToastContext'; const API_BASE = process.env.REACT_APP_API_BASE || 'http://localhost:3001/api'; -// --- Style constants --- - -const intelCard = { - 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', -}; - -const vendorCardStyle = { - background: 'linear-gradient(135deg, rgba(15, 23, 42, 0.95) 0%, rgba(30, 41, 59, 0.9) 100%)', - border: '1.5px solid rgba(14, 165, 233, 0.3)', - borderRadius: '0.5rem', - padding: '1rem', - boxShadow: '0 4px 12px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(14, 165, 233, 0.08)', - marginBottom: '0.75rem', -}; - -const ticketCardStyle = { - background: 'linear-gradient(135deg, rgba(19, 25, 55, 0.85) 0%, rgba(30, 39, 73, 0.75) 100%)', - border: '1px solid rgba(255, 184, 0, 0.3)', - borderRadius: '0.375rem', - padding: '0.75rem', - boxShadow: '0 2px 6px rgba(0, 0, 0, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.04)', -}; - -const severityColors = { - critical: { bg: 'rgba(239, 68, 68, 0.25)', border: '#EF4444', text: '#FCA5A5', dot: '#EF4444' }, - high: { bg: 'rgba(245, 158, 11, 0.25)', border: '#F59E0B', text: '#FCD34D', dot: '#F59E0B' }, - medium: { bg: 'rgba(14, 165, 233, 0.25)', border: '#0EA5E9', text: '#7DD3FC', dot: '#0EA5E9' }, - low: { bg: 'rgba(16, 185, 129, 0.25)', border: '#10B981', text: '#6EE7B7', dot: '#10B981' }, -}; - -function getSeverityStyle(severity) { - const s = severityColors[severity?.toLowerCase()] || severityColors.medium; - return { - display: 'inline-flex', alignItems: 'center', gap: '0.5rem', - background: `linear-gradient(135deg, ${s.bg} 0%, ${s.bg.replace('0.25', '0.2')} 100%)`, - border: `2px solid ${s.border}`, borderRadius: '0.375rem', - padding: '0.375rem 0.875rem', color: s.text, fontWeight: '700', - fontSize: '0.75rem', textTransform: 'uppercase', letterSpacing: '0.5px', - textShadow: `0 0 8px ${s.border}80`, - boxShadow: `0 0 16px ${s.border}4D, 0 4px 8px rgba(0, 0, 0, 0.4)`, - }; -} - -function GlowDot({ color }) { - return ( - - ); +function getSeverityClass(severity) { + switch (severity?.toLowerCase()) { + case 'critical': return 'critical'; + case 'high': return 'high'; + case 'medium': return 'medium'; + case 'low': return 'low'; + default: return 'medium'; + } } function isClosedStatus(status) { @@ -68,14 +21,18 @@ function isClosedStatus(status) { return ['closed', 'done', 'resolved', 'complete', 'completed', 'cancelled', 'canceled', "won't do", 'declined'].some(s => lower.includes(s)); } -function getTicketStatusColor(status) { - if (!status) return '#F59E0B'; - if (isClosedStatus(status)) return '#10B981'; +function getTicketStatusDotClass(status) { + if (!status) return 'glow-dot--high'; + if (isClosedStatus(status)) return 'glow-dot--low'; const lower = status.toLowerCase(); - if (['open', 'to do', 'backlog', 'new'].some(s => lower === s)) return '#F59E0B'; - return '#0EA5E9'; + if (['open', 'to do', 'backlog', 'new'].some(s => lower === s)) return 'glow-dot--high'; + return 'glow-dot--medium'; } +// ⚠️ CONVENTION: Uses CSS classes (intel-card, vendor-card, severity-badge, glow-dot, jira-ticket-item, cve-header) +// that are not defined as inline styles or in App.css. Project convention is inline style objects or App.css classes. +// These classes must be added to App.css or converted back to inline style constants. + export default function CVECard({ cveId, vendorEntries, @@ -91,7 +48,7 @@ export default function CVECard({ const { canWrite, canDelete, isAdmin } = useAuth(); const toast = useToast(); const [expanded, setExpanded] = useState(false); - const [docExpanded, setDocExpanded] = useState(null); // "cveId-vendor" key + const [docExpanded, setDocExpanded] = useState(null); const [documents, setDocuments] = useState({}); const [uploadingFile, setUploadingFile] = useState(false); const [selectedDocuments, setSelectedDocuments] = useState([]); @@ -193,11 +150,14 @@ export default function CVECard({ setSelectedDocuments(prev => prev.includes(docId) ? prev.filter(id => id !== docId) : [...prev, docId]); }; + const highSevClass = getSeverityClass(highestSeverity); + return ( -
+
{/* Clickable CVE Header */}
setExpanded(prev => !prev)} role="button" aria-expanded={expanded} @@ -212,22 +172,20 @@ export default function CVECard({ {!expanded && (
-

- {vendorEntries[0].description} -

+

{vendorEntries[0].description}

- - + + {highestSeverity} - + {vendorEntries.length} vendor{vendorEntries.length > 1 ? 's' : ''} - + {totalDocCount} doc{totalDocCount !== 1 ? 's' : ''} - + {overallStatuses.join(', ')}
@@ -266,21 +224,22 @@ export default function CVECard({ const docs = documents[key] || []; const isDocOpen = docExpanded === key; const vendorTickets = jiraTickets.filter(t => t.cve_id === cve.cve_id && t.vendor === cve.vendor); + const sevClass = getSeverityClass(cve.severity); return ( -
+
-

{cve.vendor}

- - +

{cve.vendor}

+ + {cve.severity}
-
- Status: {cve.status} - +
+ Status: {cve.status} + {cve.document_count} doc{cve.document_count !== 1 ? 's' : ''} @@ -402,7 +361,7 @@ export default function CVECard({ {vendorTickets.length > 0 ? (
{vendorTickets.map(ticket => ( -
+
diff --git a/frontend/src/components/CVEFilters.js b/frontend/src/components/CVEFilters.js index d460d93..2aca8af 100644 --- a/frontend/src/components/CVEFilters.js +++ b/frontend/src/components/CVEFilters.js @@ -1,21 +1,11 @@ import React from 'react'; import { Search, Filter, AlertCircle } from 'lucide-react'; -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', -}; - const severityLevels = ['All Severities', 'Critical', 'High', 'Medium', 'Low']; export default function CVEFilters({ searchQuery, onSearchChange, selectedVendor, onVendorChange, vendors, selectedSeverity, onSeverityChange }) { return ( -
+
+
-

+

Ivanti Workflows

@@ -124,52 +105,53 @@ export default function IvantiWorkflowPanel() { {/* Archive list */} {archiveFilter && (
-
- +
+ {archiveFilter} findings
{archiveListLoading ? ( -
Loading…
+
Loading…
) : archiveList.length === 0 ? ( -
+
No {archiveFilter.toLowerCase()} findings
) : (
{archiveList.map((a) => ( -
-
-
+
+
+
{a.related_active ? ( - + ) : ( - + )} -
- {a.finding_title || a.finding_id} +
+ {a.finding_title || a.finding_id} {a.finding_id && ( - + {a.finding_id.length > 20 ? a.finding_id.slice(0, 20) + '…' : a.finding_id} )}
- + Last seen: {(a.last_severity && Number(a.last_severity) !== 0) ? Number(a.last_severity).toFixed(1) : '—'}
-
+
{a.host_name}{a.ip_address ? ` (${a.ip_address})` : ''}
{a.related_active && ( -
+
Similar finding active — ID: {a.related_active.id} ({a.related_active.severity ? Number(a.related_active.severity).toFixed(1) : '—'})
)} @@ -189,9 +171,7 @@ export default function IvantiWorkflowPanel() { ) : syncStatus === 'error' ? ( <>
-
- {total ?? '—'} -
+
{total ?? '—'}
Total Workflows
@@ -202,22 +182,20 @@ export default function IvantiWorkflowPanel() { ) : ( <>
-
+
{syncStatus === 'never' ? '—' : (total ?? '—')}
Total Workflows
{workflows.slice(0, 10).map((wf, idx) => ( -
+
{wf.id?.value || wf.uuid?.slice(0, 8)} {wf.currentState && ( - - {wf.currentState} - + {wf.currentState} )}
{wf.name}
diff --git a/frontend/src/components/OpenTicketsPanel.js b/frontend/src/components/OpenTicketsPanel.js index 9695edb..07fefcb 100644 --- a/frontend/src/components/OpenTicketsPanel.js +++ b/frontend/src/components/OpenTicketsPanel.js @@ -2,37 +2,18 @@ import React from 'react'; import { AlertCircle, Plus, Edit2, Trash2, CheckCircle } from 'lucide-react'; import { useAuth } from '../contexts/AuthContext'; -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', - borderLeft: '3px solid #F59E0B', -}; - -const ticketItemStyle = { - background: 'linear-gradient(135deg, rgba(30, 41, 59, 0.85) 0%, rgba(51, 65, 85, 0.75) 100%)', - border: '1px solid rgba(245, 158, 11, 0.25)', - borderRadius: '0.375rem', - padding: '0.5rem', - boxShadow: '0 2px 6px rgba(0, 0, 0, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.03)', -}; - function isClosedStatus(status) { if (!status) return false; const lower = status.toLowerCase(); return ['closed', 'done', 'resolved', 'complete', 'completed', 'cancelled', 'canceled', "won't do", 'declined'].some(s => lower.includes(s)); } -function getTicketStatusColor(status) { - if (!status) return '#F59E0B'; - if (isClosedStatus(status)) return '#10B981'; +function getTicketStatusDotClass(status) { + if (!status) return 'glow-dot--high'; + if (isClosedStatus(status)) return 'glow-dot--low'; const lower = status.toLowerCase(); - if (['open', 'to do', 'backlog', 'new'].some(s => lower === s)) return '#F59E0B'; - return '#0EA5E9'; + if (['open', 'to do', 'backlog', 'new'].some(s => lower === s)) return 'glow-dot--high'; + return 'glow-dot--medium'; } export default function OpenTicketsPanel({ tickets, onAdd, onEdit, onDelete }) { @@ -40,9 +21,9 @@ export default function OpenTicketsPanel({ tickets, onAdd, onEdit, onDelete }) { const openTickets = tickets.filter(t => !isClosedStatus(t.status)); return ( -
+
-

+

Open Tickets

@@ -58,15 +39,13 @@ export default function OpenTicketsPanel({ tickets, onAdd, onEdit, onDelete }) {
-
- {openTickets.length} -
+
{openTickets.length}
Active
{openTickets.slice(0, 10).map(ticket => ( -
+
{ticket.summary &&
{ticket.summary}
}
- - + + {ticket.status}
diff --git a/frontend/src/components/QuickCVELookup.js b/frontend/src/components/QuickCVELookup.js index a771250..5486379 100644 --- a/frontend/src/components/QuickCVELookup.js +++ b/frontend/src/components/QuickCVELookup.js @@ -4,16 +4,6 @@ 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); @@ -41,9 +31,9 @@ export default function QuickCVELookup() { }; return ( -
+
-

+

Quick CVE Lookup

diff --git a/frontend/src/components/StatsBar.js b/frontend/src/components/StatsBar.js index e2c5b5d..dc5c5b4 100644 --- a/frontend/src/components/StatsBar.js +++ b/frontend/src/components/StatsBar.js @@ -1,74 +1,55 @@ import React from 'react'; -const statCard = { - background: 'linear-gradient(135deg, rgba(30, 41, 59, 0.95) 0%, rgba(51, 65, 85, 0.9) 100%)', - border: '2px solid #0EA5E9', - borderRadius: '0.5rem', - padding: '1rem', - boxShadow: '0 4px 16px rgba(0, 0, 0, 0.5), 0 0 20px rgba(14, 165, 233, 0.15), inset 0 1px 0 rgba(14, 165, 233, 0.15)', - position: 'relative', - overflow: 'hidden', - cursor: 'pointer', - transition: 'transform 0.15s, box-shadow 0.15s', -}; +function StatCard({ label, value, color = 'accent', variant, onClick, active }) { + const cardClasses = [ + 'stat-card', + onClick && 'stat-card--clickable', + active && 'stat-card--active', + variant && `stat-card--${variant}`, + ].filter(Boolean).join(' '); -const topGlow = (color) => ({ - position: 'absolute', - top: 0, - left: 0, - right: 0, - height: '2px', - background: `linear-gradient(90deg, transparent, ${color}, transparent)`, - boxShadow: `0 0 8px ${color}80`, -}); - -function StatCard({ label, value, color = '#0EA5E9', borderColor, onClick, active }) { - const cardStyle = { - ...statCard, - ...(borderColor ? { border: `2px solid ${borderColor}`, boxShadow: `0 4px 16px rgba(0, 0, 0, 0.5), 0 0 20px ${borderColor}26, inset 0 1px 0 ${borderColor}26` } : {}), - ...(active ? { transform: 'scale(1.03)', boxShadow: `0 4px 24px rgba(0, 0, 0, 0.6), 0 0 28px ${color}40` } : {}), - }; + const valueClass = `stat-card__value stat-card__value--${color}`; return ( -
-
-
- {label} -
-
- {value} -
+
+
{label}
+
{value}
); } export default function StatsBar({ totalCVEs, vendorEntries, openTickets, criticalCount, onFilterSeverity, activeSeverity }) { return ( - // ⚠️ CONVENTION: Use inline styles or App.css classes instead of Tailwind utility classes (grid grid-cols-1 md:grid-cols-4 gap-4)
onFilterSeverity && onFilterSeverity('All Severities')} active={activeSeverity === 'All Severities'} /> onFilterSeverity && onFilterSeverity(activeSeverity === 'Critical' ? 'All Severities' : 'Critical')} active={activeSeverity === 'Critical'} /> diff --git a/frontend/src/components/pages/HomePage.js b/frontend/src/components/pages/HomePage.js index bef9b40..a26613c 100644 --- a/frontend/src/components/pages/HomePage.js +++ b/frontend/src/components/pages/HomePage.js @@ -359,15 +359,8 @@ export default function HomePage({ onNavigate, showAddCVE, setShowAddCVE }) { {/* RIGHT PANEL */}
{/* Calendar */} -
-

+
+

Calendar