Frontend Architecture

⚛️ SkySpy Frontend Architecture

Premium React application for real-time aircraft tracking and monitoring

React Vite Leaflet Socket.IO


🎯 Overview

The SkySpy frontend is a modern React application built with Vite, delivering a real-time aircraft tracking and monitoring dashboard. Features include a modular component architecture, Socket.IO-based real-time data streaming, and responsive design for desktop and mobile.

📸

Screenshot Placeholder Main Dashboard The main dashboard showing live aircraft tracking with real-time updates


🛠️ Technology Stack

TechnologyPurposeBadge
⚛️ React 18+UI framework with hooks-based architectureReact
ViteLightning-fast build tool and dev serverVite
🗺️ LeafletInteractive map renderingLeaflet
🔌 Socket.IOReal-time data streamingSocket.IO
🎨 CSS3Custom styling with CSS variablesCSS3
🔷 LucideIcon libraryLucide

📁 Directory Structure

web/src/
├── 📄 App.jsx                    # 🚀 Main application entry point
│
├── 📂 components/                # React components by feature
│   ├── 🛩️ aircraft/             # Aircraft detail components
│   ├── 📋 aircraft-list/        # Aircraft list view
│   ├── 🔔 alerts/               # Alert rule management
│   ├── 📦 archive/              # Historical data archive
│   ├── 🎵 audio/                # Radio transmission playback
│   ├── 🔐 auth/                 # Authentication components
│   ├── 🎯 cannonball/           # Mobile proximity mode
│   ├── 🧩 common/               # Shared components
│   ├── 🏆 gamification/         # Achievement system
│   ├── 📜 history/              # Historical views
│   ├── 📐 layout/               # Layout (Sidebar, Header)
│   ├── 🗺️ map/                  # Map view and overlays
│   ├── 📋 notams/               # NOTAM display
│   ├── ⚠️ safety/               # Safety event components
│   └── 👁️ views/                # Main view containers
│
├── 📂 contexts/                  # React context providers
│   └── 🔑 AuthContext.jsx       # Auth state management
│
├── 📂 hooks/                     # Custom React hooks
│   ├── 📡 channels/             # Socket.IO handlers
│   └── ℹ️ aircraftInfo/         # Data fetching utilities
│
├── 📂 styles/                    # CSS stylesheets
└── 📂 utils/                     # Utility functions

🏗️ Application Architecture

Main Application Flow

The application uses hash-based routing for view navigation. App.jsx serves as the central orchestrator:

flowchart TB
    subgraph App["🚀 App.jsx"]
        Nav["📍 Navigation State"]
        WS["🔌 Socket.IO Connections"]
        Config["⚙️ Global Configuration"]
        Auth["🔐 Authentication"]
    end

    subgraph Routes["🛣️ Hash Routes"]
        Map["#map"]
        Aircraft["#aircraft"]
        Airframe["#airframe?icao=ABC123"]
        Event["#event?id=42"]
    end

    App --> Routes
💡

Tip Hash routing enables deep linking and browser history support without server-side routing configuration.

🗺️ Valid Navigation Tabs

TabIconDescription
map🗺️Live aircraft map (default)
aircraft✈️Sortable aircraft list
stats📊Statistics dashboard
history📜Historical data (sessions, sightings, ACARS, safety)
audio🎵Radio transmission archive
notams📋NOTAMs display
archive📦Data archive browser
alerts🔔Alert rule management
system⚙️System status and configuration
airframe🛩️Aircraft detail page
event⚠️Safety event detail page

🧩 Component Hierarchy

🏠 Layout Architecture

graph TB
    subgraph App["⚛️ App"]
        subgraph Sidebar["📱 Sidebar"]
            Logo["🔷 Logo"]
            NavTabs["📍 Navigation"]
            ExtLinks["🔗 External Links"]
            ConnStatus["🟢 Connection Status"]
        end

        subgraph Header["🎯 Header"]
            Stats["📊 Stats Display"]
            Location["📍 Location Info"]
            Users["👥 Online Users"]
        end

        subgraph Main["📄 Main Content"]
            ActiveView["🖼️ Active View Component"]
        end
    end

    Sidebar --> Main
    Header --> Main

🗺️ Map View Components

📸

Screenshot Placeholder Map View Interactive map with aircraft tracking, safety events, and ACARS panel

