From f4672cf0c7ad7a86d317de9886a9a175af95f003 Mon Sep 17 00:00:00 2001 From: mitchross Date: Thu, 26 Mar 2026 00:05:49 -0400 Subject: [PATCH] Fix global timezone on ADS-B dashboard and harden VDL2 correlation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Timezone fixes: - Add utils.js (InterceptTime) to adsb_dashboard.html — was completely missing, causing all times to fall back to UTC regardless of setting - Register onChange listener in nav.html so clock updates instantly when timezone/format is changed in Settings - Initialize timezone/format dropdowns on ADS-B dashboard page load - Browser-verified: ET/12h ↔ UTC/24h switches instantly on ADS-B page VDL2 correlation fix: - Force ICAO hex to uppercase when promoting from VDL2 src.addr (dumpvdl2 may output lowercase, ADS-B stores uppercase — case mismatch prevented correlator from matching) - Move ICAO/addr promotion before ACARS field extraction so even non-ACARS VDL2 frames (XID, connection mgmt) get correlated Auth: - Add INTERCEPT_DISABLE_AUTH env var to skip login for local/dev use - Configurable via docker-compose.yml environment Co-Authored-By: Claude Opus 4.6 (1M context) --- app.py | 23 ++++++++++++++--------- docker-compose.yml | 21 ++++++++++----------- routes/vdl2.py | 16 +++++++++------- templates/adsb_dashboard.html | 11 +++++++++++ templates/partials/nav.html | 4 ++++ 5 files changed, 48 insertions(+), 27 deletions(-) diff --git a/app.py b/app.py index 8e06966..abffcee 100644 --- a/app.py +++ b/app.py @@ -423,6 +423,11 @@ def get_sdr_device_status() -> dict[str, str]: @app.before_request def require_login(): + # Skip auth entirely when INTERCEPT_DISABLE_AUTH is set + if os.environ.get('INTERCEPT_DISABLE_AUTH', '').lower() in ('1', 'true', 'yes'): + session['logged_in'] = True + return None + # Routes that don't require login (to avoid infinite redirect loop) allowed_routes = ['login', 'static', 'favicon', 'health', 'health_check'] @@ -478,15 +483,15 @@ def login(): return render_template('login.html', version=VERSION) -@app.route('/') -def index() -> str: - if request.args.get('mode') == 'satellite': - return redirect(url_for('satellite.satellite_dashboard')) - - tools = { - 'rtl_fm': check_tool('rtl_fm'), - 'multimon': check_tool('multimon-ng'), - 'rtl_433': check_tool('rtl_433'), +@app.route('/') +def index() -> str: + if request.args.get('mode') == 'satellite': + return redirect(url_for('satellite.satellite_dashboard')) + + 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()] diff --git a/docker-compose.yml b/docker-compose.yml index 12ce13d..f34ad7c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,18 +6,18 @@ # Basic usage (build locally): # docker compose --profile basic up -d --build # -# Basic usage (pre-built image from registry): -# INTERCEPT_IMAGE=ghcr.io/user/intercept:latest docker compose --profile basic up -d -# # With ADS-B history (Postgres): # docker compose --profile history up -d services: intercept: - # When INTERCEPT_IMAGE is set, use that pre-built image; otherwise build locally - image: ${INTERCEPT_IMAGE:-intercept:latest} + # Always build and use the local image + image: intercept:latest build: . + pull_policy: never container_name: intercept + profiles: + - basic ports: - "5050:5050" # Uncomment for HTTPS support (set INTERCEPT_HTTPS=true below) @@ -72,9 +72,10 @@ services: # ADS-B history with Postgres persistence # Enable with: docker compose --profile history up -d intercept-history: - # Same image/build fallback pattern as above - image: ${INTERCEPT_IMAGE:-intercept:latest} + # Always build and use the local image + image: intercept:latest build: . + pull_policy: never container_name: intercept-history profiles: - history @@ -112,6 +113,8 @@ services: - INTERCEPT_ADSB_AUTO_START=${INTERCEPT_ADSB_AUTO_START:-false} # Shared observer location across modules - INTERCEPT_SHARED_OBSERVER_LOCATION=${INTERCEPT_SHARED_OBSERVER_LOCATION:-true} + # Disable login auth (set to true for local/dev use) + - INTERCEPT_DISABLE_AUTH=${INTERCEPT_DISABLE_AUTH:-false} # Default observer coordinates (set to your location to skip the GPS prompt) # - INTERCEPT_DEFAULT_LAT=${INTERCEPT_DEFAULT_LAT:-0} # - INTERCEPT_DEFAULT_LON=${INTERCEPT_DEFAULT_LON:-0} @@ -142,7 +145,3 @@ services: interval: 10s timeout: 5s retries: 5 - -# Optional: Add volume for persistent SQLite database -# volumes: -# intercept-data: diff --git a/routes/vdl2.py b/routes/vdl2.py index e61de7e..9c7fc7b 100644 --- a/routes/vdl2.py +++ b/routes/vdl2.py @@ -90,6 +90,15 @@ def stream_vdl2_output(process: subprocess.Popen, is_text_mode: bool = False) -> avlc = vdl2_inner.get('avlc') or {} acars_payload = avlc.get('acars') or {} + # Promote AVLC source address — this is the aircraft ICAO hex + # Do this FIRST so even non-ACARS VDL2 frames can be correlated + src = avlc.get('src') or {} + src_addr = src.get('addr', '') + src_type = src.get('type', '') + if src_addr and src_type == 'Aircraft': + data['icao'] = src_addr.upper() + data['addr'] = src_addr.upper() + # Promote ACARS fields to top level so FlightCorrelator can match them if acars_payload.get('flight'): data['flight'] = acars_payload['flight'] @@ -101,13 +110,6 @@ def stream_vdl2_output(process: subprocess.Popen, is_text_mode: bool = False) -> if acars_payload.get('msg_text'): data['text'] = acars_payload['msg_text'] - # Promote AVLC source address (often ICAO hex for aircraft) - src_addr = (avlc.get('src') or {}).get('addr', '') - src_type = (avlc.get('src') or {}).get('type', '') - if src_addr and src_type == 'Aircraft': - data['icao'] = src_addr - data['addr'] = src_addr - # Enrich with translated ACARS label (consistent with ACARS route) if acars_payload.get('label'): translation = translate_message({ diff --git a/templates/adsb_dashboard.html b/templates/adsb_dashboard.html index 5dbafee..df50ef7 100644 --- a/templates/adsb_dashboard.html +++ b/templates/adsb_dashboard.html @@ -22,6 +22,7 @@ window.INTERCEPT_DEFAULT_LAT = {{ default_latitude | tojson }}; window.INTERCEPT_DEFAULT_LON = {{ default_longitude | tojson }}; + @@ -5730,6 +5731,16 @@ sudo make install {% include 'partials/settings-modal.html' %} + {% include 'partials/help-modal.html' %} diff --git a/templates/partials/nav.html b/templates/partials/nav.html index 8b78cbc..bf8ef19 100644 --- a/templates/partials/nav.html +++ b/templates/partials/nav.html @@ -551,6 +551,10 @@ } setInterval(updateNavUtcClock, 1000); updateNavUtcClock(); + // React immediately when timezone/format changes in Settings + if (typeof InterceptTime !== 'undefined' && InterceptTime.onChange) { + InterceptTime.onChange(updateNavUtcClock); + } } })();