'use client'; import { useEffect, useState, useRef, useCallback, useMemo } from 'react'; import dynamic from 'next/dynamic'; import { motion, AnimatePresence } from 'framer-motion'; import { Layers, BarChart3, Newspaper, Search, X, Globe, MapPinned, Route, Radar, Satellite, Moon, ExternalLink, AlertTriangle, Activity, Database, Wifi, Play, Network, Crosshair, Bluetooth, Pentagon, Radio , PenLine } from 'lucide-react'; import { type TerrainStatus } from '@/lib/map-terrain'; import { loadCameraCatalog, mergeCameraCatalog } from '@/lib/camera-catalog'; import IntelFeed from '@/components/IntelFeed'; import MarketsPanel from '@/components/MarketsPanel'; import ScmPanel from '@/components/ScmPanel'; import SearchBar from '@/components/SearchBar'; import DirectionsBar, { type RouteResult, type LiveLocation } from '@/components/DirectionsBar'; import NavigationView from '@/components/NavigationView'; import FlightWatchPanel, { type WatchedFlight, type FlightTelemetry, type AircraftDetail, type Airport } from '@/components/FlightWatchPanel'; import type { NavProgress } from '@/lib/navigation'; import type { LiveDetection } from '@/lib/malware-intel'; import ScaleBar from '@/components/ScaleBar'; import ErrorBoundary from '@/components/ErrorBoundary'; import { applySettings, loadSavedSettings } from '@/lib/style-tokens'; import SharePanel from '@/components/SharePanel'; import ViewPresets from '@/components/ViewPresets'; import KeyboardShortcuts from '@/components/KeyboardShortcuts'; import GlobalStatusBar from '@/components/GlobalStatusBar'; import LiveAlerts from '@/components/LiveAlerts'; import WorldRemote from '@/components/WorldRemote'; import ArcGISPanel from '@/components/ArcGISPanel'; const OsirisMap = dynamic(() => import('@/components/OsirisMap'), { ssr: false }); const LayerPanel = dynamic(() => import('@/components/LayerPanel')); const SpaceCam = dynamic(() => import('@/components/SpaceCam'), { ssr: false }); const CameraViewer = dynamic(() => import('@/components/CameraViewer')); const OsintPanel = dynamic(() => import('@/components/OsintPanel')); const DrawingToolbar = dynamic(() => import('@/components/DrawingToolbar'), { ssr: false }); const DrawHud = dynamic(() => import('@/components/DrawHud'), { ssr: false }); // The measurement helpers are pure functions — importing them directly keeps // them out of the lazy chunk, so a finished polygon can be measured whether or // not the toolbar has loaded yet. import { toShape, queryRing, type DrawMode, type DrawnShape, type DrawProgress, type DrawResult } from '@/lib/draw'; import { selectInPolygon } from '@/lib/aoi'; import { diffSweep, appendEvents, type WatchBaseline, type WatchEvent } from '@/lib/watch'; import { STORAGE_KEY, serializeShapes, deserializeShapes, shapesToGeoJSON, downloadFile } from '@/lib/aoi-export'; const TokenPanel = dynamic(() => import('@/components/TokenPanel')); function useIsMobile() { const [isMobile, setIsMobile] = useState(false); useEffect(() => { const check = () => { const w = window.innerWidth; const h = window.innerHeight; // Mobile if narrow, OR landscape phone (short height + moderate width) setIsMobile(w < 768 || (h < 500 && w < 1024)); }; check(); window.addEventListener('resize', check); window.addEventListener('orientationchange', check); return () => { window.removeEventListener('resize', check); window.removeEventListener('orientationchange', check); }; }, []); return isMobile; } const UptimeClock = () => { const [uptime, setUptime] = useState('00:00:00'); const startTime = useRef(0); if (startTime.current === 0) startTime.current = Date.now(); useEffect(() => { const iv = setInterval(() => { const e = Math.floor((Date.now() - startTime.current) / 1000); setUptime(`${String(Math.floor(e/3600)).padStart(2,'0')}:${String(Math.floor((e%3600)/60)).padStart(2,'0')}:${String(e%60).padStart(2,'0')}`); }, 1000); return () => clearInterval(iv); }, []); return UPTIME: {uptime}; }; const ZuluClock = () => { const [time, setTime] = useState(''); useEffect(() => { const iv = setInterval(() => { const now = new Date(); setTime(`ZULU ${String(now.getUTCHours()).padStart(2,'0')}:${String(now.getUTCMinutes()).padStart(2,'0')}:${String(now.getUTCSeconds()).padStart(2,'0')}Z`); }, 1000); return () => clearInterval(iv); }, []); return {time || 'ZULU --:--:--Z'}; }; /** Real entity count — no fake throughput metrics */ const ActiveEntityCount = ({ data }: { data: Record }) => { const count = useMemo(() => { if (!data) return 0; return Object.values(data).reduce((sum, v) => sum + (Array.isArray(v) ? v.length : 0), 0); }, [data]); return {count.toLocaleString()}; }; /** Extracts a watchable YouTube URL from embed/channel URLs */ function getYouTubeWatchUrl(url: string): string { if (url.includes('channel=')) return `https://www.youtube.com/channel/${url.split('channel=')[1].split('&')[0]}/live`; if (url.includes('/embed/')) return `https://www.youtube.com/watch?v=${url.split('/embed/')[1].split('?')[0]}`; return url; } function ViewSegment({ active, onClick, title, icon: Icon, label, layoutId }: { active: boolean; onClick: () => void; title: string; icon: React.ComponentType<{ className?: string }>; label: string; layoutId: string; }) { return ( ); } export default function Dashboard() { const dataRef = useRef({}); const [dataVersion, setDataVersion] = useState(0); const data = dataRef.current; const [backendStatus, setBackendStatus] = useState<'connecting' | 'connected' | 'error'>('connecting'); const [mapView, setMapView] = useState({ zoom: 2.5, latitude: 20 }); const [flyToLocation, setFlyToLocation] = useState<{ lat: number; lng: number; zoom?: number; ts: number } | null>(null); const [globalStats, setGlobalStats] = useState(null); const mouseCoordsRef = useRef<{ lat: number; lng: number } | null>(null); const coordsDisplayRef = useRef(null); const [locationLabel, setLocationLabel] = useState(''); const [regionDossier, setRegionDossier] = useState(null); const [dossierLoading, setDossierLoading] = useState(false); const [showSplash, setShowSplash] = useState(true); const autoLocateCancelled = useRef(false); const [mapRetry, setMapRetry] = useState(0); const [activeCamera, setActiveCamera] = useState(null); const [spaceWeather, setSpaceWeather] = useState(null); const [showLayers, setShowLayers] = useState(true); const [showMarkets, setShowMarkets] = useState(false); const [showAlerts, setShowAlerts] = useState(false); const [showSpaceCam, setShowSpaceCam] = useState(false); const [showScmPanel, setShowScmPanel] = useState(true); const [showIntel, setShowIntel] = useState(false); const [showDrawing, setShowDrawing] = useState(false); const [drawMode, setDrawMode] = useState(null); const [drawProgress, setDrawProgress] = useState(null); const [drawCommand, setDrawCommand] = useState<{ action: 'undo' | 'finish' | 'cancel'; seq: number } | null>(null); const sendDraw = useCallback((action: 'undo' | 'finish' | 'cancel') => { setDrawCommand(c => ({ action, seq: (c?.seq ?? 0) + 1 })); }, []); /** AOIs whose contents are being watched for arrivals and departures. */ const [watched, setWatched] = useState>(new Set()); const [watchEvents, setWatchEvents] = useState([]); const watchBaselines = useRef>({}); const [selectedPolygon, setSelectedPolygon] = useState(null); const [showDesktopSearch, setShowDesktopSearch] = useState(false); const [showDirections, setShowDirections] = useState(false); const [activeRoute, setActiveRoute] = useState< (RouteResult & { from: { lat: number; lng: number }; to: { lat: number; lng: number }; alternates?: Array<{ type: 'LineString'; coordinates: [number, number][] }>; activeSegment?: [number, number][] | null; }) | null >(null); const [liveLocation, setLiveLocation] = useState(null); const [followUser, setFollowUser] = useState(false); const [navSession, setNavSession] = useState< { route: RouteResult; label: string; key: number } | null >(null); const [navProgress, setNavProgress] = useState(null); const [watchedFlights, setWatchedFlights] = useState([]); const [aircraftAirports, setAircraftAirports] = useState>({}); // The popup lives in raw map HTML, so it hands aircraft over through a global. useEffect(() => { (window as unknown as { osirisWatchFlight?: (f: WatchedFlight) => void }).osirisWatchFlight = (f) => { if (!f?.icao24) return; setWatchedFlights((prev) => prev.some((w) => w.icao24 === f.icao24) ? prev : [...prev, f].slice(-6)); }; }, []); const removeWatched = useCallback((icao24: string) => { setWatchedFlights((prev) => prev.filter((w) => w.icao24 !== icao24)); setAircraftAirports((prev) => { const next = { ...prev }; delete next[icao24]; return next; }); }, []); const handleAircraftDetail = useCallback((icao24: string, detail: AircraftDetail | null) => { const ports = [detail?.origin, detail?.destination] .filter((a): a is Airport => Boolean(a && Number.isFinite(a.lat) && Number.isFinite(a.lng))); setAircraftAirports((prev) => (ports.length ? { ...prev, [icao24]: ports } : prev)); }, []); // Telemetry for watched aircraft, refreshed from whatever the feed last gave us. const watchTelemetry = useMemo(() => { const out: Record = {}; if (!watchedFlights.length) return out; const buckets = [ data?.commercial_flights, data?.private_flights, data?.private_jets, data?.military_flights, ]; const wanted = new Set(watchedFlights.map((w) => w.icao24)); for (const bucket of buckets) { for (const f of bucket || []) { if (f?.icao24 && wanted.has(f.icao24)) { out[f.icao24] = { lat: f.lat, lng: f.lng, alt: f.alt, speed_knots: f.speed_knots, heading: f.heading, grounded: f.grounded, squawk: f.squawk, }; } } } return out; }, [watchedFlights, data]); // A navigation session owns its own position watch. The planner's watch dies // with the planner when guidance takes over the panel, so guidance cannot // depend on it — without this the banner sits on "waiting for a fix" forever. useEffect(() => { if (!navSession) return; if (typeof navigator === 'undefined' || !navigator.geolocation) return; const id = navigator.geolocation.watchPosition( (pos) => setLiveLocation({ lat: pos.coords.latitude, lng: pos.coords.longitude, accuracy: pos.coords.accuracy, heading: pos.coords.heading, }), () => { /* the view already explains the HTTPS requirement */ }, { enableHighAccuracy: true, maximumAge: 2000, timeout: 15000 }, ); return () => navigator.geolocation.clearWatch(id); }, [navSession]); const [showRemote, setShowRemote] = useState(false); const [showArcGIS, setShowArcGIS] = useState(false); const [arcgisLayers, setArcgisLayers] = useState>([]); const [mapCenter, setMapCenter] = useState<{ lat: number; lng: number; bounds?: { west: number; south: number; east: number; north: number } } | null>(null); const [isFullscreen, setIsFullscreen] = useState(false); const [mobilePanel, setMobilePanel] = useState<'layers'|'markets'|'intel'|'search'|'recon'|'remote'|null>(null); const [mapProjection, setMapProjection] = useState<'globe'|'mercator'>('globe'); const [terrainFocus, setTerrainFocus] = useState(0); const [terrainStatus, setTerrainStatus] = useState('idle'); const [terrainRetry, setTerrainRetry] = useState(0); const [mapStyle, setMapStyle] = useState<'dark'|'satellite'>('dark'); const [sweepData, setSweepData] = useState(null); const [scanTargets, setScanTargets] = useState([]); const [drawnPolygons, setDrawnPolygons] = useState([]); const [demoMode, setDemoMode] = useState(false); const [osirisTheme, setOsirisTheme] = useState<'core'|'ghost'>('core'); useEffect(() => { document.body.className = osirisTheme === 'core' ? '' : `theme-${osirisTheme}`; }, [osirisTheme]); /* Style Studio overrides are inline on , so they survive the theme swap above and only need reapplying once per load. */ useEffect(() => { const saved = loadSavedSettings(); if (saved) applySettings(saved); }, []); const isMobile = useIsMobile(); const startTime = useRef(Date.now()); const geocodeCache = useRef>(new Map()); const geocodeTimer = useRef | null>(null); const lastGeocodedPos = useRef<{ lat: number; lng: number } | null>(null); // ── DEFAULT: Most layers OFF — fast initial load ── const [activeLayers, setActiveLayers] = useState({ flights: false, private: false, jets: false, military: false, maritime: true, satellites: false, sat_comms: false, sat_military: false, sat_navigation: false, sat_earth: false, sat_science: false, balloons: false, cctv: true, /* The live preview tiles over the camera dots — see CctvPreviews. */ cctv_previews: true, live_news: true, earthquakes: true, fires: false, weather: false, radiation: false, infrastructure: false, global_incidents: true, war_alerts: false, day_night: true, cables: true, sdk_sea: true, sdk_air: true, sdk_naval: true, terrain_3d: false, terrain_elevation: false, malware: false, cyber_attacks: false, gdelt_events: false, cf_outages: false, cf_attacks: false, }); // Server-side capability flags — gate layers that need credentials. const selectFlatMap = () => { setActiveLayers(prev => ({ ...prev, terrain_elevation: false, terrain_3d: false })); setMapProjection('mercator'); }; const terrainPanelProps = { terrainStatus, on3DModeSelected: () => setMapProjection('globe'), onTerrainRetry: () => setTerrainRetry(value => value + 1), onTerrainFocus: () => setTerrainFocus(value => value + 1), }; const [capabilities, setCapabilities] = useState>({}); const [liveFeedUrl, setLiveFeedUrl] = useState(null); const [liveFeedName, setLiveFeedName] = useState(''); const [liveFeedEmbedAllowed, setLiveFeedEmbedAllowed] = useState(true); // Splash screen useEffect(() => { const splashTimer = setTimeout(() => setShowSplash(false), 2500); return () => clearTimeout(splashTimer); }, []); // On mount: geolocate by IP and fly to user's city (after splash/map init) useEffect(() => { if (typeof window === 'undefined') return; // Restore active layers from URL if present const p = new URLSearchParams(window.location.search); const layers = p.get('layers'); if (layers) { const active = layers.split(','); setActiveLayers(prev => { const next = { ...prev }; Object.keys(next).forEach(k => { (next as any)[k] = active.includes(k); }); return next; }); } // Probe which credential-gated feeds this deployment has configured, so the // layer panel can hide toggles that could never return data. fetch('/api/cloudflare-radar?probe=1') .then(r => (r.ok ? r.json() : null)) .then(p => { if (p) setCapabilities(c => ({ ...c, cloudflare: !!p.configured })); }) .catch(() => { /* leave the layer hidden */ }); // Once the user interacts, a late IP-location response must not steal the // camera back. The request is also cancelled when this page unmounts. const geoController = new AbortController(); const cancelAutoLocate = () => { autoLocateCancelled.current = true; }; window.addEventListener('pointerdown', cancelAutoLocate, { once: true }); window.addEventListener('keydown', cancelAutoLocate, { once: true }); const geoTimer = setTimeout(() => { if (autoLocateCancelled.current) return; fetch('/api/geo', { signal: geoController.signal }) .then(r => r.json()) .then(geo => { if (!autoLocateCancelled.current && !geoController.signal.aborted && geo.status === 'success' && Number.isFinite(geo.lat) && Number.isFinite(geo.lon) && Math.abs(geo.lat) <= 90 && Math.abs(geo.lon) <= 180) { setFlyToLocation({ lat: geo.lat, lng: geo.lon, zoom: 8, ts: Date.now() }); } }) .catch(() => { /* silent — keep default global view */ }); }, 3000); return () => { clearTimeout(geoTimer); geoController.abort(); window.removeEventListener('pointerdown', cancelAutoLocate); window.removeEventListener('keydown', cancelAutoLocate); }; }, []); // URL state: persist active layers only (lat/lon comes from IP geolocation on each load) const urlTimer = useRef | null>(null); useEffect(() => { if (typeof window === 'undefined') return; if (urlTimer.current) clearTimeout(urlTimer.current); urlTimer.current = setTimeout(() => { const active = Object.entries(activeLayers).filter(([,v]) => v).map(([k]) => k).join(','); const url = `${window.location.pathname}?layers=${active}`; window.history.replaceState(null, '', url); }, 1500); }, [activeLayers]); // Global Stats Fetch useEffect(() => { fetch('/api/stats') .then(res => res.json()) .then(d => { if (d.stats) setGlobalStats(d.stats); }) .catch(console.error); }, []); // Keyboard shortcuts useEffect(() => { const handler = (e: KeyboardEvent) => { if (['INPUT', 'TEXTAREA'].includes((e.target as Element)?.tagName)) return; if (e.key === 'f' && !e.ctrlKey) { if (document.fullscreenElement) document.exitFullscreen(); else document.documentElement.requestFullscreen(); } if (e.key === 'l') setShowLayers(p => !p); if (e.key === 'm') setShowMarkets(p => !p); if (e.key === 'c') setShowScmPanel(p => !p); if (e.key === 'i') setShowIntel(p => !p); if (e.key === 's') { setShowDesktopSearch(p => !p); setShowIntel(false); setShowMarkets(false); setShowAlerts(false); setShowSpaceCam(false); } if (e.key === 'r' && !e.ctrlKey && !e.metaKey) setFlyToLocation({ lat: 20, lng: 0, zoom: 2.5, ts: Date.now() }); if (e.key === 'g') { setActiveLayers(prev => ({ ...prev, terrain_elevation: false, terrain_3d: false })); setMapProjection(p => p === 'globe' ? 'mercator' : 'globe'); } if ((e.ctrlKey || e.metaKey) && e.key === 'f') { e.preventDefault(); setShowDesktopSearch(true); setShowIntel(false); setShowMarkets(false); setShowAlerts(false); setShowSpaceCam(false); } }; const fsHandler = () => setIsFullscreen(!!document.fullscreenElement); window.addEventListener('keydown', handler); document.addEventListener('fullscreenchange', fsHandler); return () => { window.removeEventListener('keydown', handler); document.removeEventListener('fullscreenchange', fsHandler); }; }, []); // Mouse coords + reverse geocode (Zero-Render) const handleMouseCoords = useCallback((coords: { lat: number; lng: number }) => { mouseCoordsRef.current = coords; if (coordsDisplayRef.current) { coordsDisplayRef.current.innerText = `${coords.lat.toFixed(4)}, ${coords.lng.toFixed(4)}`; } if (geocodeTimer.current) clearTimeout(geocodeTimer.current); geocodeTimer.current = setTimeout(async () => { if (lastGeocodedPos.current) { const d = Math.abs(coords.lat - lastGeocodedPos.current.lat) + Math.abs(coords.lng - lastGeocodedPos.current.lng); if (d < 0.5) return; // increased threshold — fewer geocode calls } const gk = `${coords.lat.toFixed(1)},${coords.lng.toFixed(1)}`; // coarser grid = more cache hits if (geocodeCache.current.has(gk)) { setLocationLabel(geocodeCache.current.get(gk)!); lastGeocodedPos.current = coords; return; } try { const res = await fetch(`https://nominatim.openstreetmap.org/reverse?lat=${coords.lat}&lon=${coords.lng}&format=json&zoom=10&addressdetails=1`, { headers: { 'Accept-Language': 'en' } }); if (res.ok) { const d = await res.json(); const a = d.address || {}; const label = [a.city||a.town||a.village||a.county, a.state||a.region, a.country].filter(Boolean).join(', ') || 'Unknown'; if (geocodeCache.current.size > 500) { const it = geocodeCache.current.keys(); for (let i=0;i<100;i++) { const k = it.next().value; if(k) geocodeCache.current.delete(k); }} geocodeCache.current.set(gk, label); setLocationLabel(label); lastGeocodedPos.current = coords; } } catch (e) { console.warn('[OSIRIS] Suppressed error:', e instanceof Error ? e.message : e); } }, 3000); // 3s debounce (was 1.5s) }, []); // Region dossier (right-click) const handleRightClick = useCallback(async (coords: { lat: number; lng: number }) => { setDossierLoading(true); setRegionDossier(null); try { const res = await fetch(`/api/region-dossier?lat=${coords.lat}&lng=${coords.lng}`); if (res.ok) setRegionDossier(await res.json()); } catch (e) { console.warn('[OSIRIS] Suppressed error:', e instanceof Error ? e.message : e); } finally { setDossierLoading(false); } }, []); // Entity click handler (hoisted from JSX to comply with Rules of Hooks - Fixes #113) const handleEntityClick = useCallback((entity: any) => { if (entity?.type === 'cctv') setActiveCamera(entity); if (entity?.type === 'live_news' && entity.url) { setLiveFeedUrl(entity.url); setLiveFeedName(entity.name); setLiveFeedEmbedAllowed(entity.embed_allowed !== false); } }, []); // ── Drawing / AOI ── // OsirisMap already owns the draw interaction and the polygon rendering; // this only turns a finished ring into a measured, named, coloured record. // Restore drawn areas on load. Work that vanishes on refresh is work the // operator will not trust the tool with. useEffect(() => { try { const restored = deserializeShapes(localStorage.getItem(STORAGE_KEY)); if (restored.length) setDrawnPolygons(restored); } catch { /* storage unavailable — start empty */ } }, []); useEffect(() => { try { localStorage.setItem(STORAGE_KEY, serializeShapes(drawnPolygons)); } catch { /* quota or private mode */ } }, [drawnPolygons]); // ── Tripwires ── // Re-sweep every watched AOI whenever live data refreshes and record what // changed. Keyed off dataVersion rather than `data` so this runs once per // refresh instead of once per render. useEffect(() => { if (watched.size === 0) return; const now = Date.now(); const fresh: WatchEvent[] = []; for (const shape of drawnPolygons) { if (!watched.has(shape.id)) continue; const ring = queryRing(shape); if (!ring) continue; const report = selectInPolygon(ring, dataRef.current as any); const prev = watchBaselines.current[shape.id] ?? null; const { baseline, events } = diffSweep(shape.id, report, prev, now); watchBaselines.current[shape.id] = baseline; fresh.push(...events); } if (fresh.length) setWatchEvents(log => appendEvents(log, fresh)); }, [dataVersion, watched, drawnPolygons]); const toggleWatch = useCallback((id: string) => { setWatched(prev => { const next = new Set(prev); if (next.has(id)) { next.delete(id); // Drop the baseline too, so re-arming starts clean rather than // reporting everything that moved while the watch was off. delete watchBaselines.current[id]; } else { next.add(id); } return next; }); }, []); const handleDrawComplete = useCallback((result: DrawResult) => { setDrawnPolygons(prev => [toShape(result, prev, prev.length), ...prev]); // One shape per arming: staying armed after a finish is how you end up // with an accidental second AOI from the click that dismisses the first. setDrawMode(null); setDrawProgress(null); }, []); const handleExportGeoJSON = useCallback(() => { downloadFile( `osiris-aoi-${new Date().toISOString().slice(0, 10)}.geojson`, JSON.stringify(shapesToGeoJSON(drawnPolygons), null, 2), 'application/geo+json', ); }, [drawnPolygons]); // ── SHARED FETCH UTILITY (Fixes #107 — single definition, not 3 copies) ── /* `skipWhenHidden` is for background polling only — skipping a *user-initiated* load (a layer toggle, or first paint in a background tab) leaves the caller believing it fetched, so the layer stays empty until a full reload. Returns whether data actually landed, so callers can retry. */ const fetchEndpoint = useCallback(async ( url: string, transform?: (d: any) => any, options?: RequestInit, { skipWhenHidden = false }: { skipWhenHidden?: boolean } = {}, ): Promise => { if (skipWhenHidden && typeof document !== 'undefined' && document.hidden) return false; try { // Force the browser to bypass its local disk cache for real-time data const res = await fetch(url, { ...options, cache: 'no-store' }); if (res.ok) { const json = await res.json(); const d = transform ? transform(json) : json; dataRef.current = { ...dataRef.current, ...d }; setDataVersion(v => v + 1); setBackendStatus('connected'); return true; } return false; } catch (e) { console.warn('[OSIRIS] Suppressed error:', e instanceof Error ? e.message : e); setBackendStatus('error'); return false; } }, []); // ── PROGRESSIVE DATA LOADING (request-optimized) ── useEffect(() => { // Priority 1: Core feeds (always needed for panels) const eqUrl = 'https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_day.geojson'; const eqTransform = (data: any) => ({ earthquakes: (data.features || []).map((f: any) => ({ id: f.id, lat: f.geometry?.coordinates?.[1] || 0, lng: f.geometry?.coordinates?.[0] || 0, depth: f.geometry?.coordinates?.[2] || 0, magnitude: f.properties?.mag, place: f.properties?.place, time: f.properties?.time, url: f.properties?.url, tsunami: f.properties?.tsunami, type: f.properties?.type, felt: f.properties?.felt, alert: f.properties?.alert })) }); fetchEndpoint(eqUrl, eqTransform); fetchEndpoint('/api/news'); /* A cold start can time out every upstream quote and return an all-empty feed. Waiting a full poll interval to find out leaves the panel blank for 15 minutes, so retry a few times up-front until instruments actually land. */ const marketRetries: ReturnType[] = []; const loadMarkets = async (attempt = 0) => { await fetchEndpoint('/api/markets', d => ({ markets: d })); if ((dataRef.current.markets?.count || 0) === 0 && attempt < 3) { marketRetries.push(setTimeout(() => loadMarkets(attempt + 1), 15000)); } }; const marketTimer = setTimeout(() => loadMarkets(), 800); // Priority 2: Space Weather (needed for MarketsPanel) const spaceTimer = setTimeout(async () => { try { const r = await fetch('/api/space-weather'); if (r.ok) setSpaceWeather(await r.json()); } catch (e) { console.warn('[OSIRIS] Suppressed error:', e instanceof Error ? e.message : e); } }, 5000); // Polling — OPTIMIZED intervals to minimize edge requests const intervals = [ setInterval(() => fetchEndpoint(eqUrl, eqTransform, undefined, { skipWhenHidden: true }), 900000), // 15 min (was 5) setInterval(() => fetchEndpoint('/api/news', undefined, undefined, { skipWhenHidden: true }), 1800000), // 30 min (was 10) setInterval(() => fetchEndpoint('/api/markets', d => ({ markets: d }), undefined, { skipWhenHidden: true }), 900000), // 15 min (was 5) ]; return () => { clearTimeout(marketTimer); marketRetries.forEach(clearTimeout); clearTimeout(spaceTimer); intervals.forEach(clearInterval); }; }, [fetchEndpoint]); // ── LAYER-AWARE DATA LOADING — only fetch when layer is toggled ON ── const layerFetchedRef = useRef>(new Set()); useEffect(() => { if (!activeLayers.cctv) return; return loadCameraCatalog(cameras => { dataRef.current = { ...dataRef.current, cameras: mergeCameraCatalog(dataRef.current.cameras ?? [], cameras), }; setDataVersion(value => value + 1); setBackendStatus('connected'); }, () => console.warn('[OSIRIS] Camera catalogue load failed; bounded retry scheduled')); }, [activeLayers.cctv]); useEffect(() => { // Flights if (activeLayers.flights || activeLayers.military || activeLayers.jets || activeLayers.private) { if (!layerFetchedRef.current.has('flights')) { fetchEndpoint('/api/flights'); layerFetchedRef.current.add('flights'); } } // Satellites (any satellite sub-layer triggers fetch) const anySatLayer = activeLayers.satellites || activeLayers.sat_comms || activeLayers.sat_military || activeLayers.sat_navigation || activeLayers.sat_earth || activeLayers.sat_science; if (anySatLayer && !layerFetchedRef.current.has('satellites')) { // Keep the moment the positions were propagated for. The catalogue is // fetched once and never re-polled, so by the time an orbit is requested // these markers can be a long way out of date — the orbit route needs the // marker's epoch to draw a track that still passes through it. fetchEndpoint('/api/satellites', d => ({ ...d, satellites_at: d.timestamp })); layerFetchedRef.current.add('satellites'); } // Fires if (activeLayers.fires && !layerFetchedRef.current.has('fires')) { fetchEndpoint('/api/fires'); layerFetchedRef.current.add('fires'); } // Maritime if (activeLayers.maritime && !layerFetchedRef.current.has('maritime')) { fetchEndpoint('/api/maritime', d => ({ maritime_ports: d.ports, maritime_chokepoints: d.chokepoints, maritime_ships: d.ships })); layerFetchedRef.current.add('maritime'); } // Balloons if (activeLayers.balloons && !layerFetchedRef.current.has('balloons')) { fetchEndpoint('/api/balloons', d => ({ balloons: d.balloons })); layerFetchedRef.current.add('balloons'); } // Radiation if (activeLayers.radiation && !layerFetchedRef.current.has('radiation')) { fetchEndpoint('/api/radiation', d => ({ radiation: d.stations })); layerFetchedRef.current.add('radiation'); } // Live News if (activeLayers.live_news && !layerFetchedRef.current.has('live_news')) { fetchEndpoint('/api/live-news', d => ({ live_feeds: d.feeds })); layerFetchedRef.current.add('live_news'); } // Weather if (activeLayers.weather && !layerFetchedRef.current.has('weather')) { fetchEndpoint('/api/weather', d => ({ weather_events: d.events })); layerFetchedRef.current.add('weather'); } // Infrastructure if (activeLayers.infrastructure && !layerFetchedRef.current.has('infrastructure')) { fetchEndpoint('/api/infrastructure', d => ({ infrastructure: d.infrastructure })); layerFetchedRef.current.add('infrastructure'); } // Global Incidents (GDELT) if (activeLayers.global_incidents && !layerFetchedRef.current.has('gdelt')) { fetchEndpoint('/api/gdelt', d => ({ gdelt: d.events })); layerFetchedRef.current.add('gdelt'); } // Submarine Cables if (activeLayers.cables && !layerFetchedRef.current.has('cables')) { (async () => { try { const ts = Date.now(); const res = await fetch(`/data/submarine-cables.json?v=${ts}`); if (res.ok) { const cablesData = await res.json(); dataRef.current = { ...dataRef.current, submarine_cables: cablesData.features }; setDataVersion(v => v + 1); } } catch (e) { console.warn('Cables fetch failed'); } })(); layerFetchedRef.current.add('cables'); } // Live Malware (abuse.ch) is pushed, not fetched — see the SSE subscription below. // Live Cyber Attacks (animated arcs) if ((activeLayers as any).cyber_attacks && !layerFetchedRef.current.has('cyber_attacks')) { fetchEndpoint('/api/cyber-attacks', d => ({ cyber_attacks: d.attacks })); layerFetchedRef.current.add('cyber_attacks'); } /* Mark before awaiting so a re-render mid-flight cannot double-fetch, then release the mark if nothing landed — otherwise one failed request leaves the layer permanently empty. */ const loadLayerOnce = (key: string, url: string, transform: (d: any) => any) => { if (layerFetchedRef.current.has(key)) return; layerFetchedRef.current.add(key); fetchEndpoint(url, transform).then(ok => { if (!ok) layerFetchedRef.current.delete(key); }); }; // GDELT 2.0 geocoded events if ((activeLayers as any).gdelt_events) { loadLayerOnce('gdelt_events', '/api/gdelt-events?limit=600', d => ({ gdelt_events: d.events })); } // Cloudflare Radar — one request backs both layers if ((activeLayers as any).cf_outages || (activeLayers as any).cf_attacks) { loadLayerOnce('cloudflare_radar', '/api/cloudflare-radar', d => ({ cf_outages: d.outages ?? [], cf_attack_origins: d.attack_origins ?? [], })); } }, [activeLayers]); // ── LAYER-AWARE POLLING — only poll data for active layers ── useEffect(() => { const intervals: ReturnType[] = []; if (activeLayers.flights || activeLayers.military || activeLayers.jets || activeLayers.private) { intervals.push(setInterval(() => fetchEndpoint('/api/flights'), 300000)); // 5 min (was 2 min) } if (activeLayers.balloons) { intervals.push(setInterval(() => fetchEndpoint('/api/balloons', d => ({ balloons: d.balloons })), 300000)); // 5m } if (activeLayers.radiation) { intervals.push(setInterval(() => fetchEndpoint('/api/radiation', d => ({ radiation: d.stations })), 300000)); // 5m } if (activeLayers.maritime) { intervals.push(setInterval(() => fetchEndpoint('/api/maritime', d => ({ maritime_ports: d.ports, maritime_chokepoints: d.chokepoints, maritime_ships: d.ships })), 10000)); // 10s } if ((activeLayers as any).cyber_attacks) { intervals.push(setInterval(() => { layerFetchedRef.current.delete('cyber_attacks'); fetchEndpoint('/api/cyber-attacks', d => ({ cyber_attacks: d.attacks })); layerFetchedRef.current.add('cyber_attacks'); }, 10000)); // 10s — rapid refresh } return () => intervals.forEach(clearInterval); }, [activeLayers, fetchEndpoint]); /* ── LIVE MALWARE — pushed over SSE while the layer is on ── Detections arrive when URLhaus reports them rather than on a timer, so there is no poll interval to tune and no request that re-downloads the same rows to discover nothing changed. The connection also carries the progressive geolocation fill, which is why a cold server paints the map in batches instead of staying empty and then snapping to full. */ useEffect(() => { if (!activeLayers.malware) return; const source = new EventSource('/api/malware/stream'); // Keyed by address: a host re-reported with a new payload updates its node // rather than stacking a second dot on the same coordinates. const byIp = new Map(); // The server sends `status` at the end of every poll, so the first one // marks the end of the initial fill — anything after it is genuinely new // and worth drawing attention to. let filled = false; const commit = () => { dataRef.current = { ...dataRef.current, malware_threats: [...byIp.values()] }; setDataVersion(v => v + 1); }; source.onmessage = ev => { try { const event = JSON.parse(ev.data); if (event.type === 'snapshot') { byIp.clear(); for (const d of event.detections) byIp.set(d.ip, d); commit(); } else if (event.type === 'detections') { // Only a first sighting is an arrival. An existing host that served // another payload arrives with fresh:false and keeps whatever beacon // state it already had, so it does not re-flag itself as new. const beacon = filled && event.fresh; for (const d of event.detections) { byIp.set(d.ip, beacon ? { ...d, detected_at: Date.now() } : { ...d, detected_at: byIp.get(d.ip)?.detected_at }); } commit(); } else if (event.type === 'status') { filled = true; // Hosts that have gone dark since the last poll. The store prunes // these; without mirroring it here the map would only ever grow. if (event.retired?.length) { for (const ip of event.retired) byIp.delete(ip); commit(); } } setBackendStatus('connected'); } catch { // One malformed frame must not tear down the subscription. } }; /* EventSource reconnects on its own, firing `error` on each attempt, and the store replays a snapshot on connect so a drop self-heals. Only a readyState of CLOSED means it has given up — reporting the transient ones would flag the backend as down every time a connection recycles. */ source.onopen = () => setBackendStatus('connected'); source.onerror = () => { if (source.readyState === EventSource.CLOSED) setBackendStatus('error'); }; return () => source.close(); }, [activeLayers.malware]); // CCTV: loaded once on layer toggle via layerFetchedRef (no viewport polling) // Reactive layer fetch: handled by layerFetchedRef above (no duplicate) // ── OSIRIS SDK — Intelligence Fusion Layer ── // Produces node coordinates for the SDK network mesh visualization. // Does NOT duplicate existing layer visuals — SDK layer is LINES ONLY. // Cameras are excluded — they have their own dedicated layer. useEffect(() => { const anyActive = activeLayers.sdk_sea || activeLayers.sdk_air || activeLayers.sdk_naval; if (!anyActive) { dataRef.current = { ...dataRef.current, sdk_entities: [] }; return; } const sdkEntities: any[] = []; // Air domain (nodes only — no visual duplication) const allFlights = [ ...(data.commercial_flights || []), ...(data.private_flights || []), ...(data.private_jets || []), ...(data.military_flights || []), ]; // Sample flights to keep it clean (every Nth) const flightStep = Math.max(1, Math.floor(allFlights.length / 60)); for (let i = 0; i < allFlights.length; i += flightStep) { const f = allFlights[i]; if (!f.lat || !f.lng) continue; sdkEntities.push({ type: 'Feature', geometry: { type: 'Point', coordinates: [f.lng, f.lat] }, properties: { domain: 'AIR', name: f.callsign?.trim() || 'TRACK', source: 'ADS-B / OpenSky' }, }); } // Sea domain const ships = data.maritime_ships || []; const shipStep = Math.max(1, Math.floor(ships.length / 60)); for (let i = 0; i < ships.length; i += shipStep) { const s = ships[i]; if (!s.lat || !s.lng) continue; sdkEntities.push({ type: 'Feature', geometry: { type: 'Point', coordinates: [s.lng, s.lat] }, properties: { domain: 'SEA', name: s.name || `MMSI-${s.mmsi}`, source: 'AIS Stream' }, }); } // Events — Earthquakes if (data.earthquakes?.length) { for (const eq of data.earthquakes) { if (!eq.lat || !eq.lng) continue; sdkEntities.push({ type: 'Feature', geometry: { type: 'Point', coordinates: [eq.lng, eq.lat] }, properties: { domain: 'LAND', name: `M${eq.magnitude} ${eq.place || ''}`, source: 'USGS' }, }); } } // GDELT events if (data.gdelt?.length) { for (const g of data.gdelt) { if (!g.lat || !g.lng) continue; sdkEntities.push({ type: 'Feature', geometry: { type: 'Point', coordinates: [g.lng, g.lat] }, properties: { domain: 'INTEL', name: g.name || 'GDELT Event', source: 'GDELT Project' }, }); } } // News intel if (data.news?.length) { for (const n of data.news) { if (!n.coords || n.coords.length < 2) continue; sdkEntities.push({ type: 'Feature', geometry: { type: 'Point', coordinates: [n.coords[1], n.coords[0]] }, properties: { domain: 'INTEL', name: n.title || 'SIGINT', source: n.source || 'RSS Feed' }, }); } } dataRef.current = { ...dataRef.current, sdk_entities: sdkEntities }; }, [dataVersion, activeLayers.sdk_sea, activeLayers.sdk_air, activeLayers.sdk_naval]); const totalFlights = useMemo(() => ( (data.commercial_flights?.length||0)+(data.private_flights?.length||0)+(data.private_jets?.length||0)+(data.military_flights?.length||0) ), [data.commercial_flights, data.private_flights, data.private_jets, data.military_flights]); return (
{/* ── SPLASH ── */} {showSplash && ( {/* ── Scanline CRT overlay ── */}
{/* ── V4.2 badge — top-left ── */} V4.2 {/* ── Geometric tactical logo ── */}
{/* Outer ring — slow clockwise */}
{/* Middle ring — faster counter-clockwise */}
{/* Inner ring — fastest clockwise */}
{/* Core circle + crosshair */} {/* Crosshair lines */}
{/* Faint pulsing radar sweep */}
{/* ── OSIRIS title — letter-by-letter stagger ── */}
{'OSIRIS'.split('').map((letter, i) => ( {letter} ))}
{/* ── Subtitle — typewriter reveal ── */}

GLOBAL INTELLIGENCE PLATFORM

{/* ── Multi-stage progress bar ── */}
{/* Thin progress track */}
{/* Status messages — cycling */}
{[ { text: 'ESTABLISHING SECURE CONNECTION...', delay: 0.5 }, { text: 'INITIALIZING FEEDS...', delay: 1.1 }, { text: 'CALIBRATING SENSORS...', delay: 1.7 }, { text: 'SYSTEM READY', delay: 2.2 }, ].map((stage, i) => ( {stage.text} ))}
{/* ── Decorative grid lines ── */}
{/* ── Corner frame accents ── */} {[ { t: '10px', l: '10px', bw: '2px 0 0 2px' }, { t: '10px', r: '10px', bw: '2px 2px 0 0' }, { b: '10px', l: '10px', bw: '0 0 2px 2px' }, { b: '10px', r: '10px', bw: '0 2px 2px 0' }, ].map((pos, i) => ( ))} {/* ── Inline keyframe for scanline drift ── */} )} {/* ── MAP ── */} setMapRetry(retry => retry + 1)} data={data} activeLayers={activeLayers} projection={mapProjection === 'mercator' ? 'mercator' : 'globe'} terrainEnabled={activeLayers.terrain_elevation && mapProjection === 'globe'} terrainFocus={terrainFocus} terrainRetry={terrainRetry} onTerrainStatusChange={setTerrainStatus} mapStyle={mapStyle === 'satellite' ? 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}' : 'dark'} onEntityClick={handleEntityClick} onMouseCoords={handleMouseCoords} onRightClick={handleRightClick} onViewStateChange={setMapView} flyToLocation={flyToLocation} sweepData={sweepData} scanTargets={scanTargets} demoMode={demoMode} theme={osirisTheme} arcgisLayers={arcgisLayers.filter(l => l.visible).map(l => ({ id: l.id, title: l.title, geojson: l.geojson, color: l.color, opacity: l.opacity }))} onMapCenter={setMapCenter} route={activeRoute} userLocation={ navSession && navProgress ? { lat: navProgress.snapped[1], lng: navProgress.snapped[0], accuracy: liveLocation?.accuracy, heading: liveLocation?.heading } : liveLocation } followUser={followUser} onFollowInterrupt={() => setFollowUser(false)} navigating={Boolean(navSession)} drawMode={drawMode} onDrawProgress={setDrawProgress} drawCommand={drawCommand} onDrawCancel={() => { setDrawMode(null); setDrawProgress(null); }} onDrawComplete={handleDrawComplete} drawnPolygons={drawnPolygons} aircraftAirports={aircraftAirports} /> {/* ── DIRECTIONS — opens beside the right-hand tool rail ── */}
{navSession ? ( setFollowUser(true)} onExit={() => { setNavSession(null); setNavProgress(null); setFollowUser(false); }} onReroute={async (fromPt) => { // Re-plan from where the driver actually is, to the same destination. const dest = navSession.route.geometry.coordinates.at(-1)!; try { const res = await fetch( `/api/directions?from=${fromPt.lat},${fromPt.lng}&to=${dest[1]},${dest[0]}&mode=auto`, ); const data = await res.json(); if (res.ok && !data.error) { setNavSession((n) => (n ? { ...n, route: data, key: Date.now() } : n)); setActiveRoute({ ...data, from: fromPt, to: { lat: dest[1], lng: dest[0] } }); } } catch { /* keep the old route rather than dropping guidance */ } }} /> ) : null} {/* The planner stays mounted underneath a running session: unmounting it would discard the route you are driving, so ending guidance would drop you into an empty form instead of back onto your route. */} {showDirections && ( setActiveRoute(r)} onLiveLocation={setLiveLocation} onFollowChange={setFollowUser} onActiveSegment={(seg) => setActiveRoute((r) => (r ? { ...r, activeSegment: seg } : r))} onStartNavigation={(r, label) => { setNavSession({ route: r, label, key: Date.now() }); setFollowUser(true); }} onLocate={(lat, lng, zoom) => setFlyToLocation({ lat, lng, zoom, ts: Date.now() })} onClose={() => { setShowDirections(false); setActiveRoute(null); }} /> )}
{/* ── FLIGHT WATCH ── */} {watchedFlights.length > 0 && ( setFlyToLocation({ lat, lng, zoom: 8, ts: Date.now() })} onDetail={handleAircraftDetail} /> )} {/* ── MAP VIEW CONTROLS ── */} {/* Unified Control Strip */}
setMapProjection('globe')} title="3D Globe" icon={Globe} label="3D" />
setMapStyle('dark')} title="Night Mode" icon={Moon} label="MAP" /> setMapStyle('satellite')} title="Satellite View" icon={Satellite} label="SAT" />
{/* Scale Bar */} {!isMobile && (
)} {/* ── HEADER ── */}