graph TB
    subgraph MapView["🗺️ MapView.jsx"]
        Leaflet["🌍 Leaflet Map"]

        subgraph Panels["📊 Panels"]
            ListPanel["📋 AircraftListPanel"]
            SafetyPanel["⚠️ SafetyEventsPanel"]
            AcarsPanel["📡 AcarsPanel"]
        end

        subgraph Controls["🎛️ Controls"]
            MapCtrl["🔧 MapControls"]
            Filter["🔍 FilterMenu"]
            Overlay["📂 OverlayMenu"]
            Legend["📖 LegendPanel"]
        end

        subgraph Overlays["🎨 Overlays"]
            Popup["💬 AircraftPopup"]
            Banner["🚨 ConflictBanner"]
        end
    end

🎨 Map Display Modes

ModeDescriptionPreview
radarTraditional radar display with sweep animation🟢
crtRetro CRT-style phosphor display🟡
proProfessional ATC-style with customizable themes🔵
mapStandard map with satellite/terrain options🟠

Pro Mode Theme Colors:

  • 🔵 Classic Cyan — Default professional look
  • 🟡 Amber/Gold — Traditional ATC aesthetic
  • 🟢 Green Phosphor — Retro terminal style
  • High Contrast — Accessibility optimized

🛩️ Aircraft Detail Components

graph TB
    subgraph AircraftDetailPage["🛩️ AircraftDetailPage"]
        Header["📝 AircraftHeader"]
        Photo["📷 AircraftPhotoHero"]

        subgraph Tabs["📑 Tab Navigation"]
            Info["ℹ️ InfoTab"]
            Live["📡 LiveTab"]
            Radio["📻 RadioTab"]
            Acars["📨 AcarsTab"]
            Safety["⚠️ SafetyTab"]
            History["📜 HistoryTab"]
            Track["🛤️ TrackTab"]
        end
    end

    Header --> Tabs
    Photo --> Tabs

Performance All tabs are lazy-loaded using React.lazy() for optimal initial load performance.


🔔 Alerts System Components

graph TB
    subgraph AlertsView["🔔 AlertsView"]
        Toolbar["🔧 AlertsFilterToolbar"]

        subgraph Rules["📜 Rules"]
            RuleCard["🎴 AlertRuleCard"]
            RuleForm["📝 RuleForm"]
        end

        subgraph RuleFormParts["📝 Rule Form Components"]
            Conditions["🔀 ConditionBuilder"]
            Preview["👁️ LivePreview"]
            Channels["📢 NotificationChannelSelector"]
            Templates["📋 RuleTemplates"]
        end

        subgraph History["📜 Alert History"]
            HistToolbar["🔧 AlertHistoryToolbar"]
            HistItem["📄 AlertHistoryItem"]
        end

        TestModal["🧪 TestRuleModal"]
        ImportModal["📥 ImportRulesModal"]
    end

🎯 Alert Condition Types

ℹ️

Supported Alert Conditions Create complex rules using AND/OR logic with these condition types:

CategoryConditions
🔢 IdentifiersICAO hex, Callsign pattern, Squawk code, Aircraft type
📏 TelemetryAltitude thresholds, Speed thresholds, Distance proximity
🏷️ ClassificationMilitary aircraft, Emergency status, Law enforcement, Helicopter
📱 MobileProximity detection (Cannonball mode)

📊 Stats Dashboard Layout

📸

Screenshot Placeholder Stats Dashboard Bento grid layout with live data, charts, and system status

graph LR
    subgraph StatsView["📊 StatsView - Bento Grid"]
        subgraph Left["📋 Left Column"]
            Leaderboard["🏆 LeaderboardCard"]
            Squawk["📡 SquawkWatchlist"]
        end

        subgraph Center["📈 Center Column"]
            KPI["📊 KPI Cards"]
            Sparkline["📈 LiveSparklines"]
            Bar["📊 HorizontalBarChart"]
            Acars["📨 AcarsSection"]
            Antenna["📡 Antenna Analytics"]
        end

        subgraph Right["⚙️ Right Column"]
            System["💻 SystemStatusCard"]
            Safety["⚠️ SafetyAlertsSummary"]
            Conn["🔌 ConnectionStatusCard"]
        end
    end

🎯 Cannonball Mode (Mobile)

📸

Screenshot Placeholder Cannonball Mode Fullscreen mobile proximity detection with HUD overlay

A fullscreen mobile-optimized mode for proximity-based aircraft detection:

ComponentPurposeIcon
CannonballMode.jsxMain container🎯
HeadsUpDisplay.jsxHUD-style overlay🎮
RadarView.jsxRadar-style display📡
ThreatDisplay.jsxProximity threat cards⚠️
ThreatList.jsxSorted threat list📋
StatusBar.jsxGPS and connection status📍
EdgeIndicators.jsxOff-screen aircraft indicators↗️

