diff --git a/src/App.ts b/src/App.ts index 35a943a5a..b6b519f9e 100644 --- a/src/App.ts +++ b/src/App.ts @@ -82,6 +82,8 @@ import { InvestmentsPanel, LanguageSelector, } from '@/components'; +import { GatraSOCDashboardPanel } from '@/panels/gatra-soc-panel'; +import { refreshGatraData } from '@/gatra/connector'; import type { SearchResult } from '@/components/SearchModal'; import { collectStoryData } from '@/services/story-data'; import { renderStoryToCanvas } from '@/services/story-renderer'; @@ -196,6 +198,7 @@ export class App { private pendingDeepLinkCountry: string | null = null; private briefRequestToken = 0; private readonly isDesktopApp = isDesktopRuntime(); + private readonly isLocalDev = typeof window !== 'undefined' && window.location.hostname === 'localhost'; private readonly UPDATE_CHECK_INTERVAL_MS = 6 * 60 * 60 * 1000; // 6 hours private updateCheckIntervalId: ReturnType | null = null; private clockIntervalId: ReturnType | null = null; @@ -1833,33 +1836,43 @@ export class App { this.container.innerHTML = `
+ ${ /* Variant switcher โ€” on localhost/desktop, switch locally via localStorage */ ''} v${__APP_VERSION__}${BETA_MODE ? 'BETA' : ''} @@ -2375,6 +2388,12 @@ export class App { this.panels['etf-flows'] = new ETFFlowsPanel(); this.panels['stablecoins'] = new StablecoinPanel(); + // GATRA SOC Panel (cyber variant) + if (SITE_VARIANT === 'cyber') { + const gatraPanel = new GatraSOCDashboardPanel(); + this.panels['gatra-soc'] = gatraPanel; + } + // AI Insights Panel (desktop only - hides itself on mobile) const insightsPanel = new InsightsPanel(); this.panels['insights'] = insightsPanel; @@ -2687,8 +2706,8 @@ export class App { // Sources modal this.setupSourcesModal(); - // Variant switcher: switch variant locally on desktop (reload with new config) - if (this.isDesktopApp) { + // Variant switcher: switch variant locally on desktop or localhost (reload with new config) + if (this.isDesktopApp || this.isLocalDev) { this.container.querySelectorAll('.variant-option').forEach(link => { link.addEventListener('click', (e) => { const variant = link.dataset.variant; @@ -3174,6 +3193,11 @@ export class App { tasks.push({ name: 'techReadiness', task: runGuarded('techReadiness', () => (this.panels['tech-readiness'] as TechReadinessPanel)?.refresh()) }); } + // GATRA SOC data (cyber variant) + if (SITE_VARIANT === 'cyber' && this.mapLayers.gatraAlerts) { + tasks.push({ name: 'gatra', task: runGuarded('gatra', () => this.loadGatraData()) }); + } + // Use allSettled to ensure all tasks complete and search index always updates const results = await Promise.allSettled(tasks.map(t => t.task)); @@ -3209,6 +3233,9 @@ export class App { case 'cyberThreats': await this.loadCyberThreats(); break; + case 'gatraAlerts': + await this.loadGatraData(); + break; case 'ais': await this.loadAisSignals(); break; @@ -4088,6 +4115,20 @@ export class App { } } + private async loadGatraData(): Promise { + try { + const snap = await refreshGatraData(); + const gatraPanel = this.panels['gatra-soc'] as GatraSOCDashboardPanel | undefined; + gatraPanel?.refresh(); + if (this.mapLayers.gatraAlerts) { + this.map?.setGatraAlerts(snap.alerts); + } + dataFreshness.recordUpdate('gatra' as DataSourceId, snap.alerts.length); + } catch (error) { + console.error('[App] GATRA data load failed:', error); + } + } + private async loadAisSignals(): Promise { try { const { disruptions, density } = await fetchAisSignals(); @@ -4668,5 +4709,10 @@ export class App { this.cyberThreatsCache = null; return this.loadCyberThreats(); }, 10 * 60 * 1000, () => CYBER_LAYER_ENABLED && this.mapLayers.cyberThreats); + + // GATRA SOC data (60s refresh, cyber variant only) + if (SITE_VARIANT === 'cyber') { + this.scheduleRefresh('gatra', () => this.loadGatraData(), 60 * 1000, () => this.mapLayers.gatraAlerts); + } } } diff --git a/src/components/DeckGLMap.ts b/src/components/DeckGLMap.ts index d5626cea4..f8550543f 100644 --- a/src/components/DeckGLMap.ts +++ b/src/components/DeckGLMap.ts @@ -33,6 +33,7 @@ import type { MapDatacenterCluster, CyberThreat, CableHealthRecord, + GatraAlert, } from '@/types'; import type { AirportDelayAlert } from '@/services/aviation'; import type { DisplacementFlow } from '@/services/displacement'; @@ -40,6 +41,7 @@ import type { Earthquake } from '@/services/earthquakes'; import type { ClimateAnomaly } from '@/services/climate'; import { ArcLayer } from '@deck.gl/layers'; import { HeatmapLayer } from '@deck.gl/aggregation-layers'; +import { createGatraAlertsLayers as buildGatraLayers } from '@/layers/gatra-alerts-layer'; import type { WeatherAlert } from '@/services/weather'; import { escapeHtml } from '@/utils/sanitize'; import { t } from '@/services/i18n'; @@ -268,6 +270,7 @@ export class DeckGLMap { private ucdpEvents: UcdpGeoEvent[] = []; private displacementFlows: DisplacementFlow[] = []; private climateAnomalies: ClimateAnomaly[] = []; + private gatraAlerts: GatraAlert[] = []; // Country highlight state private countryGeoJsonLoaded = false; @@ -1140,6 +1143,11 @@ export class DeckGLMap { layers.push(this.createGulfInvestmentsLayer()); } + // GATRA SOC alerts layer + if (mapLayers.gatraAlerts && this.gatraAlerts.length > 0) { + layers.push(...this.createGatraAlertsLayers()); + } + // News geo-locations (always shown if data exists) if (this.newsLocations.length > 0) { layers.push(...this.createNewsLocationsLayer()); @@ -2449,6 +2457,8 @@ export class DeckGLMap { return { html: `
${text(obj.asn || t('components.deckgl.tooltip.internetOutage'))}
${text(obj.country)}
` }; case 'cyber-threats-layer': return { html: `
${t('popups.cyberThreat.title')}
${text(obj.severity || t('components.deckgl.tooltip.medium'))} ยท ${text(obj.country || t('popups.unknown'))}
` }; + case 'gatra-alerts-layer': + return { html: `
GATRA ${text(obj.severity?.toUpperCase() || 'ALERT')}
${text(obj.mitreId || '')} ${text(obj.mitreName || '')}
${text(obj.locationName || '')} ยท ${text(obj.infrastructure || '')}
${obj.confidence != null ? obj.confidence + '% confidence' : ''}
` }; case 'news-locations-layer': return { html: `
๐Ÿ“ฐ ${t('components.deckgl.tooltip.news')}
${text(obj.title?.slice(0, 80) || '')}
` }; case 'gulf-investments-layer': { @@ -2786,6 +2796,18 @@ export class DeckGLMap { { key: 'natural', label: t('components.deckgl.layers.naturalEvents'), icon: '🌋' }, { key: 'cyberThreats', label: t('components.deckgl.layers.cyberThreats'), icon: '🛡' }, ] + : SITE_VARIANT === 'cyber' + ? [ + { key: 'gatraAlerts', label: t('components.deckgl.layers.gatraAlerts'), icon: '🛡' }, + { key: 'cyberThreats', label: t('components.deckgl.layers.cyberThreats'), icon: '🔒' }, + { key: 'conflicts', label: t('components.deckgl.layers.conflictZones'), icon: '⚔' }, + { key: 'cables', label: t('components.deckgl.layers.underseaCables'), icon: '🔌' }, + { key: 'datacenters', label: t('components.deckgl.layers.aiDataCenters'), icon: '🖥' }, + { key: 'military', label: t('components.deckgl.layers.militaryActivity'), icon: '✈' }, + { key: 'outages', label: t('components.deckgl.layers.internetOutages'), icon: '📡' }, + { key: 'natural', label: t('components.deckgl.layers.naturalEvents'), icon: '🌋' }, + { key: 'fires', label: t('components.deckgl.layers.fires'), icon: '🔥' }, + ] : [ { key: 'hotspots', label: t('components.deckgl.layers.intelHotspots'), icon: '🎯' }, { key: 'conflicts', label: t('components.deckgl.layers.conflictZones'), icon: '⚔' }, @@ -3257,6 +3279,10 @@ export class DeckGLMap { }); } + private createGatraAlertsLayers(): Layer[] { + return buildGatraLayers(this.gatraAlerts, this.pulseTime || Date.now()); + } + // Data setters - all use render() for debouncing public setEarthquakes(earthquakes: Earthquake[]): void { this.earthquakes = earthquakes; @@ -3280,6 +3306,11 @@ export class DeckGLMap { this.render(); } + public setGatraAlerts(alerts: GatraAlert[]): void { + this.gatraAlerts = alerts; + this.render(); + } + public setAisData(disruptions: AisDisruptionEvent[], density: AisDensityZone[]): void { this.aisDisruptions = disruptions; this.aisDensity = density; diff --git a/src/components/GatraSOCPanel.ts b/src/components/GatraSOCPanel.ts new file mode 100644 index 000000000..af34ffee8 --- /dev/null +++ b/src/components/GatraSOCPanel.ts @@ -0,0 +1,179 @@ +/** + * GatraSOCPanel โ€” GATRA SOC integration dashboard panel. + * + * Self-contained pull pattern: App calls refresh() on a 60s interval. + * Displays agent status, incident stats, alert feed, and + * correlation insights linking WorldMonitor geopolitical events to GATRA alerts. + */ + +import { Panel } from './Panel'; +import { escapeHtml } from '@/utils/sanitize'; +import { + fetchGatraAlerts, + fetchGatraAgentStatus, + fetchGatraIncidentSummary, +} from '@/services/gatra'; +import type { + GatraAlert, + GatraAgentStatus, + GatraIncidentSummary, +} from '@/types'; + +export class GatraSOCPanel extends Panel { + private alerts: GatraAlert[] = []; + private agentStatus: GatraAgentStatus[] = []; + private summary: GatraIncidentSummary | null = null; + private loading = false; + + constructor() { + super({ + id: 'gatra-soc', + title: 'GATRA SOC', + showCount: true, + }); + } + + /** Called by App on 60s interval. */ + public async refresh(): Promise { + if (this.loading) return; + this.loading = true; + + try { + const [alerts, agents, summary] = await Promise.all([ + fetchGatraAlerts(), + fetchGatraAgentStatus(), + fetchGatraIncidentSummary(), + ]); + + this.alerts = alerts; + this.agentStatus = agents; + this.summary = summary; + + this.setCount(alerts.length); + this.render(); + } catch (err) { + console.error('[GatraSOCPanel] refresh error:', err); + this.showError('Failed to load GATRA data'); + } finally { + this.loading = false; + } + } + + /** Expose alerts for the map layer. */ + public getAlerts(): GatraAlert[] { + return this.alerts; + } + + // โ”€โ”€ Rendering โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + private render(): void { + const html = [ + this.renderAgentStatusBar(), + this.renderStatsRow(), + this.renderAlertFeed(), + this.renderCorrelation(), + ].join(''); + + this.setContent(html); + } + + private renderAgentStatusBar(): string { + if (this.agentStatus.length === 0) return ''; + + const dots = this.agentStatus + .map((a) => { + const color = + a.status === 'online' ? '#22c55e' : + a.status === 'processing' ? '#eab308' : + '#ef4444'; + const title = escapeHtml(`${a.fullName} โ€” ${a.status}`); + return ` + + ${escapeHtml(a.name)} + `; + }) + .join(''); + + return `
+ Agents + ${dots} +
`; + } + + private renderStatsRow(): string { + if (!this.summary) return ''; + const s = this.summary; + const stat = (label: string, value: string | number) => + `
+
${value}
+
${label}
+
`; + + return `
+ ${stat('Active', s.activeIncidents)} + ${stat('MTTR', s.mttrMinutes + 'm')} + ${stat('24h Alerts', s.alerts24h)} + ${stat('24h Resp', s.responses24h)} +
`; + } + + private renderAlertFeed(): string { + if (this.alerts.length === 0) return '
No alerts
'; + + const rows = this.alerts.slice(0, 20).map((a) => { + const sevColor = + a.severity === 'critical' ? '#ef4444' : + a.severity === 'high' ? '#f97316' : + a.severity === 'medium' ? '#eab308' : + '#3b82f6'; + const ts = this.timeAgo(a.timestamp); + + return `
+ ${a.severity.toUpperCase()} +
+
+ ${escapeHtml(a.mitreId)} โ€” ${escapeHtml(a.mitreName)} + ${ts} +
+
${escapeHtml(a.description)}
+
${escapeHtml(a.locationName)} ยท ${escapeHtml(a.infrastructure)} ยท ${a.confidence}%
+
+
`; + }).join(''); + + return `
+
Alert Feed
+ ${rows} +
`; + } + + private renderCorrelation(): string { + const insights = [ + 'Phishing campaign (T1566) targeting Jakarta NOC operators correlates with regional APT activity detected by WorldMonitor threat intel layer.', + 'Elevated brute-force attempts on Surabaya edge gateways coincide with increased nation-state cyber activity in Southeast Asia.', + 'GATRA CRA automated 12 containment actions in 24h โ€” MTTR improved 35% vs. manual SOC baseline.', + ]; + + const rows = insights + .map( + (text) => + `
+ ${escapeHtml(text)} +
` + ) + .join(''); + + return `
+
Correlation Insights
+ ${rows} +
`; + } + + private timeAgo(date: Date): string { + const ms = Date.now() - date.getTime(); + if (ms < 60_000) return 'just now'; + if (ms < 3_600_000) return `${Math.floor(ms / 60_000)}m ago`; + if (ms < 86_400_000) return `${Math.floor(ms / 3_600_000)}h ago`; + return `${Math.floor(ms / 86_400_000)}d ago`; + } +} diff --git a/src/components/MapContainer.ts b/src/components/MapContainer.ts index d488da35d..62d6ab095 100644 --- a/src/components/MapContainer.ts +++ b/src/components/MapContainer.ts @@ -25,6 +25,7 @@ import type { UcdpGeoEvent, CyberThreat, CableHealthRecord, + GatraAlert, } from '@/types'; import type { AirportDelayAlert } from '@/services/aviation'; import type { DisplacementFlow } from '@/services/displacement'; @@ -325,6 +326,12 @@ export class MapContainer { } } + public setGatraAlerts(alerts: GatraAlert[]): void { + if (this.useDeckGL) { + this.deckGLMap?.setGatraAlerts(alerts); + } + } + public setNewsLocations(data: Array<{ lat: number; lon: number; title: string; threatLevel: string; timestamp?: Date }>): void { if (this.useDeckGL) { this.deckGLMap?.setNewsLocations(data); diff --git a/src/config/panels.ts b/src/config/panels.ts index 32f43fa0b..cc4068b7d 100644 --- a/src/config/panels.ts +++ b/src/config/panels.ts @@ -86,6 +86,7 @@ const FULL_MAP_LAYERS: MapLayers = { centralBanks: false, commodityHubs: false, gulfInvestments: false, + gatraAlerts: false, }; const FULL_MOBILE_MAP_LAYERS: MapLayers = { @@ -127,6 +128,7 @@ const FULL_MOBILE_MAP_LAYERS: MapLayers = { centralBanks: false, commodityHubs: false, gulfInvestments: false, + gatraAlerts: false, }; // ============================================ @@ -208,6 +210,7 @@ const TECH_MAP_LAYERS: MapLayers = { centralBanks: false, commodityHubs: false, gulfInvestments: false, + gatraAlerts: false, }; const TECH_MOBILE_MAP_LAYERS: MapLayers = { @@ -249,6 +252,7 @@ const TECH_MOBILE_MAP_LAYERS: MapLayers = { centralBanks: false, commodityHubs: false, gulfInvestments: false, + gatraAlerts: false, }; // ============================================ @@ -325,6 +329,7 @@ const FINANCE_MAP_LAYERS: MapLayers = { centralBanks: true, commodityHubs: false, gulfInvestments: false, + gatraAlerts: false, }; const FINANCE_MOBILE_MAP_LAYERS: MapLayers = { @@ -366,14 +371,121 @@ const FINANCE_MOBILE_MAP_LAYERS: MapLayers = { centralBanks: true, commodityHubs: false, gulfInvestments: false, + gatraAlerts: false, +}; + +// ============================================ +// CYBER VARIANT (Cybersecurity/GATRA) +// ============================================ +const CYBER_PANELS: Record = { + map: { name: 'Global Cyber Map', enabled: true, priority: 1 }, + 'live-news': { name: 'Cyber Headlines', enabled: true, priority: 1 }, + insights: { name: 'AI Insights', enabled: true, priority: 1 }, + security: { name: 'Cybersecurity News', enabled: true, priority: 1 }, + indonesia: { name: 'Indonesia Cyber (BSSN)', enabled: true, priority: 1 }, + threats: { name: 'Threat Intelligence', enabled: true, priority: 1 }, + malware: { name: 'Ransomware & Malware', enabled: true, priority: 1 }, + infrastructure: { name: 'Infrastructure Security', enabled: true, priority: 1 }, + geoCyber: { name: 'Nation-State Cyber', enabled: true, priority: 1 }, + research: { name: 'Security Research', enabled: true, priority: 1 }, + policy: { name: 'Cyber Policy', enabled: true, priority: 2 }, + aiSecurity: { name: 'AI & Security', enabled: true, priority: 2 }, + 'gatra-soc': { name: 'GATRA SOC', enabled: true, priority: 1 }, + monitors: { name: 'My Monitors', enabled: true, priority: 2 }, +}; + +const CYBER_MAP_LAYERS: MapLayers = { + conflicts: true, + bases: false, + cables: true, + pipelines: false, + hotspots: false, + ais: false, + nuclear: false, + irradiators: false, + sanctions: false, + weather: false, + economic: false, + waterways: false, + outages: true, + cyberThreats: true, + datacenters: true, + protests: false, + flights: false, + military: true, + natural: true, + spaceports: false, + minerals: false, + fires: true, + // Data source layers + ucdpEvents: false, + displacement: false, + climate: false, + // Tech layers (disabled in cyber variant) + startupHubs: false, + cloudRegions: false, + accelerators: false, + techHQs: false, + techEvents: false, + // Finance layers (disabled in cyber variant) + stockExchanges: false, + financialCenters: false, + centralBanks: false, + commodityHubs: false, + gulfInvestments: false, + // GATRA SOC layer (enabled in cyber variant) + gatraAlerts: true, +}; + +const CYBER_MOBILE_MAP_LAYERS: MapLayers = { + conflicts: true, + bases: false, + cables: true, + pipelines: false, + hotspots: false, + ais: false, + nuclear: false, + irradiators: false, + sanctions: false, + weather: false, + economic: false, + waterways: false, + outages: true, + cyberThreats: true, + datacenters: false, + protests: false, + flights: false, + military: false, + natural: true, + spaceports: false, + minerals: false, + fires: true, + // Data source layers + ucdpEvents: false, + displacement: false, + climate: false, + // Tech layers + startupHubs: false, + cloudRegions: false, + accelerators: false, + techHQs: false, + techEvents: false, + // Finance layers + stockExchanges: false, + financialCenters: false, + centralBanks: false, + commodityHubs: false, + gulfInvestments: false, + // GATRA SOC layer (enabled in cyber variant) + gatraAlerts: true, }; // ============================================ // VARIANT-AWARE EXPORTS // ============================================ -export const DEFAULT_PANELS = SITE_VARIANT === 'tech' ? TECH_PANELS : SITE_VARIANT === 'finance' ? FINANCE_PANELS : FULL_PANELS; -export const DEFAULT_MAP_LAYERS = SITE_VARIANT === 'tech' ? TECH_MAP_LAYERS : SITE_VARIANT === 'finance' ? FINANCE_MAP_LAYERS : FULL_MAP_LAYERS; -export const MOBILE_DEFAULT_MAP_LAYERS = SITE_VARIANT === 'tech' ? TECH_MOBILE_MAP_LAYERS : SITE_VARIANT === 'finance' ? FINANCE_MOBILE_MAP_LAYERS : FULL_MOBILE_MAP_LAYERS; +export const DEFAULT_PANELS = SITE_VARIANT === 'tech' ? TECH_PANELS : SITE_VARIANT === 'finance' ? FINANCE_PANELS : SITE_VARIANT === 'cyber' ? CYBER_PANELS : FULL_PANELS; +export const DEFAULT_MAP_LAYERS = SITE_VARIANT === 'tech' ? TECH_MAP_LAYERS : SITE_VARIANT === 'finance' ? FINANCE_MAP_LAYERS : SITE_VARIANT === 'cyber' ? CYBER_MAP_LAYERS : FULL_MAP_LAYERS; +export const MOBILE_DEFAULT_MAP_LAYERS = SITE_VARIANT === 'tech' ? TECH_MOBILE_MAP_LAYERS : SITE_VARIANT === 'finance' ? FINANCE_MOBILE_MAP_LAYERS : SITE_VARIANT === 'cyber' ? CYBER_MOBILE_MAP_LAYERS : FULL_MOBILE_MAP_LAYERS; /** Maps map-layer toggle keys to their data-freshness source IDs (single source of truth). */ export const LAYER_TO_SOURCE: Partial> = { @@ -387,6 +499,7 @@ export const LAYER_TO_SOURCE: Partial> = ucdpEvents: ['ucdp_events'], displacement: ['unhcr'], climate: ['climate'], + gatraAlerts: ['gatra'], }; // Monitor palette โ€” fixed category colors persisted to localStorage (not theme-dependent) diff --git a/src/config/variants/cyber.ts b/src/config/variants/cyber.ts new file mode 100644 index 000000000..2324879a4 --- /dev/null +++ b/src/config/variants/cyber.ts @@ -0,0 +1,205 @@ +// Cyber/GATRA variant - cybersecurity intelligence dashboard +// +// Includes the GATRA integration layer: +// - GatraSOCDashboardPanel (src/panels/gatra-soc-panel.ts) +// - GATRA alerts map layer (src/layers/gatra-alerts-layer.ts) +// - GATRA connector service (src/gatra/connector.ts) +import type { PanelConfig, MapLayers } from '@/types'; +import type { VariantConfig } from './base'; + +// Re-export base config +export * from './base'; + +// Cyber-focused feeds configuration +import type { Feed } from '@/types'; + +const rss = (url: string) => `/api/rss-proxy?url=${encodeURIComponent(url)}`; + +export const FEEDS: Record = { + // Core Cybersecurity News + security: [ + { name: 'Krebs on Security', url: rss('https://krebsonsecurity.com/feed/') }, + { name: 'Bleeping Computer', url: rss('https://www.bleepingcomputer.com/feed/') }, + { name: 'The Hacker News', url: rss('https://feeds.feedburner.com/TheHackersNews') }, + { name: 'Dark Reading', url: rss('https://www.darkreading.com/rss.xml') }, + { name: 'Schneier on Security', url: rss('https://www.schneier.com/feed/') }, + { name: 'SecurityWeek', url: rss('https://www.securityweek.com/feed/') }, + { name: 'The Record', url: rss('https://therecord.media/feed') }, + { name: 'CSO Online', url: rss('https://www.csoonline.com/feed/') }, + ], + + // Indonesian Cyber Sources + indonesia: [ + { name: 'BSSN News', url: rss('https://www.bssn.go.id/feed/') }, + { name: 'Indonesia Cyber', url: rss('https://news.google.com/rss/search?q=(BSSN+OR+"Badan+Siber"+OR+Indonesia+cybersecurity+OR+Indonesia+cyber+attack)+when:7d&hl=en-US&gl=US&ceid=US:en') }, + { name: 'APJII News', url: rss('https://news.google.com/rss/search?q=APJII+OR+"internet+Indonesia"+OR+"digital+Indonesia"+when:7d&hl=en-US&gl=US&ceid=US:en') }, + ], + + // Threat Intelligence + threats: [ + { name: 'CISA Advisories', url: rss('https://www.cisa.gov/cybersecurity-advisories/all.xml') }, + { name: 'US-CERT Alerts', url: rss('https://www.cisa.gov/uscert/ncas/alerts.xml') }, + { name: 'NIST CVE', url: rss('https://news.google.com/rss/search?q=(CVE+OR+"zero+day"+OR+"critical+vulnerability")+when:3d&hl=en-US&gl=US&ceid=US:en') }, + { name: 'Exploit DB', url: rss('https://news.google.com/rss/search?q=("exploit"+OR+"proof+of+concept"+OR+"CVE")+cybersecurity+when:3d&hl=en-US&gl=US&ceid=US:en') }, + { name: 'Cyber Incidents', url: rss('https://news.google.com/rss/search?q=(cyber+attack+OR+data+breach+OR+ransomware+OR+hacking)+when:3d&hl=en-US&gl=US&ceid=US:en') }, + ], + + // Ransomware & Malware + malware: [ + { name: 'Ransomware News', url: rss('https://news.google.com/rss/search?q=ransomware+attack+OR+ransomware+gang+when:3d&hl=en-US&gl=US&ceid=US:en') }, + { name: 'Malware Analysis', url: rss('https://news.google.com/rss/search?q=malware+analysis+OR+malware+campaign+when:7d&hl=en-US&gl=US&ceid=US:en') }, + { name: 'APT Groups', url: rss('https://news.google.com/rss/search?q=(APT+OR+"advanced+persistent+threat"+OR+"state+sponsored")+cyber+when:7d&hl=en-US&gl=US&ceid=US:en') }, + ], + + // Infrastructure & ICS/OT Security + infrastructure: [ + { name: 'ICS Security', url: rss('https://news.google.com/rss/search?q=(ICS+OR+SCADA+OR+"operational+technology"+OR+OT)+cybersecurity+when:7d&hl=en-US&gl=US&ceid=US:en') }, + { name: 'Critical Infrastructure', url: rss('https://news.google.com/rss/search?q="critical+infrastructure"+cyber+OR+attack+when:7d&hl=en-US&gl=US&ceid=US:en') }, + { name: 'Submarine Cable News', url: rss('https://news.google.com/rss/search?q="submarine+cable"+OR+"undersea+cable"+sabotage+OR+damage+OR+security+when:7d&hl=en-US&gl=US&ceid=US:en') }, + ], + + // Nation-State & Geopolitical Cyber + geoCyber: [ + { name: 'Cyber Warfare', url: rss('https://news.google.com/rss/search?q="cyber+warfare"+OR+"cyber+espionage"+OR+"nation+state"+hacking+when:7d&hl=en-US&gl=US&ceid=US:en') }, + { name: 'China Cyber', url: rss('https://news.google.com/rss/search?q=China+cyber+espionage+OR+China+hacking+when:7d&hl=en-US&gl=US&ceid=US:en') }, + { name: 'Russia Cyber', url: rss('https://news.google.com/rss/search?q=Russia+cyber+attack+OR+Russia+hacking+when:7d&hl=en-US&gl=US&ceid=US:en') }, + { name: 'North Korea Cyber', url: rss('https://news.google.com/rss/search?q="North+Korea"+cyber+OR+Lazarus+Group+when:14d&hl=en-US&gl=US&ceid=US:en') }, + ], + + // Security Research & Vendor Blogs + research: [ + { name: 'Google Project Zero', url: rss('https://googleprojectzero.blogspot.com/feeds/posts/default?alt=rss') }, + { name: 'Microsoft Security', url: rss('https://www.microsoft.com/en-us/security/blog/feed/') }, + { name: 'Google TAG', url: rss('https://blog.google/threat-analysis-group/rss/') }, + { name: 'Mandiant Blog', url: rss('https://www.mandiant.com/resources/blog/rss.xml') }, + { name: 'CrowdStrike Blog', url: rss('https://www.crowdstrike.com/blog/feed/') }, + { name: 'Palo Alto Unit 42', url: rss('https://unit42.paloaltonetworks.com/feed/') }, + ], + + // Policy & Regulation + policy: [ + { name: 'Cyber Policy', url: rss('https://news.google.com/rss/search?q=cybersecurity+regulation+OR+cybersecurity+policy+OR+cybersecurity+law+when:7d&hl=en-US&gl=US&ceid=US:en') }, + { name: 'NIST Framework', url: rss('https://news.google.com/rss/search?q=NIST+cybersecurity+framework+when:14d&hl=en-US&gl=US&ceid=US:en') }, + { name: 'EU Cyber', url: rss('https://news.google.com/rss/search?q=(NIS2+OR+ENISA+OR+"EU+cybersecurity")+when:14d&hl=en-US&gl=US&ceid=US:en') }, + ], + + // AI & Security + aiSecurity: [ + { name: 'AI Security', url: rss('https://news.google.com/rss/search?q=("AI+security"+OR+"machine+learning"+cybersecurity+OR+"adversarial+AI")+when:7d&hl=en-US&gl=US&ceid=US:en') }, + { name: 'AI for SOC', url: rss('https://news.google.com/rss/search?q=("AI+SOC"+OR+"AI+threat+detection"+OR+"automated+security")+when:7d&hl=en-US&gl=US&ceid=US:en') }, + ], +}; + +// Panel configuration for cyber/GATRA dashboard +export const DEFAULT_PANELS: Record = { + map: { name: 'Global Cyber Map', enabled: true, priority: 1 }, + 'live-news': { name: 'Cyber Headlines', enabled: true, priority: 1 }, + insights: { name: 'AI Insights', enabled: true, priority: 1 }, + 'gatra-soc': { name: 'GATRA SOC', enabled: true, priority: 1 }, + security: { name: 'Cybersecurity News', enabled: true, priority: 1 }, + indonesia: { name: 'Indonesia Cyber (BSSN)', enabled: true, priority: 1 }, + threats: { name: 'Threat Intelligence', enabled: true, priority: 1 }, + malware: { name: 'Ransomware & Malware', enabled: true, priority: 1 }, + infrastructure: { name: 'Infrastructure Security', enabled: true, priority: 1 }, + geoCyber: { name: 'Nation-State Cyber', enabled: true, priority: 1 }, + research: { name: 'Security Research', enabled: true, priority: 1 }, + policy: { name: 'Cyber Policy', enabled: true, priority: 2 }, + aiSecurity: { name: 'AI & Security', enabled: true, priority: 2 }, + monitors: { name: 'My Monitors', enabled: true, priority: 2 }, +}; + +// Cyber-focused map layers +export const DEFAULT_MAP_LAYERS: MapLayers = { + conflicts: true, + bases: false, + cables: true, + pipelines: false, + hotspots: false, + ais: false, + nuclear: false, + irradiators: false, + sanctions: false, + weather: false, + economic: false, + waterways: false, + outages: true, + cyberThreats: true, + datacenters: true, + protests: false, + flights: false, + military: true, + natural: true, + spaceports: false, + minerals: false, + fires: true, + // Data source layers + ucdpEvents: false, + displacement: false, + climate: false, + // Tech layers + startupHubs: false, + cloudRegions: false, + accelerators: false, + techHQs: false, + techEvents: false, + // Finance layers + stockExchanges: false, + financialCenters: false, + centralBanks: false, + commodityHubs: false, + gulfInvestments: false, + // GATRA SOC layer โ€” enabled by default in cyber variant + gatraAlerts: true, +}; + +// Mobile defaults for cyber variant +export const MOBILE_DEFAULT_MAP_LAYERS: MapLayers = { + conflicts: true, + bases: false, + cables: true, + pipelines: false, + hotspots: false, + ais: false, + nuclear: false, + irradiators: false, + sanctions: false, + weather: false, + economic: false, + waterways: false, + outages: true, + cyberThreats: true, + datacenters: false, + protests: false, + flights: false, + military: false, + natural: true, + spaceports: false, + minerals: false, + fires: true, + // Data source layers + ucdpEvents: false, + displacement: false, + climate: false, + // Tech layers + startupHubs: false, + cloudRegions: false, + accelerators: false, + techHQs: false, + techEvents: false, + // Finance layers + stockExchanges: false, + financialCenters: false, + centralBanks: false, + commodityHubs: false, + gulfInvestments: false, + // GATRA SOC layer โ€” enabled on mobile too + gatraAlerts: true, +}; + +export const VARIANT_CONFIG: VariantConfig = { + name: 'cyber', + description: 'Cybersecurity & GATRA threat intelligence dashboard', + panels: DEFAULT_PANELS, + mapLayers: DEFAULT_MAP_LAYERS, + mobileMapLayers: MOBILE_DEFAULT_MAP_LAYERS, +}; diff --git a/src/config/variants/finance.ts b/src/config/variants/finance.ts index ccb2db20c..9c9857c38 100644 --- a/src/config/variants/finance.ts +++ b/src/config/variants/finance.ts @@ -209,6 +209,7 @@ export const DEFAULT_MAP_LAYERS: MapLayers = { centralBanks: true, commodityHubs: false, gulfInvestments: false, + gatraAlerts: false, }; // Mobile defaults for finance variant @@ -250,6 +251,7 @@ export const MOBILE_DEFAULT_MAP_LAYERS: MapLayers = { centralBanks: true, commodityHubs: false, gulfInvestments: false, + gatraAlerts: false, }; export const VARIANT_CONFIG: VariantConfig = { diff --git a/src/config/variants/full.ts b/src/config/variants/full.ts index 02bc89e02..4f4c31655 100644 --- a/src/config/variants/full.ts +++ b/src/config/variants/full.ts @@ -87,6 +87,7 @@ export const DEFAULT_MAP_LAYERS: MapLayers = { centralBanks: false, commodityHubs: false, gulfInvestments: false, + gatraAlerts: false, }; // Mobile-specific defaults for geopolitical @@ -128,6 +129,7 @@ export const MOBILE_DEFAULT_MAP_LAYERS: MapLayers = { centralBanks: false, commodityHubs: false, gulfInvestments: false, + gatraAlerts: false, }; export const VARIANT_CONFIG: VariantConfig = { diff --git a/src/config/variants/tech.ts b/src/config/variants/tech.ts index 0925b0e68..311aff819 100644 --- a/src/config/variants/tech.ts +++ b/src/config/variants/tech.ts @@ -238,6 +238,7 @@ export const DEFAULT_MAP_LAYERS: MapLayers = { centralBanks: false, commodityHubs: false, gulfInvestments: false, + gatraAlerts: false, }; // Mobile defaults for tech variant @@ -279,6 +280,7 @@ export const MOBILE_DEFAULT_MAP_LAYERS: MapLayers = { centralBanks: false, commodityHubs: false, gulfInvestments: false, + gatraAlerts: false, }; export const VARIANT_CONFIG: VariantConfig = { diff --git a/src/e2e/map-harness.ts b/src/e2e/map-harness.ts index 58910989b..4461aded0 100644 --- a/src/e2e/map-harness.ts +++ b/src/e2e/map-harness.ts @@ -171,6 +171,7 @@ const allLayersEnabled: MapLayers = { centralBanks: true, commodityHubs: true, gulfInvestments: true, + gatraAlerts: true, }; const allLayersDisabled: MapLayers = { @@ -209,6 +210,7 @@ const allLayersDisabled: MapLayers = { centralBanks: false, commodityHubs: false, gulfInvestments: false, + gatraAlerts: false, }; const SEEDED_NEWS_LOCATIONS: Array<{ diff --git a/src/e2e/mobile-map-integration-harness.ts b/src/e2e/mobile-map-integration-harness.ts index 4b25351c9..7b24b73ec 100644 --- a/src/e2e/mobile-map-integration-harness.ts +++ b/src/e2e/mobile-map-integration-harness.ts @@ -120,6 +120,7 @@ const layers = { centralBanks: false, commodityHubs: false, gulfInvestments: false, + gatraAlerts: false, }; await initI18n(); diff --git a/src/gatra/connector.ts b/src/gatra/connector.ts new file mode 100644 index 000000000..aff6079fa --- /dev/null +++ b/src/gatra/connector.ts @@ -0,0 +1,127 @@ +/** + * GATRA SOC Connector โ€” unified integration layer + * + * Exposes GATRA's 5-agent pipeline data via the same panel system + * World Monitor uses. Current implementation uses mock data from + * `@/services/gatra`; the mock calls will be replaced by real GATRA + * API feeds via Pub/Sub once the production connector is ready. + * + * Data exposed: + * ADA alerts โ€” anomaly detections with MITRE ATT&CK mapping + * TAA analyses โ€” threat investigation with actor/campaign/kill-chain + * CRA actions โ€” automated containment responses with status + * Agent health โ€” ADA/TAA/CRA/CLA/RVA heartbeat & state + * Correlations โ€” links between World Monitor events and GATRA alerts + */ + +import { + fetchGatraAlerts, + fetchGatraAgentStatus, + fetchGatraIncidentSummary, + fetchGatraCRAActions, + fetchGatraTAAAnalyses, + fetchGatraCorrelations, +} from '@/services/gatra'; + +import type { + GatraAlert, + GatraAgentStatus, + GatraIncidentSummary, + GatraCRAAction, + GatraTAAAnalysis, + GatraCorrelation, + GatraConnectorSnapshot, +} from '@/types'; + +// โ”€โ”€ Connector state โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +let _snapshot: GatraConnectorSnapshot | null = null; +let _refreshing = false; +const _listeners: Set<(snap: GatraConnectorSnapshot) => void> = new Set(); + +// โ”€โ”€ Public API โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +/** + * Fetch all GATRA data sources in parallel and cache the result. + * Returns a unified snapshot that panels, layers, and other consumers + * can read without issuing their own requests. + */ +export async function refreshGatraData(): Promise { + if (_refreshing && _snapshot) return _snapshot; + _refreshing = true; + + try { + const [alerts, agents, summary, craActions] = await Promise.all([ + fetchGatraAlerts(), + fetchGatraAgentStatus(), + fetchGatraIncidentSummary(), + fetchGatraCRAActions(), + ]); + + // TAA and correlations depend on alerts + const [taaAnalyses, correlations] = await Promise.all([ + fetchGatraTAAAnalyses(alerts), + fetchGatraCorrelations(alerts), + ]); + + _snapshot = { + alerts, + agents, + summary, + craActions, + taaAnalyses, + correlations, + lastRefresh: new Date(), + }; + + // Notify subscribers + for (const fn of _listeners) { + try { fn(_snapshot); } catch (e) { console.error('[GatraConnector] listener error:', e); } + } + + return _snapshot; + } catch (err) { + console.error('[GatraConnector] refresh failed:', err); + if (_snapshot) return _snapshot; + throw err; + } finally { + _refreshing = false; + } +} + +/** Return the last cached snapshot (may be null before first refresh). */ +export function getGatraSnapshot(): GatraConnectorSnapshot | null { + return _snapshot; +} + +/** Subscribe to snapshot updates. Returns an unsubscribe function. */ +export function onGatraUpdate(fn: (snap: GatraConnectorSnapshot) => void): () => void { + _listeners.add(fn); + return () => { _listeners.delete(fn); }; +} + +// โ”€โ”€ Convenience accessors โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +export function getAlerts(): GatraAlert[] { + return _snapshot?.alerts ?? []; +} + +export function getAgentStatus(): GatraAgentStatus[] { + return _snapshot?.agents ?? []; +} + +export function getIncidentSummary(): GatraIncidentSummary | null { + return _snapshot?.summary ?? null; +} + +export function getCRAActions(): GatraCRAAction[] { + return _snapshot?.craActions ?? []; +} + +export function getTAAAnalyses(): GatraTAAAnalysis[] { + return _snapshot?.taaAnalyses ?? []; +} + +export function getCorrelations(): GatraCorrelation[] { + return _snapshot?.correlations ?? []; +} diff --git a/src/layers/gatra-alerts-layer.ts b/src/layers/gatra-alerts-layer.ts new file mode 100644 index 000000000..77d8575ff --- /dev/null +++ b/src/layers/gatra-alerts-layer.ts @@ -0,0 +1,123 @@ +/** + * GATRA Alerts Map Layer โ€” deck.gl layer factory + * + * Plots GATRA alert locations on the World Monitor map using deck.gl: + * - Red pulsing markers for critical alerts + * - Orange markers for high severity + * - Yellow for medium, blue for low + * + * Returns an array of Layer instances that DeckGLMap can spread into + * its layer list. The pulse ring layer uses a time-based radius scale + * so critical alerts visually pulse on the map. + */ + +import { ScatterplotLayer, TextLayer } from '@deck.gl/layers'; +import type { Layer } from '@deck.gl/core'; +import type { GatraAlert, GatraAlertSeverity } from '@/types'; + +// โ”€โ”€ Color palette โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +type RGBA = [number, number, number, number]; + +const SEVERITY_FILL: Record = { + critical: [255, 50, 50, 220], + high: [255, 150, 0, 220], + medium: [255, 220, 0, 200], + low: [100, 150, 255, 180], +}; + +const SEVERITY_RADIUS: Record = { + critical: 18000, + high: 14000, + medium: 10000, + low: 8000, +}; + +// โ”€โ”€ Public factory โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +/** + * Build the deck.gl layers for GATRA alerts. + * + * @param alerts Current GATRA alert list + * @param pulseTime Monotonic timestamp used for pulse animation + * (pass `Date.now()` or the shared pulse clock from DeckGLMap) + */ +export function createGatraAlertsLayers( + alerts: GatraAlert[], + pulseTime: number = Date.now(), +): Layer[] { + if (alerts.length === 0) return []; + + const layers: Layer[] = []; + + // 1. Base scatterplot โ€” all alerts + layers.push( + new ScatterplotLayer({ + id: 'gatra-alerts-layer', + data: alerts, + getPosition: (d) => [d.lon, d.lat], + getRadius: (d) => SEVERITY_RADIUS[d.severity], + getFillColor: (d) => SEVERITY_FILL[d.severity], + radiusMinPixels: 4, + radiusMaxPixels: 16, + pickable: true, + stroked: true, + getLineColor: [255, 255, 255, 100] as RGBA, + lineWidthMinPixels: 1, + }), + ); + + // 2. Pulse ring โ€” critical and high alerts only + const pulsable = alerts.filter( + (a) => a.severity === 'critical' || a.severity === 'high', + ); + + if (pulsable.length > 0) { + const pulseScale = 1.0 + 0.9 * (0.5 + 0.5 * Math.sin(pulseTime / 350)); + + layers.push( + new ScatterplotLayer({ + id: 'gatra-alerts-pulse', + data: pulsable, + getPosition: (d) => [d.lon, d.lat], + getRadius: (d) => SEVERITY_RADIUS[d.severity], + radiusScale: pulseScale, + radiusMinPixels: 6, + radiusMaxPixels: 28, + stroked: true, + filled: false, + getLineColor: (d) => + d.severity === 'critical' + ? [255, 50, 50, 120] as RGBA + : [255, 150, 0, 100] as RGBA, + lineWidthMinPixels: 1.5, + pickable: false, + updateTriggers: { radiusScale: pulseTime }, + }), + ); + } + + // 3. Severity badge labels at higher zoom + const critical = alerts.filter((a) => a.severity === 'critical'); + if (critical.length > 0) { + layers.push( + new TextLayer({ + id: 'gatra-alerts-labels', + data: critical, + getText: (d) => d.mitreId, + getPosition: (d) => [d.lon, d.lat], + getColor: [255, 255, 255, 255], + getSize: 11, + getPixelOffset: [0, -16], + background: true, + getBackgroundColor: [220, 38, 38, 200] as RGBA, + backgroundPadding: [4, 2, 4, 2], + pickable: false, + fontFamily: 'system-ui, sans-serif', + fontWeight: 700, + }), + ); + } + + return layers; +} diff --git a/src/panels/gatra-soc-panel.ts b/src/panels/gatra-soc-panel.ts new file mode 100644 index 000000000..6bf59a7f0 --- /dev/null +++ b/src/panels/gatra-soc-panel.ts @@ -0,0 +1,312 @@ +/** + * GatraSOCPanel โ€” enhanced GATRA SOC integration dashboard panel. + * + * Renders: + * 1. Agent status indicators (5 agents with health dots) + * 2. Active incident count and mean time to respond + * 3. Live alert feed with severity coloring + * 4. TAA threat analysis section (actor, campaign, kill chain) + * 5. CRA response actions with status badges + * 6. Correlation section linking World Monitor events to GATRA alerts + * + * Pulls data from the GATRA connector on a 60s refresh cycle. + */ + +import { Panel } from '@/components/Panel'; +import { escapeHtml } from '@/utils/sanitize'; +import { refreshGatraData } from '@/gatra/connector'; +import type { + GatraAlert, + GatraAgentStatus, + GatraIncidentSummary, + GatraCRAAction, + GatraTAAAnalysis, + GatraCorrelation, +} from '@/types'; + +// โ”€โ”€ Severity โ†’ color mapping โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +const SEV_COLORS: Record = { + critical: '#ef4444', + high: '#f97316', + medium: '#eab308', + low: '#3b82f6', +}; + +const AGENT_STATUS_COLORS: Record = { + online: '#22c55e', + processing: '#eab308', + degraded: '#ef4444', +}; + +const ACTION_TYPE_LABELS: Record = { + ip_blocked: 'IP Blocked', + endpoint_isolated: 'Endpoint Isolated', + credential_rotated: 'Credential Rotated', + playbook_triggered: 'Playbook', + rule_pushed: 'Rule Pushed', + rate_limited: 'Rate Limited', +}; + +const KILL_CHAIN_LABELS: Record = { + reconnaissance: 'Recon', + weaponization: 'Weapon', + delivery: 'Delivery', + exploitation: 'Exploit', + installation: 'Install', + c2: 'C2', + actions: 'Actions', +}; + +// โ”€โ”€ Panel class โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +export class GatraSOCDashboardPanel extends Panel { + private alerts: GatraAlert[] = []; + private agentStatus: GatraAgentStatus[] = []; + private summary: GatraIncidentSummary | null = null; + private craActions: GatraCRAAction[] = []; + private taaAnalyses: GatraTAAAnalysis[] = []; + private correlations: GatraCorrelation[] = []; + private loading = false; + + constructor() { + super({ + id: 'gatra-soc', + title: 'GATRA SOC', + showCount: true, + trackActivity: true, + infoTooltip: 'GATRA AI-Driven SOC โ€” 5-agent pipeline monitoring IOH infrastructure. Data refreshes every 60 s.', + }); + } + + /** Called by App on a 60 s interval. */ + public async refresh(): Promise { + if (this.loading) return; + this.loading = true; + + try { + const snap = await refreshGatraData(); + + this.alerts = snap.alerts; + this.agentStatus = snap.agents; + this.summary = snap.summary; + this.craActions = snap.craActions; + this.taaAnalyses = snap.taaAnalyses; + this.correlations = snap.correlations; + + this.setCount(snap.alerts.length); + this.setDataBadge('live', `${snap.alerts.length} alerts`); + this.render(); + } catch (err) { + console.error('[GatraSOCDashboardPanel] refresh error:', err); + this.showError('Failed to load GATRA data'); + } finally { + this.loading = false; + } + } + + /** Expose alerts for the map layer. */ + public getAlerts(): GatraAlert[] { + return this.alerts; + } + + // โ”€โ”€ Rendering โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + private render(): void { + const html = [ + this.renderAgentStatusBar(), + this.renderStatsRow(), + this.renderAlertFeed(), + this.renderTAASection(), + this.renderCRASection(), + this.renderCorrelation(), + ].join(''); + + this.setContent(html); + } + + // โ”€โ”€ Agent status bar โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + private renderAgentStatusBar(): string { + if (this.agentStatus.length === 0) return ''; + + const dots = this.agentStatus + .map((a) => { + const color = AGENT_STATUS_COLORS[a.status] || '#6b7280'; + const title = escapeHtml(`${a.fullName} โ€” ${a.status} (${this.timeAgo(a.lastHeartbeat)})`); + return ` + + ${escapeHtml(a.name)} + `; + }) + .join(''); + + return `
+ Agents + ${dots} +
+ `; + } + + // โ”€โ”€ Stats row โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + private renderStatsRow(): string { + if (!this.summary) return ''; + const s = this.summary; + const stat = (label: string, value: string | number, color?: string) => + `
+
${value}
+
${label}
+
`; + + const mttrColor = s.mttrMinutes <= 15 ? '#22c55e' : s.mttrMinutes <= 30 ? '#eab308' : '#ef4444'; + + return `
+ ${stat('Active', s.activeIncidents, s.activeIncidents > 5 ? '#ef4444' : undefined)} + ${stat('MTTR', s.mttrMinutes + 'm', mttrColor)} + ${stat('24h Alerts', s.alerts24h)} + ${stat('24h Resp', s.responses24h)} +
`; + } + + // โ”€โ”€ Alert feed โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + private renderAlertFeed(): string { + if (this.alerts.length === 0) return '
No alerts
'; + + const rows = this.alerts.slice(0, 20).map((a) => { + const sevColor = SEV_COLORS[a.severity] || '#6b7280'; + const ts = this.timeAgo(a.timestamp); + + return `
+ ${a.severity.toUpperCase()} +
+
+ ${escapeHtml(a.mitreId)} โ€” ${escapeHtml(a.mitreName)} + ${ts} +
+
${escapeHtml(a.description)}
+
${escapeHtml(a.locationName)} ยท ${escapeHtml(a.infrastructure)} ยท ${a.confidence}% ยท ${escapeHtml(a.agent)}
+
+
`; + }).join(''); + + return `
+
Alert Feed
+ ${rows} +
`; + } + + // โ”€โ”€ TAA Analysis section โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + private renderTAASection(): string { + if (this.taaAnalyses.length === 0) return ''; + + const rows = this.taaAnalyses.slice(0, 6).map((t) => { + const phaseLabel = KILL_CHAIN_LABELS[t.killChainPhase] || t.killChainPhase; + const phaseColor = this.killChainColor(t.killChainPhase); + + return `
+
+ ${escapeHtml(t.actorAttribution)} + ${phaseLabel} +
+
${escapeHtml(t.campaign)} ยท ${t.confidence}% confidence
+
IOCs: ${t.iocs.map(i => escapeHtml(i)).join(', ')}
+
`; + }).join(''); + + return `
+
TAA Threat Analysis
+ ${rows} +
`; + } + + // โ”€โ”€ CRA Response section โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + private renderCRASection(): string { + if (this.craActions.length === 0) return ''; + + const rows = this.craActions.slice(0, 8).map((c) => { + const statusColor = c.success ? '#22c55e' : '#ef4444'; + const statusLabel = c.success ? 'OK' : 'FAIL'; + const typeLabel = ACTION_TYPE_LABELS[c.actionType] || c.actionType; + + return `
+ ${statusLabel} + ${escapeHtml(typeLabel)} +
${escapeHtml(c.action)}
+ ${this.timeAgo(c.timestamp)} +
`; + }).join(''); + + return `
+
CRA Response Actions
+ ${rows} +
`; + } + + // โ”€โ”€ Correlation insights โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + private renderCorrelation(): string { + if (this.correlations.length === 0) { + // Fallback static insights + const staticInsights = [ + 'GATRA CRA automated containment actions โ€” MTTR improved 35% vs. manual SOC baseline.', + ]; + const rows = staticInsights.map(text => + `
+ ${escapeHtml(text)} +
` + ).join(''); + + return `
+
Correlation Insights
+ ${rows} +
`; + } + + const rows = this.correlations.map((c) => { + const sevColor = SEV_COLORS[c.severity] || '#6b7280'; + const typeIcon = c.worldMonitorEventType === 'cii_spike' ? '📈' + : c.worldMonitorEventType === 'apt_activity' ? '🕵' + : c.worldMonitorEventType === 'geopolitical' ? '🌎' + : '🔒'; + + return `
+
+ ${typeIcon} + ${escapeHtml(c.region)} +
+
${escapeHtml(c.summary)}
+
`; + }).join(''); + + return `
+
Correlation Insights
+ ${rows} +
`; + } + + // โ”€โ”€ Helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + private timeAgo(date: Date): string { + const ms = Date.now() - date.getTime(); + if (ms < 60_000) return 'just now'; + if (ms < 3_600_000) return `${Math.floor(ms / 60_000)}m ago`; + if (ms < 86_400_000) return `${Math.floor(ms / 3_600_000)}h ago`; + return `${Math.floor(ms / 86_400_000)}d ago`; + } + + private killChainColor(phase: string): string { + const colors: Record = { + reconnaissance: '#6366f1', + weaponization: '#8b5cf6', + delivery: '#a855f7', + exploitation: '#d946ef', + installation: '#ec4899', + c2: '#f43f5e', + actions: '#ef4444', + }; + return colors[phase] || '#6b7280'; + } +} diff --git a/src/services/gatra.ts b/src/services/gatra.ts new file mode 100644 index 000000000..b42b8b519 --- /dev/null +++ b/src/services/gatra.ts @@ -0,0 +1,290 @@ +/** + * GATRA SOC Mock Data Connector + * + * Provides mock data simulating GATRA's 5-agent pipeline: + * ADA - Anomaly Detection Agent + * TAA - Triage & Analysis Agent + * CRA - Containment & Response Agent + * CLA - Continuous Learning Agent + * RVA - Reporting & Visualization Agent + * + * Mock data uses realistic Indonesian locations and IOH infrastructure refs. + * Data is stable within 5-minute time buckets and regenerated with slight + * randomization per call. Will be replaced by real GATRA API feeds via Pub/Sub. + */ + +import type { + GatraAlert, + GatraAgentStatus, + GatraIncidentSummary, + GatraCRAAction, + GatraAlertSeverity, + GatraAgentName, + GatraAgentStatusType, + GatraTAAAnalysis, + GatraCorrelation, + KillChainPhase, +} from '@/types'; + +// โ”€โ”€ Deterministic seed from 5-min time buckets โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +function timeBucketSeed(): number { + return Math.floor(Date.now() / (5 * 60 * 1000)); +} + +/** Simple seeded PRNG (mulberry32). */ +function mulberry32(seed: number): () => number { + return () => { + seed |= 0; + seed = (seed + 0x6d2b79f5) | 0; + let t = Math.imul(seed ^ (seed >>> 15), 1 | seed); + t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; + return ((t ^ (t >>> 14)) >>> 0) / 4294967296; + }; +} + +// โ”€โ”€ Reference data โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +interface Location { + name: string; + lat: number; + lon: number; +} + +const LOCATIONS: Location[] = [ + { name: 'Jakarta', lat: -6.2088, lon: 106.8456 }, + { name: 'Surabaya', lat: -7.2575, lon: 112.7521 }, + { name: 'Bandung', lat: -6.9175, lon: 107.6191 }, + { name: 'Medan', lat: 3.5952, lon: 98.6722 }, + { name: 'Makassar', lat: -5.1477, lon: 119.4327 }, +]; + +const MITRE_TECHNIQUES: Array<{ id: string; name: string }> = [ + { id: 'T1566', name: 'Phishing' }, + { id: 'T1190', name: 'Exploit Public-Facing Application' }, + { id: 'T1078', name: 'Valid Accounts' }, + { id: 'T1021', name: 'Remote Services' }, + { id: 'T1059', name: 'Command and Scripting Interpreter' }, + { id: 'T1486', name: 'Data Encrypted for Impact' }, +]; + +const IOH_INFRA: string[] = [ + 'IOH-CORE-JKT-01', + 'IOH-EDGE-SBY-03', + 'IOH-GW-BDG-02', + 'IOH-DNS-MDN-01', + 'IOH-CDN-MKS-04', + 'IOH-MPLS-JKT-02', + 'IOH-RADIUS-SBY-01', + 'IOH-FW-JKT-05', + 'IOH-LB-BDG-03', + 'IOH-VPN-MDN-02', +]; + +const ALERT_DESCRIPTIONS: string[] = [ + 'Suspicious inbound connection from known C2 infrastructure', + 'Brute-force attempt on edge authentication gateway', + 'Anomalous lateral movement detected in core network segment', + 'Credential stuffing against customer portal', + 'Encrypted payload upload to staging server', + 'DNS tunneling activity on recursive resolver', + 'Unauthorized privilege escalation on RADIUS server', + 'Port scan targeting management VLAN', + 'Malicious PowerShell execution via remote service', + 'Data exfiltration attempt over HTTPS to external IP', + 'Spear-phishing campaign targeting NOC operators', + 'Abnormal API call volume on load balancer endpoint', + 'Ransomware pre-cursor activity on file server', + 'VPN session hijack attempt detected', + 'Rogue DHCP server detected on edge segment', +]; + +const CRA_ACTIONS: Array<{ text: string; type: GatraCRAAction['actionType'] }> = [ + { text: 'Blocked IP 45.33.xx.xx at perimeter firewall', type: 'ip_blocked' }, + { text: 'Isolated host IOH-WS-042 from network', type: 'endpoint_isolated' }, + { text: 'Revoked compromised service account creds', type: 'credential_rotated' }, + { text: 'Enabled enhanced logging on MPLS segment', type: 'playbook_triggered' }, + { text: 'Triggered SOAR playbook: credential-reset', type: 'playbook_triggered' }, + { text: 'Quarantined malicious attachment in sandbox', type: 'endpoint_isolated' }, + { text: 'Rate-limited API endpoint /auth/token', type: 'rate_limited' }, + { text: 'Pushed emergency WAF rule for CVE-2024-3094', type: 'rule_pushed' }, +]; + +const THREAT_ACTORS: string[] = [ + 'APT-41 (Winnti)', + 'Lazarus Group', + 'Mustang Panda', + 'OceanLotus (APT-32)', + 'Naikon APT', + 'SideWinder', + 'Turla Group', + 'Unknown / Unattributed', +]; + +const CAMPAIGNS: string[] = [ + 'Operation ShadowNet', + 'Campaign CobaltStrike-SEA', + 'Project DarkTide', + 'Operation MalayBridge', + 'Campaign TelekomTarget', + 'Operation PacificRim', + 'Campaign IndonesiaHarvest', + 'Opportunistic Scanning', +]; + +const KILL_CHAIN_PHASES: KillChainPhase[] = [ + 'reconnaissance', 'weaponization', 'delivery', + 'exploitation', 'installation', 'c2', 'actions', +]; + +const AGENTS: GatraAgentName[] = ['ADA', 'TAA', 'CRA', 'CLA', 'RVA']; + +// โ”€โ”€ Public API โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +export async function fetchGatraAlerts(): Promise { + const rng = mulberry32(timeBucketSeed()); + const count = 15 + Math.floor(rng() * 11); // 15-25 + const now = Date.now(); + const alerts: GatraAlert[] = []; + + for (let i = 0; i < count; i++) { + const loc = LOCATIONS[Math.floor(rng() * LOCATIONS.length)]!; + const technique = MITRE_TECHNIQUES[Math.floor(rng() * MITRE_TECHNIQUES.length)]!; + const sevIdx = rng(); + const severity: GatraAlertSeverity = + sevIdx < 0.15 ? 'critical' : sevIdx < 0.40 ? 'high' : sevIdx < 0.75 ? 'medium' : 'low'; + const confidence = Math.round((0.55 + rng() * 0.44) * 100); // 55-99% + + alerts.push({ + id: `gatra-${timeBucketSeed()}-${i}`, + severity, + mitreId: technique.id, + mitreName: technique.name, + description: ALERT_DESCRIPTIONS[Math.floor(rng() * ALERT_DESCRIPTIONS.length)] ?? 'Anomalous activity detected', + confidence, + lat: loc.lat + (rng() - 0.5) * 0.1, + lon: loc.lon + (rng() - 0.5) * 0.1, + locationName: loc.name, + infrastructure: IOH_INFRA[Math.floor(rng() * IOH_INFRA.length)] ?? 'IOH-UNKNOWN', + timestamp: new Date(now - Math.floor(rng() * 24 * 60 * 60 * 1000)), + agent: AGENTS[Math.floor(rng() * AGENTS.length)] ?? 'ADA', + }); + } + + // Sort by timestamp desc + alerts.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()); + return alerts; +} + +export async function fetchGatraAgentStatus(): Promise { + const rng = mulberry32(timeBucketSeed() + 1); + const now = new Date(); + + const fullNames: Record = { + ADA: 'Anomaly Detection Agent', + TAA: 'Triage & Analysis Agent', + CRA: 'Containment & Response Agent', + CLA: 'Continuous Learning Agent', + RVA: 'Reporting & Visualization Agent', + }; + + return AGENTS.map((name) => { + const roll = rng(); + const status: GatraAgentStatusType = + roll < 0.7 ? 'online' : roll < 0.9 ? 'processing' : 'degraded'; + return { + name, + fullName: fullNames[name], + status, + lastHeartbeat: new Date(now.getTime() - Math.floor(rng() * 120_000)), + }; + }); +} + +export async function fetchGatraIncidentSummary(): Promise { + const rng = mulberry32(timeBucketSeed() + 2); + return { + activeIncidents: 2 + Math.floor(rng() * 6), + mttrMinutes: 8 + Math.floor(rng() * 25), + alerts24h: 40 + Math.floor(rng() * 80), + responses24h: 12 + Math.floor(rng() * 30), + }; +} + +export async function fetchGatraCRAActions(): Promise { + const rng = mulberry32(timeBucketSeed() + 3); + const now = Date.now(); + const count = 4 + Math.floor(rng() * 5); // 4-8 + const actions: GatraCRAAction[] = []; + + for (let i = 0; i < count; i++) { + const entry = CRA_ACTIONS[Math.floor(rng() * CRA_ACTIONS.length)]!; + actions.push({ + id: `cra-${timeBucketSeed()}-${i}`, + action: entry.text, + actionType: entry.type, + target: IOH_INFRA[Math.floor(rng() * IOH_INFRA.length)] ?? 'IOH-UNKNOWN', + timestamp: new Date(now - Math.floor(rng() * 12 * 60 * 60 * 1000)), + success: rng() > 0.1, + }); + } + + actions.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()); + return actions; +} + +export async function fetchGatraTAAAnalyses(alerts: GatraAlert[]): Promise { + const rng = mulberry32(timeBucketSeed() + 4); + const analysable = alerts.filter(a => a.severity === 'critical' || a.severity === 'high'); + const count = Math.min(analysable.length, 5 + Math.floor(rng() * 4)); + + return analysable.slice(0, count).map((alert, i) => ({ + id: `taa-${timeBucketSeed()}-${i}`, + alertId: alert.id, + actorAttribution: THREAT_ACTORS[Math.floor(rng() * THREAT_ACTORS.length)] ?? 'Unknown', + campaign: CAMPAIGNS[Math.floor(rng() * CAMPAIGNS.length)] ?? 'Unknown Campaign', + killChainPhase: KILL_CHAIN_PHASES[Math.floor(rng() * KILL_CHAIN_PHASES.length)] ?? 'reconnaissance', + confidence: Math.round((0.4 + rng() * 0.55) * 100), + iocs: [ + `${Math.floor(rng() * 255)}.${Math.floor(rng() * 255)}.${Math.floor(rng() * 255)}.${Math.floor(rng() * 255)}`, + `sha256:${Array.from({ length: 8 }, () => Math.floor(rng() * 16).toString(16)).join('')}...`, + ], + timestamp: new Date(alert.timestamp.getTime() + Math.floor(rng() * 300_000)), + })); +} + +export async function fetchGatraCorrelations(alerts: GatraAlert[]): Promise { + const rng = mulberry32(timeBucketSeed() + 5); + const locationAlertMap = new Map(); + for (const a of alerts) { + const list = locationAlertMap.get(a.locationName) || []; + list.push(a); + locationAlertMap.set(a.locationName, list); + } + + const correlations: GatraCorrelation[] = []; + const templates: Array<{ type: GatraCorrelation['worldMonitorEventType']; template: (loc: string, count: number) => string }> = [ + { type: 'cii_spike', template: (loc, count) => `CII spike in ${loc} region correlates with ${count} new anomalies detected by ADA on IOH infrastructure` }, + { type: 'apt_activity', template: (loc, count) => `Elevated APT scanning activity near ${loc} NOC aligns with ${count} GATRA alerts โ€” nation-state campaign suspected` }, + { type: 'geopolitical', template: (loc, count) => `Regional geopolitical tensions around ${loc} preceded ${count} brute-force attempts on edge gateways` }, + { type: 'cyber_threat', template: (loc, count) => `WorldMonitor threat intel layer shows C2 infrastructure overlap with ${count} GATRA detections in ${loc}` }, + ]; + + let idx = 0; + for (const [loc, locAlerts] of locationAlertMap) { + if (rng() < 0.4 || idx >= 4 || locAlerts.length === 0) continue; + const tmpl = templates[Math.floor(rng() * templates.length)]!; + const alertIds = locAlerts.slice(0, 3).map(a => a.id); + correlations.push({ + id: `corr-${timeBucketSeed()}-${idx}`, + gatraAlertIds: alertIds, + worldMonitorEventType: tmpl.type, + region: loc, + summary: tmpl.template(loc, locAlerts.length), + severity: locAlerts[0]!.severity, + timestamp: new Date(), + }); + idx++; + } + + return correlations; +} diff --git a/src/types/index.ts b/src/types/index.ts index 624627bb8..7b9294586 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -519,6 +519,8 @@ export interface MapLayers { commodityHubs: boolean; // Gulf FDI layers gulfInvestments: boolean; + // GATRA SOC layers + gatraAlerts: boolean; } export interface AIDataCenter { @@ -1273,3 +1275,89 @@ export interface MapDatacenterCluster { plannedCount?: number; sampled?: boolean; } + +// ============================================ +// GATRA SOC TYPES +// ============================================ + +export type GatraAlertSeverity = 'critical' | 'high' | 'medium' | 'low'; +export type GatraAgentName = 'ADA' | 'TAA' | 'CRA' | 'CLA' | 'RVA'; +export type GatraAgentStatusType = 'online' | 'processing' | 'degraded'; + +export interface GatraAlert { + id: string; + severity: GatraAlertSeverity; + mitreId: string; + mitreName: string; + description: string; + confidence: number; + lat: number; + lon: number; + locationName: string; + infrastructure: string; + timestamp: Date; + agent: GatraAgentName; +} + +export interface GatraAgentStatus { + name: GatraAgentName; + fullName: string; + status: GatraAgentStatusType; + lastHeartbeat: Date; +} + +export interface GatraIncidentSummary { + activeIncidents: number; + mttrMinutes: number; + alerts24h: number; + responses24h: number; +} + +export interface GatraCRAAction { + id: string; + action: string; + actionType: 'ip_blocked' | 'endpoint_isolated' | 'credential_rotated' | 'playbook_triggered' | 'rule_pushed' | 'rate_limited'; + target: string; + timestamp: Date; + success: boolean; +} + +export type KillChainPhase = + | 'reconnaissance' + | 'weaponization' + | 'delivery' + | 'exploitation' + | 'installation' + | 'c2' + | 'actions'; + +export interface GatraTAAAnalysis { + id: string; + alertId: string; + actorAttribution: string; + campaign: string; + killChainPhase: KillChainPhase; + confidence: number; + iocs: string[]; + timestamp: Date; +} + +export interface GatraCorrelation { + id: string; + gatraAlertIds: string[]; + worldMonitorEventType: 'geopolitical' | 'cyber_threat' | 'cii_spike' | 'apt_activity'; + region: string; + summary: string; + severity: GatraAlertSeverity; + timestamp: Date; +} + +export interface GatraConnectorSnapshot { + alerts: GatraAlert[]; + agents: GatraAgentStatus[]; + summary: GatraIncidentSummary; + craActions: GatraCRAAction[]; + taaAnalyses: GatraTAAAnalysis[]; + correlations: GatraCorrelation[]; + lastRefresh: Date; +}