// HomePrimitives.jsx — primitives for the CVE Dashboard Home page kit. // Lifted directly from frontend/src/App.js (the home view), normalized to // match the same vocabulary the Reporting + Knowledge Base kits use. // // Exported on window.HOME for the assembly + docs files to consume. const { useState: useHomeState } = React; /* ── Tokens ────────────────────────────────────────────────────── Identical palette to Reporting + KB. Home adds purple (Archer) and teal (Ivanti) — both used as left-rail / title-glow colors on the right-side panel stack. */ const H_COLORS = { sky: '#0EA5E9', skySoft: '#7DD3FC', green: '#10B981', amber: '#F59E0B', amberSoft: '#FCD34D', red: '#EF4444', redSoft: '#FCA5A5', purple: '#8B5CF6', teal: '#0D9488', }; /* Card chrome shared with the rest of the system. One chrome, every panel. */ const CARD_BG = 'linear-gradient(135deg, rgba(30,41,59,0.95) 0%, rgba(15,23,42,0.98) 100%)'; const CARD_BORDER = '1.5px solid rgba(14,165,233,0.12)'; const CARD_BORDER_HOVER = '1.5px solid rgba(14,165,233,0.35)'; /* ── StatCard ──────────────────────────────────────────────────── Top-of-page metric tile. Color-coded by tone — sky for neutral counts, amber for "needs attention", red for critical. Top edge has a soft horizontal glow line in the same color. */ function StatCard({ label, value, tone = 'sky', mono = true }) { const c = H_COLORS[tone] || H_COLORS.sky; const isAccent = tone !== 'neutral'; return (
{label}
{value}
); } /* ── HomeCard ──────────────────────────────────────────────────── Same chrome as Reporting's KbCard but without a label slot — the home cards put their title inline above the body. Used as the wrapper for Quick Lookup, the filter row, and CVE rows. */ function HomeCard({ children, padding = 24, hover = true, leftRail, style }) { const [h, setH] = useHomeState(false); return (
hover && setH(true)} onMouseLeave={() => setH(false)} style={{ background: CARD_BG, border: h ? CARD_BORDER_HOVER : CARD_BORDER, borderLeft: leftRail ? `3px solid ${leftRail}` : (h ? CARD_BORDER_HOVER : CARD_BORDER).split(' ').slice(0).join(' '), borderRadius: 8, padding, transition: 'border-color 200ms ease, box-shadow 200ms ease', position: 'relative', ...style, }} > {children}
); } /* ── CardTitle ─────────────────────────────────────────────────── Mono uppercase, glow color matches the card's identity (sky for neutral, amber for tickets, purple for Archer, teal for Ivanti). */ function CardTitle({ color = H_COLORS.sky, icon, children, action }) { return (

{icon && } {children}

{action}
); } /* ── HomeButton ────────────────────────────────────────────────── Wraps the four button variants the Home page uses, keeping the exact same tinted-fill / outlined treatment as the Reporting kit so all pages feel consistent. */ function HomeButton({ variant = 'neutral', icon, children, size = 'md', ...rest }) { const [hover, setHover] = useHomeState(false); const v = { primary: { bg: hover ? 'rgba(16,185,129,0.18)' : 'rgba(16,185,129,0.10)', bd: H_COLORS.green, fg: H_COLORS.green }, neutral: { bg: hover ? 'rgba(14,165,233,0.10)' : 'transparent', bd: 'rgba(14,165,233,0.5)', fg: H_COLORS.sky }, subtle: { bg: hover ? 'rgba(14,165,233,0.16)' : 'rgba(14,165,233,0.08)', bd: 'rgba(14,165,233,0.30)', fg: H_COLORS.sky }, danger: { bg: hover ? 'rgba(239,68,68,0.18)' : 'rgba(239,68,68,0.10)', bd: 'rgba(239,68,68,0.5)', fg: H_COLORS.red }, warning: { bg: hover ? 'rgba(245,158,11,0.18)' : 'rgba(245,158,11,0.10)', bd: 'rgba(245,158,11,0.5)', fg: H_COLORS.amber }, }[variant]; const padX = size === 'sm' ? 10 : 14; const padY = size === 'sm' ? 4 : 8; const fs = size === 'sm' ? 11 : 12; return ( ); } /* ── SeverityBadge ─────────────────────────────────────────────── Strong tinted-fill badge used in CVE rows. Critical/High/Medium/Low. */ function SeverityBadge({ level }) { const map = { Critical: { c: H_COLORS.red, text: H_COLORS.redSoft }, High: { c: H_COLORS.amber, text: H_COLORS.amberSoft }, Medium: { c: H_COLORS.sky, text: H_COLORS.skySoft }, Low: { c: H_COLORS.green, text: '#6EE7B7' }, }[level] || { c: H_COLORS.sky, text: H_COLORS.skySoft }; return ( {level} ); } /* ── StatusBadge ───────────────────────────────────────────────── Tone-coded text badge used for ticket statuses (Open / In Progress / Closed / Draft / Accepted). Smaller and lighter than SeverityBadge. */ function StatusBadge({ tone = 'sky', children, size = 'md' }) { const c = H_COLORS[tone] || H_COLORS.sky; const fs = size === 'sm' ? 10 : 11; const pad = size === 'sm' ? '3px 7px' : '4px 9px'; return ( {children} ); } /* ── HomeInput / HomeSelect ────────────────────────────────────── The intel-input look: dark fill + sky border on focus. */ function HomeInput({ icon, ...rest }) { const [focus, setFocus] = useHomeState(false); return (
{icon && (
)} setFocus(true)} onBlur={() => setFocus(false)} style={{ width: '100%', boxSizing: 'border-box', background: 'rgba(15,23,42,0.85)', border: `1px solid ${focus ? H_COLORS.sky : 'rgba(14,165,233,0.25)'}`, borderRadius: 6, padding: icon ? '9px 12px 9px 34px' : '9px 12px', color: 'var(--fg-1)', fontFamily: 'var(--font-mono)', fontSize: 13, outline: 'none', transition: 'border-color 160ms ease', boxShadow: focus ? `0 0 0 3px ${withAlpha(H_COLORS.sky, 0.15)}` : 'none', }} {...rest} />
); } function HomeSelect({ value, onChange, options }) { return ( ); } function FieldLabel({ icon, children }) { return ( ); } /* ── ResultBanner ──────────────────────────────────────────────── Sub-card used in Quick Lookup to surface scan results. Tones: success (CVE addressed), warning (not found), error. */ function ResultBanner({ tone = 'success', icon, title, children }) { const map = { success: { c: H_COLORS.green, bg: 'rgba(16,185,129,0.10)', bd: 'rgba(16,185,129,0.30)' }, warning: { c: H_COLORS.amber, bg: 'rgba(245,158,11,0.10)', bd: 'rgba(245,158,11,0.30)' }, error: { c: H_COLORS.red, bg: 'rgba(239,68,68,0.10)', bd: 'rgba(239,68,68,0.30)' }, }[tone]; return (
{title}
{children}
); } /* ── BigStat ───────────────────────────────────────────────────── The centered "active count + label" shown at the top of each right-rail panel (Open Tickets · Archer · Ivanti). */ function BigStat({ value, label, color = H_COLORS.sky }) { return (
{value}
{label}
); } /* ── MiniTicket ────────────────────────────────────────────────── Compact card shown inside the right-rail scrollable lists. Color-coded by category via the `tone` prop (amber/purple/teal). */ function MiniTicket({ keyText, cveId, vendor, status, tone = 'amber', summary, onEdit, onDelete }) { const c = H_COLORS[tone] || H_COLORS.amber; return (
{keyText} {(onEdit || onDelete) && (
{onEdit && } {onDelete && }
)}
{cveId}
{vendor}
{summary && (
{summary}
)} {status && (
{status}
)}
); } const iconBtn = (color) => ({ background: 'transparent', border: 'none', color: 'var(--fg-2)', cursor: 'pointer', padding: 2, display: 'inline-flex', alignItems: 'center', transition: 'color 120ms ease', }); /* ── CVERow ────────────────────────────────────────────────────── The main "row" in the home feed. Collapsed = chevron · CVE-ID · description · meta row (severity badge, vendor count, doc count, statuses). Expanded = full description + admin actions slot. */ function CVERow({ cveId, severity, description, vendorCount, docCount, statuses, expanded, onToggle, children }) { return (
{expanded && children && (
{children}
)}
); } const metaText = { fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--fg-2)', }; /* ── VendorEntry ───────────────────────────────────────────────── Sub-card inside an expanded CVE row, one per vendor that filed the CVE. Holds vendor name, severity, status, doc count, and inline action buttons. */ function VendorEntry({ vendor, severity, status, docCount, children, onView, onEdit, onDelete }) { return (