🔄 State Management

State Flow Diagram

flowchart TB
    subgraph Sources["📡 Data Sources"]
        WS1["🔌 Main Socket.IO"]
        WS2["🔌 Position Socket.IO"]
        API["🌐 REST API"]
        LS["💾 localStorage"]
    end

    subgraph State["⚛️ React State"]
        Context["🔑 AuthContext"]
        Local["📍 Local State"]
        Ref["🔗 useRef"]
    end

    subgraph UI["🖼️ UI Components"]
        Views["👁️ Views"]
        Map["🗺️ Map"]
        Lists["📋 Lists"]
    end

    WS1 -->|"aircraft, safety, alerts"| Local
    WS2 -->|"positions (60fps)"| Ref
    API -->|"fetch"| Local
    LS -->|"config"| Context

    Context --> Views
    Local --> Views
    Ref --> Map

🔑 AuthContext API

const {
  // 📊 State
  status,           // 'loading' | 'anonymous' | 'authenticated'
  user,             // User object with permissions
  config,           // Auth configuration
  error,            // Last auth error
  isAuthenticated,  // Boolean shorthand

  // 🔧 Methods
  login,            // Username/password login
  logout,           // Clear session
  loginWithOIDC,    // OAuth/OIDC popup flow
  authFetch,        // Authenticated fetch wrapper
  hasPermission,    // Check single permission
  hasAnyPermission, // Check any of the permissions
  hasAllPermissions,// Check all permissions
  canAccessFeature, // Feature-based access check
  getAccessToken,   // Get JWT for Socket.IO
} = useAuth();

🔌 Socket.IO State

sequenceDiagram
    participant B as 🖥️ Backend
    participant M as 📡 Main Socket
    participant P as 📍 Position Socket
    participant R as ⚛️ React

    B->>M: aircraft:snapshot
    M->>R: setState(aircraft)

    B->>M: safety:event
    M->>R: setState(events)

    B->>P: position:update (1Hz)
    P->>R: positionsRef.current = positions
    Note over R: No re-render! 🚀

💾 localStorage Keys

KeyPurposeIcon
adsb-dashboard-configMap mode, dark mode, notifications⚙️
adsb-dashboard-overlaysMap overlay visibility🗺️
adsb-layer-opacitiesOverlay opacity settings🎨
adsb-show-aircraft-listList panel visibility📋
adsb-show-short-tracksTrack trail display🛤️
adsb-sound-mutedSound preferences🔇
skyspy-auth-tokensJWT access/refresh tokens🔑
skyspy-userCached user profile👤

🪝 Custom Hooks Reference

📡 Data Hooks

HookPurposeExample
useApiHTTP API calls with loading/error statesconst { data, loading, error } = useApi('/api/stats')
useSocketApiHTTP with Socket.IO fallbackuseSocketApi('/api/aircraft', wsData)
useAircraftInfoAircraft registry lookups with cachingconst info = useAircraftInfo(icao)
useAviationDataAviation reference data (airports, VORs)const { airports } = useAviationData()
useAlertRulesAlert rule CRUD operationsconst { rules, createRule } = useAlertRules()
useStatsDataStatistics data aggregationconst stats = useStatsData(timeRange)

🔌 Socket.IO Hooks

// 📡 Main channels socket
const { aircraft, safetyEvents, acarsMessages } = useChannelsSocket();

// 📍 High-frequency position updates (ref-based)
const positionsRef = usePositionChannels();

// 🎵 Audio streaming
const { transmissions, isConnected } = useAudioSocket();

🗺️ Map Hooks

HookPurposeReturns
useTrackHistoryAircraft track trail management{ tracks, addTrack, clearTracks }
useMapAlarmsProximity and alert sound triggers{ playAlarm, stopAlarm }
useSafetyEventsSafety event state management{ events, activeEvent }
useGesturesTouch gesture handling{ onPinch, onPan }
useDraggableDrag interaction for panels{ position, handlers }

🎯 Cannonball Mode Hooks

// 📍 GPS tracking
const { position, accuracy, error } = useDeviceGPS();

// ⚠️ Threat calculation
const threats = useThreatCalculation(aircraft, position);

// 🔊 Voice alerts
const { speak, isSpeaking } = useVoiceAlerts();

// 📳 Haptic feedback
const { vibrate } = useHapticFeedback();

