App structure
API proxy pattern
The Next.js server acts as a transparent proxy for all/api/* requests. The catch-all route at src/app/api/[...path]/route.ts reads BACKEND_URL at request time and forwards the call:
- The browser only ever talks to port 3000. Port 8000 does not need to be exposed.
BACKEND_URLis a runtime environment variable. Changing it in Docker Compose or Portainer does not require rebuilding the image.- Local dev without Docker uses
BACKEND_URL=http://localhost:8000by default.
next.config.ts:
Data polling
Polling logic lives insrc/hooks/useDataPolling.ts. It maintains two independent timer loops — one for the fast endpoint and one for the slow endpoint — and merges their responses into a shared dataRef:
| Tier | Startup retry | Steady state | Endpoint |
|---|---|---|---|
| Fast | 3s | 15s | /api/live-data/fast |
| Slow | 5s | 120s | /api/live-data/slow |
The
useEffect dependency array is intentionally empty. This matches the proven GitHub polling pattern — the loop starts once and never restarts in response to prop or state changes.MaplibreViewer
MaplibreViewer.tsx (~2,500 lines) is the core rendering component. It wraps react-map-gl/maplibre and manages every GeoJSON data source on the map.
The component is loaded with next/dynamic and ssr: false to prevent window is not defined errors during server-side rendering:
- Viewport bounds tracking — computes map bounds on every move and writes them to a
viewBoundsRefused by polling hooks for server-side bbox filtering. - GeoJSON builders — dedicated builder functions (
buildFlightLayerGeoJSON,buildShipsGeoJSON,buildSatellitesGeoJSON, etc.) convert raw API data into MapLibre-compatible FeatureCollections. - Imperative source updates — high-frequency layers (flights, satellites, fires) call
map.getSource(id).setData(geojson)directly, bypassing React reconciliation entirely. - Position interpolation — a
useInterpolationhook animates positions between data refreshes using a 10-second tick. - Viewport culling — an
inView(lat, lng)helper filters features to those within the current map bounds plus a 20% buffer before building GeoJSON. - Solar terminator — a night polygon is recomputed every 60 seconds from the current UTC time.
HUD layout
page.tsx arranges the map and panels in a fixed full-screen layout:
Key components
WorldviewLeftPanel
Vertical list of toggleable data layers. Each toggle fires
setActiveLayers in page.tsx, which flows down to MaplibreViewer as a prop. Also hosts the NASA GIBS date slider and opacity control.NewsFeed
Displays the aggregated RSS intel feed. When a flight, ship, or map entity is selected, it switches to a detail view showing entity metadata, Wikipedia images, and GDELT events.
FindLocateBar
Searches flights, ships, and satellites by callsign or name. Accepts raw coordinates (
31.8, 34.8) or place names geocoded via OSM Nominatim. Selecting a result calls flyTo on the map.RadioInterceptPanel
Scanner-style panel for Broadcastify and OpenMHz feeds. Supports “Eavesdrop Mode” — clicking the map finds the nearest radio system to that location.
MarketsPanel
Live financial market indices (defense stocks, oil prices). Minimizable. Data comes from the slow endpoint’s
stocks and oil fields.FilterPanel
Basic layer filters (airline, country, aircraft type).
AdvancedFilterModal extends this with airport, owner, and registration prefix filtering.SettingsPanel
Manages API keys and the customizable news feed list (up to 20 RSS sources with priority weights 1–5). Changes are persisted to the backend via
PUT /api/settings/*.ErrorBoundary
Each major panel is wrapped in an
ErrorBoundary that catches render errors and shows a fallback UI without crashing the rest of the dashboard.MapLibre GL layers
The map renders the following named source/layer pairs:| Layer | Type | Clustering |
|---|---|---|
| Commercial flights | Symbol (SVG icons) | No |
| Military flights | Symbol | No |
| Private flights / jets | Symbol | No |
| Satellites | Symbol | No |
| AIS ships | Symbol | Yes (MapLibre cluster) |
| CCTV cameras | Symbol | Yes |
| Earthquakes | Circle + label | Yes |
| GPS jamming zones | Fill rectangle | No |
| Fire hotspots | Symbol | Yes |
| GDELT incidents | Symbol | No |
| Ukraine frontline | Line + fill | No |
| KiwiSDR receivers | Symbol | Yes |
| Internet outages | Circle | No |
| Data centers | Symbol | Yes |
| Day/night cycle | Fill (polygon) | No |
| NASA GIBS imagery | Raster tile | No |
| Esri satellite imagery | Raster tile | No |