{vendor}

Status: {status} {docCount} doc{docCount !== 1 ? 's' : ''}
{onView && View} {onEdit && } {onDelete && }
{children && (
{children}
)}
); } /* ── HomeIcon ──────────────────────────────────────────────────── Inline SVGs covering every icon used on the home page so the kit has no external icon-font dependency. Keys mirror lucide-react names. */ function HomeIcon({ name, size = 16, color = 'currentColor' }) { const p = { width: size, height: size, viewBox: '0 0 24 24', fill: 'none', stroke: color, strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round', style: { display: 'inline-block', verticalAlign: 'middle' }, }; switch (name) { case 'search': return ; case 'filter': return ; case 'alert': return ; case 'check': case 'success': return ; case 'warning': return ; case 'error': case 'x': return ; case 'shield': return ; case 'activity': return ; case 'doc': return ; case 'eye': return ; case 'edit': return ; case 'trash': return ; case 'plus': return ; case 'refresh': return ; case 'chevron': return ; case 'upload': return ; case 'download': return ; case 'calendar': return ; default: return ; } } /* ── CalendarMini ──────────────────────────────────────────────── Minimal calendar surface for the right rail. Static — accepts a `today` index and an optional `markedDays` map for severity dots. */ function CalendarMini({ month = 'April 2026', today = 26, markedDays = {} }) { const dows = ['S', 'M', 'T', 'W', 'T', 'F', 'S']; // April 2026 starts on Wednesday — empty cells for S/M/T const startOffset = 3; const daysInMonth = 30; const cells = [...Array(startOffset).fill(null), ...Array.from({ length: daysInMonth }, (_, i) => i + 1)]; return (
{month}
{dows.map((d, i) => (
{d}
))} {cells.map((day, i) => { if (day === null) return
; const mark = markedDays[day]; const isToday = day === today; return ( ); })}
); } const navBtn = { background: 'transparent', border: '1px solid rgba(14,165,233,0.25)', color: H_COLORS.sky, borderRadius: 4, width: 22, height: 22, fontFamily: 'var(--font-mono)', fontSize: 12, cursor: 'pointer', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', }; /* ── ArchiveSummary ────────────────────────────────────────────── The bar of state pills that lives at the top of the Ivanti card. Each pill shows an Ivanti workflow state + count, color-coded. */ function ArchiveSummary({ items, activeFilter, onSelect }) { return (
{items.map(it => { const c = H_COLORS[it.tone] || H_COLORS.teal; const active = activeFilter === it.label; return ( ); })}
); } /* ── ScrollList ────────────────────────────────────────────────── Generic max-height scroll wrapper for the right-rail panels. */ function ScrollList({ maxHeight = 300, children }) { return (
{children}
); } /* ── EmptyState ────────────────────────────────────────────────── Center-aligned check-circle + caption, used inside ScrollList when a panel has no items. */ function EmptyState({ icon = 'check', tone = 'green', children }) { const c = H_COLORS[tone] || H_COLORS.green; return (

{children}

); } /* ── helpers ─────────────────────────────────────────────────── */ function withAlpha(hex, a) { const h = hex.replace('#', ''); const r = parseInt(h.slice(0, 2), 16); const g = parseInt(h.slice(2, 4), 16); const b = parseInt(h.slice(4, 6), 16); return `rgba(${r}, ${g}, ${b}, ${a})`; } window.HOME = { COLORS: H_COLORS, StatCard, HomeCard, CardTitle, HomeButton, SeverityBadge, StatusBadge, HomeInput, HomeSelect, FieldLabel, ResultBanner, BigStat, MiniTicket, CVERow, VendorEntry, HomeIcon, CalendarMini, ArchiveSummary, ScrollList, EmptyState, withAlpha, };