// 🔒 Screen wake lock
const { requestWakeLock, releaseWakeLock } = useWakeLock();

🔧 Utility Functions

✈️ Aircraft Utilities

import {
  icaoToNNumber,
  getCountryFromIcao,
  getTailNumber,
  getCategoryName,
  callsignsMatch,
  getPirepType
} from '@/utils/aircraft';

// 🔢 ICAO to N-number conversion
icaoToNNumber('A1B2C3');      // → "N12345"

// 🌍 Country identification
getCountryFromIcao('A1B2C3'); // → { country: 'USA', flag: '🇺🇸' }

// 📋 Category names
getCategoryName('A1');        // → "Light"

// 🔀 Callsign matching (IATA/ICAO)
callsignsMatch('AAL123', 'AA123'); // → true

🔔 Alert Evaluation

import {
  evaluateCondition,
  evaluateConditionGroup,
  evaluateRule,
  findMatchingAircraft,
  getMatchReasons
} from '@/utils/alertEvaluator';

// ✅ Single condition evaluation
evaluateCondition(condition, aircraft, distanceNm);

// 🔀 Group evaluation with AND/OR logic
evaluateConditionGroup(group, aircraft, distanceNm);

// 📋 Find all matching aircraft
const matches = findMatchingAircraft(rule, aircraftList, feederLocation);

// 💬 Human-readable match reasons
const reasons = getMatchReasons(rule, aircraft, distanceNm);
// → ["Squawk 7700 (Emergency)", "Altitude below 1000ft"]

🎨 Styling Architecture

📁 CSS File Organization

styles/
├── 📄 index.css              # 🚀 Main entry, imports all
├── 📄 base.css               # 🎨 Variables, reset, typography
├── 📄 layout.css             # 📐 Layout grid and containers
├── 📄 components.css         # 🧩 Shared component styles
├── 📄 map.css                # 🗺️ Map-specific styles
├── 📄 pro-mode.css           # 📡 Pro radar mode
├── 📄 views.css              # 👁️ View-specific styles
├── 📄 aircraft-detail.css    # 🛩️ Aircraft detail page
├── 📄 stats-extended.css     # 📊 Statistics dashboard
├── 📄 cannonball.css         # 🎯 Cannonball mode
├── 📄 acars.css              # 📨 ACARS messages
├── 📄 auth.css               # 🔐 Authentication forms
├── 📄 toast.css              # 🔔 Toast notifications
├── 📄 visualizations.css     # 📈 Charts and graphs
└── 📄 responsive.css         # 📱 Mobile breakpoints

🎨 CSS Variables Reference

:root {
  /* 🎨 Colors */
  --bg-primary: #0a0d12;      /* Main background */
  --bg-secondary: #141922;    /* Card background */
  --text-primary: #e5e5e5;    /* Main text */
  --text-secondary: #9ca3af;  /* Muted text */

  /* 🌈 Accent Colors */
  --accent-cyan: #00c8ff;     /* Primary accent */
  --accent-green: #10b981;    /* Success states */
  --accent-red: #ef4444;      /* Error/danger */
  --accent-yellow: #f59e0b;   /* Warning states */

  /* 📏 Spacing Scale */
  --spacing-xs: 0.25rem;      /* 4px */
  --spacing-sm: 0.5rem;       /* 8px */
  --spacing-md: 1rem;         /* 16px */
  --spacing-lg: 1.5rem;       /* 24px */
  --spacing-xl: 2rem;         /* 32px */

  /* 🔤 Typography */
  --font-mono: 'JetBrains Mono', monospace;
  --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;

  /* ⚡ Transitions */
  --transition-fast: 150ms ease;
  --transition-normal: 250ms ease;
}

🎨 Color Palette Visual

VariableColorUsage
--bg-primary#0a0d12 #0a0d12Main background
--bg-secondary#141922 #141922Card background
--accent-cyan#00c8ff #00c8ffPrimary accent
--accent-green#10b981 #10b981Success states
--accent-red#ef4444 #ef4444Error/danger
--accent-yellow#f59e0b #f59e0bWarnings

📱 Responsive Breakpoints

graph LR
    subgraph Breakpoints["📱 Responsive Breakpoints"]
        Mobile["📱 Mobile<br/>≤768px"]
        Tablet["📱 Tablet<br/>769-1024px"]
        Desktop["💻 Desktop<br/>1025-1439px"]
        Large["🖥️ Large<br/>≥1440px"]
    end

    Mobile --> Tablet --> Desktop --> Large