OSIRIS

OPEN SOURCE INTELLIGENCE
REAL-TIME GLOBAL MONITORING · FLIGHTS · MARITIME · SATELLITES · CCTV · WEATHER · CYBER THREATS
{/* ── TOP-RIGHT STATUS (desktop) ── */} STATUS: {backendStatus === 'connected' ? 'LIVE' : backendStatus.toUpperCase()} {Object.values(activeLayers).filter(Boolean).length} LAYERS ENTITIES {spaceWeather && SOLAR: Kp{spaceWeather.kp_index}} V.4.1
SUPPORT {/* ── MOBILE: Compact top status ── */} {/* The route planner claims the top of a phone screen; leaving this in place would put the support badge underneath the destination field. */} {isMobile && !showDirections && !navSession && (
SUPPORT )} {/* ── NEW SIDEBAR (Root Level) ── */} {showLayers && !isMobile && } {/* ── RIGHT TOOL STRIP (desktop only — mobile uses bottom nav) ── */} {!isMobile &&
RECON {showIntel && ( { setScanTargets(prev => { const existing = prev.filter(t => t.id !== target); return [{ id: target, timestamp: Date.now(), ...data }, ...existing].slice(0, 10); }); setFlyToLocation({ lat: data.lat, lng: data.lng, ts: Date.now() }); }} /> )}
SPACE {showSpaceCam && ( )}
MARKETS {showMarkets && ( )}
ALERTS {showAlerts && ( setFlyToLocation({ lat, lng, ts: Date.now() })} onWatchFeed={(url, name) => { setLiveFeedUrl(url); setLiveFeedName(name); }} /> )}
DRAW
ROUTE
SEARCH {showDesktopSearch && ( { setFlyToLocation({ lat, lng, zoom, ts: Date.now() }); setShowDesktopSearch(false); }} /> )}
{/* Separator */}
{/* ── ARCGIS INTEL ── */}
ARCGIS {showArcGIS && (
setArcgisLayers(prev => [...prev.filter(l => l.id !== layer.id), { ...layer, color: layer.color || '#D4AF37', visible: true, opacity: layer.opacity ?? 0.8 }])} onRemoveLayer={(id) => setArcgisLayers(prev => prev.filter(l => l.id !== id))} onUpdateLayer={(id, updates) => setArcgisLayers(prev => prev.map(l => l.id === id ? { ...l, ...updates } : l))} importedLayers={arcgisLayers} mapBounds={mapCenter?.bounds || null} />
)}
{/* Separator */}
{/* ── WORLD REMOTE ── */}
REMOTE {showRemote && ( setShowRemote(false)} onPlaceOnMap={(devs) => { setScanTargets(prev => { const ids = new Set(prev.map((t: any) => t.id)); const next = [...prev]; devs.forEach(d => { if (!ids.has(d.id)) next.unshift({ id: d.id, name: d.name, lat: d.lat, lng: d.lng, type: d.type, color: d.color, timestamp: Date.now(), source: 'BLE' }); }); return next.slice(0, 20); }); if (devs.length > 0) setFlyToLocation({ lat: devs[0].lat, lng: devs[0].lng, ts: Date.now() }); }} /> )}
} {/* ── LIVE FEED VIEWER OVERLAY ── */} {liveFeedUrl && ( setLiveFeedUrl(null)} > e.stopPropagation()} > {/* Header */}
{liveFeedName} LIVE STREAM {!liveFeedEmbedAllowed && ( EXTERNAL ONLY )}
Open in YouTube
{/* Body — iframe or external card */} {liveFeedEmbedAllowed ? (