Catalyst / admin/SAynapse-Horizon 14.8 GB / 57.8 GB 40.0 GB free
Help Sign in

admin / SAynapse-Horizon

public

Self Hosted Cyber Threat Intelligence Hub

Code Issues Pull requests Pipelines Packages Security Insights Wiki Settings
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>
  )
}