diff --git a/README.md b/README.md index 787d5c1..c05dc49 100644 --- a/README.md +++ b/README.md @@ -63,18 +63,59 @@ cd intercept docker compose up -d ``` -> **Note:** Docker requires privileged mode for USB SDR access. See `docker-compose.yml` for configuration options. - -### ADS-B History (Optional) - -The ADS-B history feature persists aircraft messages to Postgres for long-term analysis. - -```bash -# Start with ADS-B history and Postgres -docker compose --profile history up -d -``` - -Then open **/adsb/history** for the reporting dashboard. +> **Note:** Docker requires privileged mode for USB SDR access. See `docker-compose.yml` for configuration options. + +### ADS-B History (Optional) + +The ADS-B history feature persists aircraft messages to Postgres for long-term analysis. + +```bash +# Start with ADS-B history and Postgres +docker compose --profile history up -d +``` + +Set the following environment variables (for example in a `.env` file): + +```bash +INTERCEPT_ADSB_HISTORY_ENABLED=true +INTERCEPT_ADSB_DB_HOST=adsb_db +INTERCEPT_ADSB_DB_PORT=5432 +INTERCEPT_ADSB_DB_NAME=intercept_adsb +INTERCEPT_ADSB_DB_USER=intercept +INTERCEPT_ADSB_DB_PASSWORD=intercept +``` + +### Other ADS-B Settings + +Set these as environment variables for either local installs or Docker: + +| Variable | Default | Description | +|----------|---------|-------------| +| `INTERCEPT_ADSB_AUTO_START` | `false` | Auto-start ADS-B tracking when the dashboard loads | +| `INTERCEPT_SHARED_OBSERVER_LOCATION` | `true` | Share observer location across ADS-B/AIS/SSTV/Satellite modules | + +**Local install example** + +```bash +INTERCEPT_ADSB_AUTO_START=true \ +INTERCEPT_SHARED_OBSERVER_LOCATION=false \ +python app.py +``` + +**Docker example (.env)** + +```bash +INTERCEPT_ADSB_AUTO_START=true +INTERCEPT_SHARED_OBSERVER_LOCATION=false +``` + +To store Postgres data on external storage, set `PGDATA_PATH` (defaults to `./pgdata`): + +```bash +PGDATA_PATH=/mnt/usbpi1/intercept/pgdata +``` + +Then open **/adsb/history** for the reporting dashboard. ### Open the Interface diff --git a/app.py b/app.py index 3d8e113..af8dd8a 100644 --- a/app.py +++ b/app.py @@ -27,7 +27,7 @@ from typing import Any from flask import Flask, render_template, jsonify, send_file, Response, request,redirect, url_for, flash, session from werkzeug.security import check_password_hash -from config import VERSION, CHANGELOG +from config import VERSION, CHANGELOG, SHARED_OBSERVER_LOCATION_ENABLED from utils.dependencies import check_tool, check_all_dependencies, TOOL_DEPENDENCIES from utils.process import cleanup_stale_processes from utils.sdr import SDRFactory @@ -271,15 +271,22 @@ def login(): return render_template('login.html', version=VERSION) @app.route('/') -def index() -> str: - tools = { - 'rtl_fm': check_tool('rtl_fm'), - 'multimon': check_tool('multimon-ng'), - 'rtl_433': check_tool('rtl_433'), - 'rtlamr': check_tool('rtlamr') - } - devices = [d.to_dict() for d in SDRFactory.detect_devices()] - return render_template('index.html', tools=tools, devices=devices, version=VERSION, changelog=CHANGELOG) +def index() -> str: + tools = { + 'rtl_fm': check_tool('rtl_fm'), + 'multimon': check_tool('multimon-ng'), + 'rtl_433': check_tool('rtl_433'), + 'rtlamr': check_tool('rtlamr') + } + devices = [d.to_dict() for d in SDRFactory.detect_devices()] + return render_template( + 'index.html', + tools=tools, + devices=devices, + version=VERSION, + changelog=CHANGELOG, + shared_observer_location=SHARED_OBSERVER_LOCATION_ENABLED, + ) @app.route('/favicon.svg') diff --git a/config.py b/config.py index 21e54fe..2250f1d 100644 --- a/config.py +++ b/config.py @@ -136,23 +136,27 @@ AIRODUMP_HEADER_LINES = _get_env_int('AIRODUMP_HEADER_LINES', 2) BT_SCAN_TIMEOUT = _get_env_int('BT_SCAN_TIMEOUT', 10) BT_UPDATE_INTERVAL = _get_env_float('BT_UPDATE_INTERVAL', 2.0) -# ADS-B settings -ADSB_SBS_PORT = _get_env_int('ADSB_SBS_PORT', 30003) -ADSB_UPDATE_INTERVAL = _get_env_float('ADSB_UPDATE_INTERVAL', 1.0) -ADSB_HISTORY_ENABLED = _get_env_bool('ADSB_HISTORY_ENABLED', False) +# ADS-B settings +ADSB_SBS_PORT = _get_env_int('ADSB_SBS_PORT', 30003) +ADSB_UPDATE_INTERVAL = _get_env_float('ADSB_UPDATE_INTERVAL', 1.0) +ADSB_AUTO_START = _get_env_bool('ADSB_AUTO_START', False) +ADSB_HISTORY_ENABLED = _get_env_bool('ADSB_HISTORY_ENABLED', False) ADSB_DB_HOST = _get_env('ADSB_DB_HOST', 'localhost') ADSB_DB_PORT = _get_env_int('ADSB_DB_PORT', 5432) ADSB_DB_NAME = _get_env('ADSB_DB_NAME', 'intercept_adsb') ADSB_DB_USER = _get_env('ADSB_DB_USER', 'intercept') ADSB_DB_PASSWORD = _get_env('ADSB_DB_PASSWORD', 'intercept') -ADSB_HISTORY_BATCH_SIZE = _get_env_int('ADSB_HISTORY_BATCH_SIZE', 500) -ADSB_HISTORY_FLUSH_INTERVAL = _get_env_float('ADSB_HISTORY_FLUSH_INTERVAL', 1.0) -ADSB_HISTORY_QUEUE_SIZE = _get_env_int('ADSB_HISTORY_QUEUE_SIZE', 50000) - -# Satellite settings -SATELLITE_UPDATE_INTERVAL = _get_env_int('SATELLITE_UPDATE_INTERVAL', 30) -SATELLITE_TRAJECTORY_POINTS = _get_env_int('SATELLITE_TRAJECTORY_POINTS', 30) -SATELLITE_ORBIT_MINUTES = _get_env_int('SATELLITE_ORBIT_MINUTES', 45) +ADSB_HISTORY_BATCH_SIZE = _get_env_int('ADSB_HISTORY_BATCH_SIZE', 500) +ADSB_HISTORY_FLUSH_INTERVAL = _get_env_float('ADSB_HISTORY_FLUSH_INTERVAL', 1.0) +ADSB_HISTORY_QUEUE_SIZE = _get_env_int('ADSB_HISTORY_QUEUE_SIZE', 50000) + +# Observer location settings +SHARED_OBSERVER_LOCATION_ENABLED = _get_env_bool('SHARED_OBSERVER_LOCATION', True) + +# Satellite settings +SATELLITE_UPDATE_INTERVAL = _get_env_int('SATELLITE_UPDATE_INTERVAL', 30) +SATELLITE_TRAJECTORY_POINTS = _get_env_int('SATELLITE_TRAJECTORY_POINTS', 30) +SATELLITE_ORBIT_MINUTES = _get_env_int('SATELLITE_ORBIT_MINUTES', 45) # Update checking GITHUB_REPO = _get_env('GITHUB_REPO', 'smittix/intercept') diff --git a/docker-compose.yml b/docker-compose.yml index 303a29b..234eb3e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -36,6 +36,10 @@ services: # - INTERCEPT_ADSB_DB_NAME=intercept_adsb # - INTERCEPT_ADSB_DB_USER=intercept # - INTERCEPT_ADSB_DB_PASSWORD=intercept + # ADS-B auto-start on dashboard load (default false) + - INTERCEPT_ADSB_AUTO_START=${INTERCEPT_ADSB_AUTO_START:-false} + # Shared observer location across modules + - INTERCEPT_SHARED_OBSERVER_LOCATION=${INTERCEPT_SHARED_OBSERVER_LOCATION:-true} # Network mode for WiFi scanning (requires host network) # network_mode: host restart: unless-stopped @@ -68,6 +72,10 @@ services: - INTERCEPT_ADSB_DB_NAME=intercept_adsb - INTERCEPT_ADSB_DB_USER=intercept - INTERCEPT_ADSB_DB_PASSWORD=intercept + # ADS-B auto-start on dashboard load (default false) + - INTERCEPT_ADSB_AUTO_START=${INTERCEPT_ADSB_AUTO_START:-false} + # Shared observer location across modules + - INTERCEPT_SHARED_OBSERVER_LOCATION=${INTERCEPT_SHARED_OBSERVER_LOCATION:-true} restart: unless-stopped healthcheck: test: ["CMD", "curl", "-sf", "http://localhost:5050/health"] diff --git a/docs/USAGE.md b/docs/USAGE.md index eff954e..9c7aa11 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -61,55 +61,88 @@ INTERCEPT automatically detects known trackers: 1. **Select Hardware** - Choose your SDR type (RTL-SDR uses dump1090, others use readsb) 2. **Check Tools** - Ensure dump1090 or readsb is installed -3. **Set Location** - Choose location source: - - **Manual Entry** - Type coordinates directly - - **Browser GPS** - Use browser's built-in geolocation (requires HTTPS) - - **USB GPS Dongle** - Connect a USB GPS receiver for continuous updates -4. **Start Tracking** - Click "Start Tracking" to begin ADS-B reception -5. **View Map** - Aircraft appear on the interactive Leaflet map +3. **Set Location** - Choose location source: + - **Manual Entry** - Type coordinates directly + - **Browser GPS** - Use browser's built-in geolocation (requires HTTPS) + - **USB GPS Dongle** - Connect a USB GPS receiver for continuous updates + - **Shared Location** - By default, the observer location is shared across modules + (disable with `INTERCEPT_SHARED_OBSERVER_LOCATION=false`) +4. **Start Tracking** - Click "Start Tracking" to begin ADS-B reception +5. **View Map** - Aircraft appear on the interactive Leaflet map 6. **Click Aircraft** - Click markers for detailed information 7. **Display Options** - Toggle callsigns, altitude, trails, range rings, clustering 8. **Filter Aircraft** - Use dropdown to show all, military, civil, or emergency only -9. **Full Dashboard** - Click "Full Screen Dashboard" for dedicated radar view +9. **Full Dashboard** - Click "Full Screen Dashboard" for dedicated radar view + +> Note: ADS-B auto-start is disabled by default. To enable auto-start on dashboard load, +> set `INTERCEPT_ADSB_AUTO_START=true`. ### Emergency Squawks -The system highlights aircraft transmitting emergency squawks: -- **7500** - Hijack -- **7600** - Radio failure -- **7700** - General emergency - -## ADS-B History (Optional) - -The history dashboard persists aircraft messages and per-aircraft snapshots to Postgres for long-running tracking and reporting. - -### Enable History - -Set the following environment variables (Docker recommended): - -| Variable | Default | Description | -|----------|---------|-------------| -| `INTERCEPT_ADSB_HISTORY_ENABLED` | `false` | Enables history storage and reporting | -| `INTERCEPT_ADSB_DB_HOST` | `localhost` | Postgres host (use `adsb_db` in Docker) | -| `INTERCEPT_ADSB_DB_PORT` | `5432` | Postgres port | -| `INTERCEPT_ADSB_DB_NAME` | `intercept_adsb` | Database name | -| `INTERCEPT_ADSB_DB_USER` | `intercept` | Database user | -| `INTERCEPT_ADSB_DB_PASSWORD` | `intercept` | Database password | - -### Docker Setup - -`docker-compose.yml` includes an `adsb_db` service and a persistent volume for history storage: - -```bash -docker compose up -d -``` - -### Using the History Dashboard - -1. Open **/adsb/history** -2. Use **Start Tracking** to run ADS-B in headless mode -3. View aircraft history and timelines -4. Stop tracking when desired (session history is recorded) +The system highlights aircraft transmitting emergency squawks: +- **7500** - Hijack +- **7600** - Radio failure +- **7700** - General emergency + +## ADS-B History (Optional) + +The history dashboard persists aircraft messages and per-aircraft snapshots to Postgres for long-running tracking and reporting. + +### Enable History + +Set the following environment variables (Docker recommended): + +| Variable | Default | Description | +|----------|---------|-------------| +| `INTERCEPT_ADSB_HISTORY_ENABLED` | `false` | Enables history storage and reporting | +| `INTERCEPT_ADSB_DB_HOST` | `localhost` | Postgres host (use `adsb_db` in Docker) | +| `INTERCEPT_ADSB_DB_PORT` | `5432` | Postgres port | +| `INTERCEPT_ADSB_DB_NAME` | `intercept_adsb` | Database name | +| `INTERCEPT_ADSB_DB_USER` | `intercept` | Database user | +| `INTERCEPT_ADSB_DB_PASSWORD` | `intercept` | Database password | + +### Other ADS-B Settings + +| Variable | Default | Description | +|----------|---------|-------------| +| `INTERCEPT_ADSB_AUTO_START` | `false` | Auto-start ADS-B tracking when the dashboard loads | +| `INTERCEPT_SHARED_OBSERVER_LOCATION` | `true` | Share observer location across ADS-B/AIS/SSTV/Satellite modules | + +**Local install example** + +```bash +INTERCEPT_ADSB_AUTO_START=true \ +INTERCEPT_SHARED_OBSERVER_LOCATION=false \ +python app.py +``` + +**Docker example (.env)** + +```bash +INTERCEPT_ADSB_AUTO_START=true +INTERCEPT_SHARED_OBSERVER_LOCATION=false +``` + +### Docker Setup + +`docker-compose.yml` includes an `adsb_db` service and a persistent volume for history storage: + +```bash +docker compose --profile history up -d +``` + +To store Postgres data on external storage, set `PGDATA_PATH` (defaults to `./pgdata`): + +```bash +PGDATA_PATH=/mnt/usbpi1/intercept/pgdata +``` + +### Using the History Dashboard + +1. Open **/adsb/history** +2. Use **Start Tracking** to run ADS-B in headless mode +3. View aircraft history and timelines +4. Stop tracking when desired (session history is recorded) ## Satellite Mode diff --git a/routes/adsb.py b/routes/adsb.py index 1338e97..7bd8e1b 100644 --- a/routes/adsb.py +++ b/routes/adsb.py @@ -33,7 +33,9 @@ from config import ( ADSB_DB_PASSWORD, ADSB_DB_PORT, ADSB_DB_USER, + ADSB_AUTO_START, ADSB_HISTORY_ENABLED, + SHARED_OBSERVER_LOCATION_ENABLED, ) from utils.logging import adsb_logger as logger from utils.validation import ( @@ -812,7 +814,11 @@ def stream_adsb(): @adsb_bp.route('/dashboard') def adsb_dashboard(): """Popout ADS-B dashboard.""" - return render_template('adsb_dashboard.html') + return render_template( + 'adsb_dashboard.html', + shared_observer_location=SHARED_OBSERVER_LOCATION_ENABLED, + adsb_auto_start=ADSB_AUTO_START, + ) @adsb_bp.route('/history') diff --git a/routes/ais.py b/routes/ais.py index 36cae5e..5edfe18 100644 --- a/routes/ais.py +++ b/routes/ais.py @@ -15,6 +15,7 @@ from typing import Generator from flask import Blueprint, jsonify, request, Response, render_template import app as app_module +from config import SHARED_OBSERVER_LOCATION_ENABLED from utils.logging import get_logger from utils.validation import validate_device_index, validate_gain from utils.sse import format_sse @@ -480,4 +481,7 @@ def stream_ais(): @ais_bp.route('/dashboard') def ais_dashboard(): """Popout AIS dashboard.""" - return render_template('ais_dashboard.html') + return render_template( + 'ais_dashboard.html', + shared_observer_location=SHARED_OBSERVER_LOCATION_ENABLED, + ) diff --git a/routes/satellite.py b/routes/satellite.py index 3b597af..b3a7e98 100644 --- a/routes/satellite.py +++ b/routes/satellite.py @@ -11,7 +11,9 @@ from urllib.parse import urlparse import requests -from flask import Blueprint, jsonify, request, render_template, Response +from flask import Blueprint, jsonify, request, render_template, Response + +from config import SHARED_OBSERVER_LOCATION_ENABLED from data.satellites import TLE_SATELLITES from utils.logging import satellite_logger as logger @@ -118,9 +120,12 @@ def _fetch_iss_realtime(observer_lat: Optional[float] = None, observer_lon: Opti @satellite_bp.route('/dashboard') -def satellite_dashboard(): - """Popout satellite tracking dashboard.""" - return render_template('satellite_dashboard.html') +def satellite_dashboard(): + """Popout satellite tracking dashboard.""" + return render_template( + 'satellite_dashboard.html', + shared_observer_location=SHARED_OBSERVER_LOCATION_ENABLED, + ) @satellite_bp.route('/predict', methods=['POST']) diff --git a/static/js/core/app.js b/static/js/core/app.js index 7db3272..4cca0bd 100644 --- a/static/js/core/app.js +++ b/static/js/core/app.js @@ -31,16 +31,19 @@ let autoScroll = localStorage.getItem('autoScroll') !== 'false'; let muted = localStorage.getItem('audioMuted') === 'true'; // Observer location (load from localStorage or default to London) -let observerLocation = (function() { - const saved = localStorage.getItem('observerLocation'); - if (saved) { - try { - const parsed = JSON.parse(saved); - if (parsed.lat && parsed.lon) return parsed; - } catch (e) {} - } - return { lat: 51.5074, lon: -0.1278 }; -})(); +let observerLocation = (function() { + if (window.ObserverLocation && ObserverLocation.getForModule) { + return ObserverLocation.getForModule('observerLocation'); + } + const saved = localStorage.getItem('observerLocation'); + if (saved) { + try { + const parsed = JSON.parse(saved); + if (parsed.lat && parsed.lon) return parsed; + } catch (e) {} + } + return { lat: 51.5074, lon: -0.1278 }; +})(); // Message storage for export let allMessages = []; diff --git a/static/js/core/observer-location.js b/static/js/core/observer-location.js new file mode 100644 index 0000000..018c2b4 --- /dev/null +++ b/static/js/core/observer-location.js @@ -0,0 +1,103 @@ +// Shared observer location helper for map-based modules. +// Default: shared location enabled unless explicitly disabled via config. +window.ObserverLocation = (function() { + const DEFAULT_LOCATION = { lat: 51.5074, lon: -0.1278 }; + const SHARED_KEY = 'observerLocation'; + const AIS_KEY = 'ais_observerLocation'; + const LEGACY_LAT_KEY = 'observerLat'; + const LEGACY_LON_KEY = 'observerLon'; + + function isSharedEnabled() { + return window.INTERCEPT_SHARED_OBSERVER_LOCATION !== false; + } + + function normalize(lat, lon) { + const latNum = parseFloat(lat); + const lonNum = parseFloat(lon); + if (Number.isNaN(latNum) || Number.isNaN(lonNum)) return null; + if (latNum < -90 || latNum > 90 || lonNum < -180 || lonNum > 180) return null; + return { lat: latNum, lon: lonNum }; + } + + function parseLocation(raw) { + if (!raw) return null; + try { + const parsed = JSON.parse(raw); + if (parsed && parsed.lat !== undefined && parsed.lon !== undefined) { + return normalize(parsed.lat, parsed.lon); + } + } catch (e) {} + return null; + } + + function readKey(key) { + return parseLocation(localStorage.getItem(key)); + } + + function readLegacyLatLon() { + const lat = localStorage.getItem(LEGACY_LAT_KEY); + const lon = localStorage.getItem(LEGACY_LON_KEY); + if (!lat || !lon) return null; + return normalize(lat, lon); + } + + function getShared() { + const current = readKey(SHARED_KEY); + if (current) return current; + + const legacy = readKey(AIS_KEY) || readLegacyLatLon(); + if (legacy) { + setShared(legacy); + return legacy; + } + return { ...DEFAULT_LOCATION }; + } + + function setShared(location, options = {}) { + if (!location) return; + localStorage.setItem(SHARED_KEY, JSON.stringify(location)); + if (options.updateLegacy !== false) { + localStorage.setItem(LEGACY_LAT_KEY, location.lat.toString()); + localStorage.setItem(LEGACY_LON_KEY, location.lon.toString()); + } + } + + function getForModule(moduleKey, options = {}) { + if (isSharedEnabled()) { + return getShared(); + } + if (moduleKey) { + const moduleLocation = readKey(moduleKey); + if (moduleLocation) return moduleLocation; + } + if (options.fallbackToLatLon) { + const legacy = readLegacyLatLon(); + if (legacy) return legacy; + } + return { ...DEFAULT_LOCATION }; + } + + function setForModule(moduleKey, location, options = {}) { + if (!location) return; + if (isSharedEnabled()) { + setShared(location, options); + return; + } + if (moduleKey) { + localStorage.setItem(moduleKey, JSON.stringify(location)); + } else if (options.fallbackToLatLon) { + localStorage.setItem(LEGACY_LAT_KEY, location.lat.toString()); + localStorage.setItem(LEGACY_LON_KEY, location.lon.toString()); + } + } + + return { + isSharedEnabled, + getShared, + setShared, + getForModule, + setForModule, + normalize, + DEFAULT_LOCATION: { ...DEFAULT_LOCATION } + }; +})(); diff --git a/static/js/core/settings-manager.js b/static/js/core/settings-manager.js index d1de449..3c44701 100644 --- a/static/js/core/settings-manager.js +++ b/static/js/core/settings-manager.js @@ -547,9 +547,14 @@ document.addEventListener('DOMContentLoaded', () => { /** * Load and display current observer location */ -function loadObserverLocation() { - const lat = localStorage.getItem('observerLat'); - const lon = localStorage.getItem('observerLon'); +function loadObserverLocation() { + let lat = localStorage.getItem('observerLat'); + let lon = localStorage.getItem('observerLon'); + if (window.ObserverLocation && ObserverLocation.isSharedEnabled()) { + const shared = ObserverLocation.getShared(); + lat = shared.lat.toString(); + lon = shared.lon.toString(); + } const latInput = document.getElementById('observerLatInput'); const lonInput = document.getElementById('observerLonInput'); @@ -633,9 +638,9 @@ function detectLocationGPS(btn) { /** * Save observer location to localStorage */ -function saveObserverLocation() { - const latInput = document.getElementById('observerLatInput'); - const lonInput = document.getElementById('observerLonInput'); +function saveObserverLocation() { + const latInput = document.getElementById('observerLatInput'); + const lonInput = document.getElementById('observerLonInput'); const lat = parseFloat(latInput?.value); const lon = parseFloat(lonInput?.value); @@ -658,8 +663,12 @@ function saveObserverLocation() { return; } - localStorage.setItem('observerLat', lat.toString()); - localStorage.setItem('observerLon', lon.toString()); + if (window.ObserverLocation && ObserverLocation.isSharedEnabled()) { + ObserverLocation.setShared({ lat, lon }); + } else { + localStorage.setItem('observerLat', lat.toString()); + localStorage.setItem('observerLon', lon.toString()); + } // Also update dashboard-specific location keys for ADS-B and AIS const locationObj = JSON.stringify({ lat: lat, lon: lon }); @@ -669,12 +678,17 @@ function saveObserverLocation() { // Update display const currentLatDisplay = document.getElementById('currentLatDisplay'); const currentLonDisplay = document.getElementById('currentLonDisplay'); - if (currentLatDisplay) currentLatDisplay.textContent = lat.toFixed(4) + '°'; - if (currentLonDisplay) currentLonDisplay.textContent = lon.toFixed(4) + '°'; - - if (typeof showNotification === 'function') { - showNotification('Location', 'Observer location saved'); - } + if (currentLatDisplay) currentLatDisplay.textContent = lat.toFixed(4) + '°'; + if (currentLonDisplay) currentLonDisplay.textContent = lon.toFixed(4) + '°'; + + if (typeof showNotification === 'function') { + showNotification('Location', 'Observer location saved'); + } + + if (window.observerLocation) { + window.observerLocation.lat = lat; + window.observerLocation.lon = lon; + } // Refresh SSTV ISS schedule if available if (typeof SSTV !== 'undefined' && typeof SSTV.loadIssSchedule === 'function') { diff --git a/static/js/modes/sstv.js b/static/js/modes/sstv.js index cdcb3e8..617f626 100644 --- a/static/js/modes/sstv.js +++ b/static/js/modes/sstv.js @@ -37,15 +37,20 @@ const SSTV = (function() { /** * Load location into input fields */ - function loadLocationInputs() { - const latInput = document.getElementById('sstvObsLat'); - const lonInput = document.getElementById('sstvObsLon'); - - const storedLat = localStorage.getItem('observerLat'); - const storedLon = localStorage.getItem('observerLon'); - - if (latInput && storedLat) latInput.value = storedLat; - if (lonInput && storedLon) lonInput.value = storedLon; + function loadLocationInputs() { + const latInput = document.getElementById('sstvObsLat'); + const lonInput = document.getElementById('sstvObsLon'); + + let storedLat = localStorage.getItem('observerLat'); + let storedLon = localStorage.getItem('observerLon'); + if (window.ObserverLocation && ObserverLocation.isSharedEnabled()) { + const shared = ObserverLocation.getShared(); + storedLat = shared.lat.toString(); + storedLon = shared.lon.toString(); + } + + if (latInput && storedLat) latInput.value = storedLat; + if (lonInput && storedLon) lonInput.value = storedLon; // Add change handlers to save and refresh if (latInput) latInput.addEventListener('change', saveLocationFromInputs); @@ -55,19 +60,23 @@ const SSTV = (function() { /** * Save location from input fields */ - function saveLocationFromInputs() { - const latInput = document.getElementById('sstvObsLat'); - const lonInput = document.getElementById('sstvObsLon'); + function saveLocationFromInputs() { + const latInput = document.getElementById('sstvObsLat'); + const lonInput = document.getElementById('sstvObsLon'); const lat = parseFloat(latInput?.value); const lon = parseFloat(lonInput?.value); - if (!isNaN(lat) && lat >= -90 && lat <= 90 && - !isNaN(lon) && lon >= -180 && lon <= 180) { - localStorage.setItem('observerLat', lat.toString()); - localStorage.setItem('observerLon', lon.toString()); - loadIssSchedule(); // Refresh pass predictions - } + if (!isNaN(lat) && lat >= -90 && lat <= 90 && + !isNaN(lon) && lon >= -180 && lon <= 180) { + if (window.ObserverLocation && ObserverLocation.isSharedEnabled()) { + ObserverLocation.setShared({ lat, lon }); + } else { + localStorage.setItem('observerLat', lat.toString()); + localStorage.setItem('observerLon', lon.toString()); + } + loadIssSchedule(); // Refresh pass predictions + } } /** @@ -94,8 +103,12 @@ const SSTV = (function() { if (latInput) latInput.value = lat; if (lonInput) lonInput.value = lon; - localStorage.setItem('observerLat', lat); - localStorage.setItem('observerLon', lon); + if (window.ObserverLocation && ObserverLocation.isSharedEnabled()) { + ObserverLocation.setShared({ lat: parseFloat(lat), lon: parseFloat(lon) }); + } else { + localStorage.setItem('observerLat', lat); + localStorage.setItem('observerLon', lon); + } btn.innerHTML = originalText; btn.disabled = false; diff --git a/templates/adsb_dashboard.html b/templates/adsb_dashboard.html index 2cd93bb..725e3ca 100644 --- a/templates/adsb_dashboard.html +++ b/templates/adsb_dashboard.html @@ -17,10 +17,15 @@ {% else %} - {% endif %} - - - + {% endif %} + + + + +
@@ -517,16 +522,19 @@ } // Observer location and range rings (load from localStorage or default to London) - let observerLocation = (function() { - const saved = localStorage.getItem('observerLocation'); - if (saved) { - try { - const parsed = JSON.parse(saved); - if (parsed.lat && parsed.lon) return parsed; - } catch (e) {} - } - return { lat: 51.5074, lon: -0.1278 }; - })(); + let observerLocation = (function() { + if (window.ObserverLocation && ObserverLocation.getForModule) { + return ObserverLocation.getForModule('observerLocation'); + } + const saved = localStorage.getItem('observerLocation'); + if (saved) { + try { + const parsed = JSON.parse(saved); + if (parsed.lat && parsed.lon) return parsed; + } catch (e) {} + } + return { lat: 51.5074, lon: -0.1278 }; + })(); let rangeRingsLayer = null; let observerMarker = null; @@ -1802,8 +1810,12 @@ ACARS: ${r.statistics.acarsMessages} messages`; observerLocation.lat = lat; observerLocation.lon = lon; - // Save to localStorage for persistence - localStorage.setItem('observerLocation', JSON.stringify(observerLocation)); + // Save to localStorage for persistence + if (window.ObserverLocation) { + ObserverLocation.setForModule('observerLocation', observerLocation); + } else { + localStorage.setItem('observerLocation', JSON.stringify(observerLocation)); + } if (radarMap) { radarMap.setView([lat, lon], radarMap.getZoom()); @@ -1830,8 +1842,12 @@ ACARS: ${r.statistics.acarsMessages} messages`; observerLocation.lat = position.coords.latitude; observerLocation.lon = position.coords.longitude; - // Save to localStorage for persistence - localStorage.setItem('observerLocation', JSON.stringify(observerLocation)); + // Save to localStorage for persistence + if (window.ObserverLocation) { + ObserverLocation.setForModule('observerLocation', observerLocation); + } else { + localStorage.setItem('observerLocation', JSON.stringify(observerLocation)); + } document.getElementById('obsLat').value = observerLocation.lat.toFixed(4); document.getElementById('obsLon').value = observerLocation.lon.toFixed(4); @@ -1920,14 +1936,17 @@ ACARS: ${r.statistics.acarsMessages} messages`; } }); - function updateLocationFromGps(position) { - observerLocation.lat = position.latitude; - observerLocation.lon = position.longitude; - document.getElementById('obsLat').value = position.latitude.toFixed(4); - document.getElementById('obsLon').value = position.longitude.toFixed(4); - - // Center map on GPS location (on first fix) - if (radarMap && !radarMap._gpsInitialized) { + function updateLocationFromGps(position) { + observerLocation.lat = position.latitude; + observerLocation.lon = position.longitude; + document.getElementById('obsLat').value = position.latitude.toFixed(4); + document.getElementById('obsLon').value = position.longitude.toFixed(4); + if (window.ObserverLocation && ObserverLocation.isSharedEnabled()) { + ObserverLocation.setShared({ lat: position.latitude, lon: position.longitude }); + } + + // Center map on GPS location (on first fix) + if (radarMap && !radarMap._gpsInitialized) { radarMap.setView([position.latitude, position.longitude], radarMap.getZoom()); radarMap._gpsInitialized = true; // Draw range rings immediately after centering @@ -2523,28 +2542,32 @@ sudo make install } } - async function syncTrackingStatus() { - // This function checks LOCAL tracking status on page load - // For local mode: auto-start if session is already running OR SDR is available - // For agent mode: don't auto-start (user controls agent tracking) - - const useAgent = typeof adsbCurrentAgent !== 'undefined' && adsbCurrentAgent !== 'local'; - if (useAgent) { - console.log('[ADS-B] Agent mode on page load - not auto-starting local'); - return; - } - - try { - const response = await fetch('/adsb/session'); - if (!response.ok) { - // No session info - try to auto-start if SDR available - console.log('[ADS-B] No session found, attempting auto-start...'); - await tryAutoStartLocal(); - return; - } - const data = await response.json(); - - if (data.tracking_active) { + async function syncTrackingStatus() { + // This function checks LOCAL tracking status on page load + // For local mode: auto-start if session is already running OR SDR is available + // For agent mode: don't auto-start (user controls agent tracking) + + const useAgent = typeof adsbCurrentAgent !== 'undefined' && adsbCurrentAgent !== 'local'; + if (useAgent) { + console.log('[ADS-B] Agent mode on page load - not auto-starting local'); + return; + } + + try { + const response = await fetch('/adsb/session'); + if (!response.ok) { + // No session info - only auto-start if enabled + if (window.INTERCEPT_ADSB_AUTO_START) { + console.log('[ADS-B] No session found, attempting auto-start...'); + await tryAutoStartLocal(); + } else { + console.log('[ADS-B] No session found; auto-start disabled'); + } + return; + } + const data = await response.json(); + + if (data.tracking_active) { // Session is running - auto-connect to stream console.log('[ADS-B] Local session already active - auto-connecting to stream'); @@ -2580,18 +2603,24 @@ sudo make install document.getElementById('trackingDot').classList.add('active'); const statusEl = document.getElementById('trackingStatus'); statusEl.textContent = 'TRACKING'; - } else { - // Session not active - try to auto-start - console.log('[ADS-B] No active session, attempting auto-start...'); - await tryAutoStartLocal(); - } - - } catch (err) { - console.warn('[ADS-B] Failed to sync tracking status:', err); - // Try auto-start anyway - await tryAutoStartLocal(); - } - } + } else { + // Session not active - only auto-start if enabled + if (window.INTERCEPT_ADSB_AUTO_START) { + console.log('[ADS-B] No active session, attempting auto-start...'); + await tryAutoStartLocal(); + } else { + console.log('[ADS-B] No active session; auto-start disabled'); + } + } + + } catch (err) { + console.warn('[ADS-B] Failed to sync tracking status:', err); + // Try auto-start only if enabled + if (window.INTERCEPT_ADSB_AUTO_START) { + await tryAutoStartLocal(); + } + } + } async function tryAutoStartLocal() { // Try to auto-start local ADS-B tracking if SDR is available diff --git a/templates/ais_dashboard.html b/templates/ais_dashboard.html index e964f46..656099b 100644 --- a/templates/ais_dashboard.html +++ b/templates/ais_dashboard.html @@ -20,6 +20,10 @@ {% endif %} + + @@ -219,7 +223,12 @@ const MAX_TRAIL_POINTS = 50; // Observer location - let observerLocation = { lat: 51.5074, lon: -0.1278 }; + let observerLocation = (function() { + if (window.ObserverLocation && ObserverLocation.getForModule) { + return ObserverLocation.getForModule('ais_observerLocation'); + } + return { lat: 51.5074, lon: -0.1278 }; + })(); let rangeRingsLayer = null; let observerMarker = null; @@ -376,17 +385,9 @@ // Initialize map function initMap() { - // Load saved observer location - const saved = localStorage.getItem('ais_observerLocation'); - if (saved) { - try { - const parsed = JSON.parse(saved); - if (parsed.lat && parsed.lon) { - observerLocation = parsed; - document.getElementById('obsLat').value = parsed.lat; - document.getElementById('obsLon').value = parsed.lon; - } - } catch (e) {} + if (observerLocation) { + document.getElementById('obsLat').value = observerLocation.lat; + document.getElementById('obsLon').value = observerLocation.lon; } vesselMap = L.map('vesselMap', { @@ -470,7 +471,11 @@ const lon = parseFloat(document.getElementById('obsLon').value); if (!isNaN(lat) && !isNaN(lon) && lat >= -90 && lat <= 90 && lon >= -180 && lon <= 180) { observerLocation = { lat, lon }; - localStorage.setItem('ais_observerLocation', JSON.stringify(observerLocation)); + if (window.ObserverLocation) { + ObserverLocation.setForModule('ais_observerLocation', observerLocation); + } else { + localStorage.setItem('ais_observerLocation', JSON.stringify(observerLocation)); + } if (observerMarker) { observerMarker.setLatLng([lat, lon]); } @@ -1058,7 +1063,11 @@ drawRangeRings(); // Save to localStorage - localStorage.setItem('ais_observerLocation', JSON.stringify(observerLocation)); + if (window.ObserverLocation) { + ObserverLocation.setForModule('ais_observerLocation', observerLocation); + } else { + localStorage.setItem('ais_observerLocation', JSON.stringify(observerLocation)); + } } function showGpsIndicator(show) { diff --git a/templates/index.html b/templates/index.html index 46cddb0..13700d2 100644 --- a/templates/index.html +++ b/templates/index.html @@ -14,6 +14,10 @@ window._showDisclaimerOnLoad = true; } + + {% if offline_settings.fonts_source == 'local' %} @@ -2252,6 +2256,9 @@ // Observer location for distance calculations (load from localStorage or default to London) let observerLocation = (function () { + if (window.ObserverLocation && ObserverLocation.getForModule) { + return ObserverLocation.getForModule('observerLocation'); + } const saved = localStorage.getItem('observerLocation'); if (saved) { try { @@ -8375,8 +8382,15 @@ if (navigator.geolocation) { navigator.geolocation.getCurrentPosition( position => { - document.getElementById('obsLat').value = position.coords.latitude.toFixed(4); - document.getElementById('obsLon').value = position.coords.longitude.toFixed(4); + const lat = position.coords.latitude; + const lon = position.coords.longitude; + document.getElementById('obsLat').value = lat.toFixed(4); + document.getElementById('obsLon').value = lon.toFixed(4); + observerLocation.lat = lat; + observerLocation.lon = lon; + if (window.ObserverLocation && ObserverLocation.isSharedEnabled()) { + ObserverLocation.setShared({ lat, lon }); + } showInfo('Location updated!'); }, error => { @@ -8476,6 +8490,9 @@ // Update observerLocation observerLocation.lat = position.latitude; observerLocation.lon = position.longitude; + if (window.ObserverLocation && ObserverLocation.isSharedEnabled()) { + ObserverLocation.setShared({ lat: position.latitude, lon: position.longitude }); + } // Update APRS user location updateAprsUserLocation(position); diff --git a/templates/satellite_dashboard.html b/templates/satellite_dashboard.html index 74d56a9..cd05609 100644 --- a/templates/satellite_dashboard.html +++ b/templates/satellite_dashboard.html @@ -17,10 +17,14 @@ {% else %} - {% endif %} - - - + {% endif %} + + + + + @@ -313,16 +317,33 @@ } } - document.addEventListener('DOMContentLoaded', () => { - setupEmbeddedMode(); - initGroundMap(); - updateClock(); - setInterval(updateClock, 1000); - setInterval(updateCountdown, 1000); - setInterval(updateRealTimePositions, 5000); - loadAgents(); - getLocation(); - }); + function applySharedObserverLocation() { + if (window.ObserverLocation && ObserverLocation.isSharedEnabled()) { + const shared = ObserverLocation.getShared(); + if (shared) { + const latInput = document.getElementById('obsLat'); + const lonInput = document.getElementById('obsLon'); + if (latInput) latInput.value = shared.lat.toFixed(4); + if (lonInput) lonInput.value = shared.lon.toFixed(4); + return true; + } + } + return false; + } + + document.addEventListener('DOMContentLoaded', () => { + setupEmbeddedMode(); + const usedShared = applySharedObserverLocation(); + initGroundMap(); + updateClock(); + setInterval(updateClock, 1000); + setInterval(updateCountdown, 1000); + setInterval(updateRealTimePositions, 5000); + loadAgents(); + if (!usedShared) { + getLocation(); + } + }); async function loadAgents() { try { @@ -376,10 +397,13 @@ if (data.status === 'success' && data.result) { const agentStatus = data.result; - if (agentStatus.gps_position) { - const gps = agentStatus.gps_position; - document.getElementById('obsLat').value = gps.lat.toFixed(4); - document.getElementById('obsLon').value = gps.lon.toFixed(4); + if (agentStatus.gps_position) { + const gps = agentStatus.gps_position; + document.getElementById('obsLat').value = gps.lat.toFixed(4); + document.getElementById('obsLon').value = gps.lon.toFixed(4); + if (window.ObserverLocation && ObserverLocation.isSharedEnabled()) { + ObserverLocation.setShared({ lat: gps.lat, lon: gps.lon }); + } // Update observer marker label const agent = agents.find(a => a.id == agentId); @@ -407,39 +431,50 @@ now.toISOString().substring(11, 19) + ' UTC'; } - function initGroundMap() { - groundMap = L.map('groundMap', { - center: [20, 0], - zoom: 2, - minZoom: 1, - maxZoom: 10, - worldCopyJump: true - }); + function initGroundMap() { + groundMap = L.map('groundMap', { + center: [20, 0], + zoom: 2, + minZoom: 1, + maxZoom: 10, + worldCopyJump: true + }); // Use settings manager for tile layer (allows runtime changes) window.groundMap = groundMap; - if (typeof Settings !== 'undefined' && Settings.createTileLayer) { - Settings.createTileLayer().addTo(groundMap); - Settings.registerMap(groundMap); - } else { - L.tileLayer('https://cartodb-basemaps-{s}.global.ssl.fastly.net/dark_all/{z}/{x}/{y}.png', { - attribution: '© OSM © CARTO', - maxZoom: 19, - subdomains: 'abcd' - }).addTo(groundMap); - } - } + if (typeof Settings !== 'undefined' && Settings.createTileLayer) { + Settings.createTileLayer().addTo(groundMap); + Settings.registerMap(groundMap); + } else { + L.tileLayer('https://cartodb-basemaps-{s}.global.ssl.fastly.net/dark_all/{z}/{x}/{y}.png', { + attribution: '© OSM © CARTO', + maxZoom: 19, + subdomains: 'abcd' + }).addTo(groundMap); + } + + const lat = parseFloat(document.getElementById('obsLat')?.value); + const lon = parseFloat(document.getElementById('obsLon')?.value); + if (!Number.isNaN(lat) && !Number.isNaN(lon)) { + groundMap.setView([lat, lon], 3); + } + } - function getLocation() { - if (navigator.geolocation) { - navigator.geolocation.getCurrentPosition(pos => { - document.getElementById('obsLat').value = pos.coords.latitude.toFixed(4); - document.getElementById('obsLon').value = pos.coords.longitude.toFixed(4); - calculatePasses(); - }, () => { - calculatePasses(); - }); - } else { + function getLocation() { + if (navigator.geolocation) { + navigator.geolocation.getCurrentPosition(pos => { + const lat = pos.coords.latitude; + const lon = pos.coords.longitude; + document.getElementById('obsLat').value = lat.toFixed(4); + document.getElementById('obsLon').value = lon.toFixed(4); + if (window.ObserverLocation && ObserverLocation.isSharedEnabled()) { + ObserverLocation.setShared({ lat, lon }); + } + calculatePasses(); + }, () => { + calculatePasses(); + }); + } else { calculatePasses(); } }