admin / SAynapse-Horizon
publicSelf Hosted Cyber Threat Intelligence Hub
SAynapse-Horizon / Synapse-Horizon-v2 / src / components / CriticalTriagePanel.jsx
6504 B · main
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | import { Siren, ChevronRight, ShieldAlert, Check, CheckCheck } from 'lucide-react' import { SOURCE_MAP } from '../data/sources' import { severityMeta } from '../utils/theme' import { formatUtcTime, relativeAge } from '../utils/format' // Executive critical radar: at most 5 highest-priority live events, each of which // must be acknowledged. Unacknowledged events drive the flashing header alarm. export default function CriticalTriagePanel({ rows, loading, onSelect, displayTick, acknowledgedIds, onAcknowledge, onAcknowledgeAll, }) { const isAcked = (id) => acknowledgedIds?.has(id) const unacked = rows.filter((r) => !isAcked(r.id)).length return ( <section className={`rounded-xl border bg-gradient-to-b to-slate-900/40 ${ unacked > 0 ? 'border-rose-500/30 from-rose-950/25' : 'border-slate-800/80 from-slate-900/40' }`} > <header className="flex items-center justify-between gap-3 border-b border-slate-800/70 px-4 py-2.5"> <div className="flex items-center gap-2"> <Siren className={`h-4 w-4 ${unacked > 0 ? 'text-rose-400' : 'text-slate-500'}`} /> <h2 className="text-sm font-semibold text-slate-100">Critical Priority Triage</h2> <span className="rounded bg-rose-500/15 px-1.5 py-px text-[10px] font-semibold uppercase tracking-wide text-rose-300 ring-1 ring-inset ring-rose-500/30"> Top 5 </span> </div> {unacked > 0 ? ( <button type="button" onClick={onAcknowledgeAll} className="flex items-center gap-1.5 rounded-md bg-rose-500/15 px-2 py-1 text-[11px] font-semibold text-rose-300 ring-1 ring-inset ring-rose-500/40 transition-colors hover:bg-rose-500/25" > <CheckCheck className="h-3.5 w-3.5" /> Acknowledge all ({unacked}) </button> ) : rows.length > 0 ? ( <span className="flex items-center gap-1.5 text-[11px] font-medium text-emerald-400"> <Check className="h-3.5 w-3.5" /> All acknowledged </span> ) : ( <span className="text-[10px] font-medium uppercase tracking-widest text-slate-500"> Auto-resorting radar </span> )} </header> <div className="p-2"> {loading ? ( <SkeletonRows /> ) : rows.length === 0 ? ( <Empty /> ) : ( <ul className="space-y-1"> {rows.map((r) => { const sev = severityMeta(r.severity) const src = SOURCE_MAP[r.source] const acked = isAcked(r.id) return ( <li key={r.id} className={`flex items-stretch gap-1.5 rounded-lg border border-transparent transition-colors ${ acked ? 'opacity-55' : sev.row }`} > <button type="button" onClick={() => onSelect(r)} className="group flex min-w-0 flex-1 animate-slide-in items-center gap-3 rounded-lg px-2.5 py-2 text-left" > <span className={`h-8 w-1 shrink-0 rounded-full ${acked ? 'bg-emerald-500/60' : sev.bar}`} /> <span className={`inline-flex w-[74px] shrink-0 justify-center rounded px-1.5 py-1 text-[10px] font-bold tracking-wide ${sev.badge}`} > {sev.label} </span> <div className="min-w-0 flex-1"> <div className="truncate text-sm font-medium text-slate-100">{r.target}</div> <div className="truncate text-[11px] text-slate-500">{r.category}</div> </div> <div className="hidden shrink-0 text-right sm:block"> <div className="text-xs font-medium text-slate-300">{src?.name}</div> <div className="font-mono text-[10px] text-slate-500"> {formatUtcTime(r.detectedAt)} · {relativeAge(r.detectedAt, displayTick)} </div> </div> <ChevronRight className="h-4 w-4 shrink-0 text-slate-600 transition-transform group-hover:translate-x-0.5 group-hover:text-slate-400" /> </button> <button type="button" onClick={() => onAcknowledge(r.id)} disabled={acked} aria-label={acked ? 'Acknowledged' : `Acknowledge ${r.target}`} title={acked ? 'Acknowledged' : 'Acknowledge event'} className={`my-1 mr-1 flex w-[58px] shrink-0 items-center justify-center gap-1 rounded-md text-[11px] font-bold transition-colors ${ acked ? 'cursor-default text-emerald-400' : 'border border-rose-500/40 bg-rose-500/10 text-rose-300 hover:bg-rose-500/25' }`} > {acked ? ( <> <Check className="h-3.5 w-3.5" /> ACK </> ) : ( 'ACK' )} </button> </li> ) })} </ul> )} </div> </section> ) } function SkeletonRows() { return ( <ul className="space-y-1"> {Array.from({ length: 4 }).map((_, i) => ( <li key={i} className="flex items-center gap-3 px-2.5 py-2"> <div className="h-8 w-1 rounded-full bg-slate-800" /> <div className="h-6 w-[74px] animate-pulse rounded bg-slate-800" /> <div className="flex-1 space-y-1.5"> <div className="h-3 w-1/2 animate-pulse rounded bg-slate-800" /> <div className="h-2.5 w-1/4 animate-pulse rounded bg-slate-800/70" /> </div> </li> ))} </ul> ) } function Empty() { return ( <div className="flex flex-col items-center justify-center gap-2 px-4 py-8 text-center"> <ShieldAlert className="h-6 w-6 text-slate-600" /> <p className="text-sm font-medium text-slate-400">No critical events in active channels</p> <p className="text-[11px] text-slate-500"> Enable more feed sources or run a force sync to populate the radar. </p> </div> ) } |