Fix global timezone on ADS-B dashboard and harden VDL2 correlation

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) <noreply@anthropic.com>
This commit is contained in:
mitchross
2026-03-26 00:05:49 -04:00
parent b66ac935b7
commit f4672cf0c7
5 changed files with 48 additions and 27 deletions
+14 -9
View File
@@ -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()]
+10 -11
View File
@@ -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:
+9 -7
View File
@@ -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({
+11
View File
@@ -22,6 +22,7 @@
window.INTERCEPT_DEFAULT_LAT = {{ default_latitude | tojson }};
window.INTERCEPT_DEFAULT_LON = {{ default_longitude | tojson }};
</script>
<script src="{{ url_for('static', filename='js/core/utils.js') }}"></script>
<script defer src="{{ url_for('static', filename='vendor/leaflet/leaflet.js') }}"></script>
<script defer src="{{ url_for('static', filename='js/core/observer-location.js') }}"></script>
</head>
@@ -5730,6 +5731,16 @@ sudo make install</code>
<!-- Settings Modal -->
{% include 'partials/settings-modal.html' %}
<script>
// Initialize timezone/format dropdowns from saved preferences
(function() {
if (typeof InterceptTime === 'undefined') return;
var tzSel = document.getElementById('globalTimezoneSelect');
var fmtSel = document.getElementById('globalTimeFormatSelect');
if (tzSel) tzSel.value = InterceptTime.getTimezone();
if (fmtSel) fmtSel.value = InterceptTime.getHour12() ? '12' : '24';
})();
</script>
<!-- Help Modal -->
{% include 'partials/help-modal.html' %}
+4
View File
@@ -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);
}
}
})();
</script>