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 / App.jsx 3630 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
import { useMemo, useState } from 'react'
import { AlertTriangle, X } from 'lucide-react'
import { useFeeds } from './hooks/useFeeds'
import { SOURCES } from './data/sources'
import Header from './components/Header'
import Sidebar from './components/Sidebar'
import MetricsRow from './components/MetricsRow'
import CriticalTriagePanel from './components/CriticalTriagePanel'
import CTIFeed from './components/CTIFeed'
import DetailsDrawer from './components/DetailsDrawer'

export default function App() {
  const feeds = useFeeds()
  const [selected, setSelected] = useState(null)
  const [dismissedError, setDismissedError] = useState(null)
  const showError = feeds.error && feeds.error !== dismissedError

  // Per-source counts for the sidebar badges (respecting only that source's own toggle).
  const countsBySource = useMemo(() => {
    const counts = Object.fromEntries(SOURCES.map((s) => [s.key, 0]))
    for (const r of feeds.store) counts[r.source] = (counts[r.source] || 0) + 1
    return counts
  }, [feeds.store])

  return (
    <div className="flex h-screen flex-col overflow-hidden bg-[#0a0e14] text-slate-200">
      <Header
        activeCount={feeds.activeCount}
        totalSources={feeds.totalSources}
        lastPull={feeds.lastPull}
        loading={feeds.loading}
        displayTick={feeds.displayTick}
        triageCount={feeds.triageCount}
      />

      <div className="flex min-h-0 flex-1">
        <Sidebar
          enabled={feeds.enabled}
          onToggle={feeds.toggleSource}
          refreshKey={feeds.refreshKey}
          onRefreshChange={feeds.setRefreshKey}
          cadenceKey={feeds.cadenceKey}
          onCadenceChange={feeds.setCadenceKey}
          onForceSync={() => feeds.pull()}
          loading={feeds.loading}
          visibleCountBySource={countsBySource}
          sourceStatus={feeds.sourceStatus}
        />

        <main className="flex min-h-0 flex-1 flex-col gap-3 overflow-hidden p-4">
          {showError && (
            <div className="flex items-center gap-3 rounded-lg border border-rose-500/40 bg-rose-500/10 px-4 py-2.5 text-sm text-rose-200">
              <AlertTriangle className="h-4 w-4 shrink-0 text-rose-400" />
              <div className="min-w-0 flex-1">
                <span className="font-medium">Ingestion service error:</span>{' '}
                <span className="text-rose-300/90">{feeds.error}</span>
              </div>
              <button
                onClick={() => setDismissedError(feeds.error)}
                className="shrink-0 rounded p-1 text-rose-300 hover:bg-rose-500/20"
                aria-label="Dismiss"
              >
                <X className="h-4 w-4" />
              </button>
            </div>
          )}

          <MetricsRow
            activeCount={feeds.activeCount}
            totalSources={feeds.totalSources}
            lastPull={feeds.lastPull}
            totalIocs={feeds.visible.length}
            loading={feeds.loading}
          />

          <CriticalTriagePanel
            rows={feeds.critical}
            loading={feeds.loading}
            onSelect={setSelected}
            displayTick={feeds.displayTick}
            acknowledgedIds={feeds.acknowledgedIds}
            onAcknowledge={feeds.acknowledge}
            onAcknowledgeAll={feeds.acknowledgeAll}
          />

          <CTIFeed
            rows={feeds.visible}
            loading={feeds.loading}
            onSelect={setSelected}
            displayTick={feeds.displayTick}
          />
        </main>
      </div>

      <DetailsDrawer row={selected} onClose={() => setSelected(null)} />
    </div>
  )
}