🏗️ Build and Development

⚡ Vite Configuration

// vite.config.js
export default defineConfig({
  plugins: [react()],
  base: '/static/',           // 🗂️ Django static path
  build: {
    outDir: 'dist',
    sourcemap: false,
    minify: 'terser',
  },
  server: {
    host: '0.0.0.0',
    port: 3000,
    proxy: {
      '/api': {
        target: process.env.VITE_API_TARGET || 'http://localhost:8000',
        changeOrigin: true,
      },
      '/ws': {
        target: apiTarget.replace('http', 'ws'),
        ws: true,
        changeOrigin: true,
      },
    },
  },
});

🛠️ Development Commands

# 📦 Install dependencies
npm install

# 🚀 Start development server
npm run dev

# 🏗️ Build for production
npm run build

# 👁️ Preview production build
npm run preview

# 🔍 Lint code
npm run lint

🌍 Environment Variables

VariablePurposeDefault
VITE_API_TARGETBackend API URL for dev proxyhttp://localhost:8000
📝

Note In production, the frontend is served by Django using relative URLs. VITE_API_TARGET is only used for the Vite dev server proxy.


⚡ Performance Optimization

Performance Tips SkySpy is optimized for real-time data at 60fps. Here's how:

🚀 Virtual Scrolling

Large lists use the VirtualList component to render only visible items, enabling smooth scrolling with thousands of aircraft.

🧠 Memoization

// ✅ Expensive computations memoized
const filteredAircraft = useMemo(() => {
  return aircraft
    .filter(a => matchesFilters(a, filters))
    .sort((a, b) => sortComparator(a, b, sortField));
}, [aircraft, filters, sortField]);

🔗 Ref-Based State for High-Frequency Data

// 🚀 Positions in ref = no React re-renders!
const positionsRef = useRef({});

// Animation loop reads directly from ref
requestAnimationFrame(() => {
  const positions = positionsRef.current;
  // Update map markers at 60fps
  // Zero React overhead! ⚡
});

⏱️ Debounced Updates

Search and filter inputs are debounced to prevent excessive re-renders during typing.

📦 Lazy Loading

// 📦 Components loaded on-demand
const InfoTab = lazy(() => import('./tabs/InfoTab'));
const LiveTab = lazy(() => import('./tabs/LiveTab'));
const RadioTab = lazy(() => import('./tabs/RadioTab'));
const AcarsTab = lazy(() => import('./tabs/AcarsTab'));
const SafetyTab = lazy(() => import('./tabs/SafetyTab'));
const HistoryTab = lazy(() => import('./tabs/HistoryTab'));
const TrackTab = lazy(() => import('./tabs/TrackTab'));

🛡️ Error Boundaries

// 🛡️ Prevents cascading failures
<ErrorBoundary
  onRetry={retry}
  fallback={<ErrorFallback />}
>
  {renderTabContent()}
</ErrorBoundary>

🔐 Authentication Integration

🔑 Token Management

sequenceDiagram
    participant U as 👤 User
    participant A as ⚛️ App
    participant B as 🖥️ Backend

    U->>A: Login
    A->>B: POST /api/auth/login
    B->>A: JWT tokens
    A->>A: Store in localStorage
    A->>A: Schedule refresh (30s before expiry)

    Note over A: Token expires in 30s...

    A->>B: POST /api/auth/refresh
    B->>A: New JWT tokens
    A->>A: Update localStorage

🔒 Permission-Based UI

const { canAccessFeature } = useAuth();

// 🔐 Conditional rendering based on permissions
if (canAccessFeature('alerts', 'write')) {
  return <AlertRuleForm />;
}

// 🛡️ Protected route wrapper
<ProtectedRoute permission="audio:read">
  <AudioView />
</ProtectedRoute>

🌐 Browser Compatibility

Desktop Browsers

BrowserVersionStatus
Chrome90+✅ Supported
Firefox88+✅ Supported
Safari14+✅ Supported
Edge90+✅ Supported

Mobile Browsers

BrowserVersionStatus
iOS Safari14+✅ Supported
Chrome Android90+✅ Supported

📚 Related Documentation

DocumentDescription
📡 Backend API DocumentationREST API reference
🔌 Socket.IO APIReal-time streaming protocol
🚀 Deployment GuideProduction deployment
⚙️ Configuration ReferenceEnvironment configuration

Built with ⚛️ React + ⚡ Vite + 🗺️ Leaflet

Real-time aircraft tracking at 60fps