This commit is contained in:
Mitch Ross
2026-02-06 13:29:45 -05:00
parent ff36687f53
commit 1683d98b90
5 changed files with 187 additions and 2 deletions
+3 -1
View File
@@ -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, SHARED_OBSERVER_LOCATION_ENABLED
from config import VERSION, CHANGELOG, SHARED_OBSERVER_LOCATION_ENABLED, DEFAULT_LATITUDE, DEFAULT_LONGITUDE
from utils.dependencies import check_tool, check_all_dependencies, TOOL_DEPENDENCIES
from utils.process import cleanup_stale_processes
from utils.sdr import SDRFactory
@@ -350,6 +350,8 @@ def index() -> str:
version=VERSION,
changelog=CHANGELOG,
shared_observer_location=SHARED_OBSERVER_LOCATION_ENABLED,
default_latitude=DEFAULT_LATITUDE,
default_longitude=DEFAULT_LONGITUDE,
)
+2
View File
@@ -185,6 +185,8 @@ 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)
DEFAULT_LATITUDE = _get_env_float('DEFAULT_LAT', 0.0)
DEFAULT_LONGITUDE = _get_env_float('DEFAULT_LON', 0.0)
# Satellite settings
SATELLITE_UPDATE_INTERVAL = _get_env_int('SATELLITE_UPDATE_INTERVAL', 30)
+6
View File
@@ -45,6 +45,9 @@ services:
- INTERCEPT_ADSB_AUTO_START=${INTERCEPT_ADSB_AUTO_START:-false}
# Shared observer location across modules
- INTERCEPT_SHARED_OBSERVER_LOCATION=${INTERCEPT_SHARED_OBSERVER_LOCATION:-true}
# 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}
# Network mode for WiFi scanning (requires host network)
# network_mode: host
restart: unless-stopped
@@ -88,6 +91,9 @@ services:
- INTERCEPT_ADSB_AUTO_START=${INTERCEPT_ADSB_AUTO_START:-false}
# Shared observer location across modules
- INTERCEPT_SHARED_OBSERVER_LOCATION=${INTERCEPT_SHARED_OBSERVER_LOCATION:-true}
# 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}
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost:5050/health"]
+8 -1
View File
@@ -1,7 +1,9 @@
// 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 DEFAULT_LOCATION = (window.INTERCEPT_DEFAULT_LAT && window.INTERCEPT_DEFAULT_LON)
? { lat: window.INTERCEPT_DEFAULT_LAT, lon: window.INTERCEPT_DEFAULT_LON }
: { lat: 51.5074, lon: -0.1278 };
const SHARED_KEY = 'observerLocation';
const AIS_KEY = 'ais_observerLocation';
const LEGACY_LAT_KEY = 'observerLat';
@@ -41,6 +43,10 @@ window.ObserverLocation = (function() {
return normalize(lat, lon);
}
function hasStoredLocation() {
return !!(readKey(SHARED_KEY) || readKey(AIS_KEY) || readLegacyLatLon());
}
function getShared() {
const current = readKey(SHARED_KEY);
if (current) return current;
@@ -93,6 +99,7 @@ window.ObserverLocation = (function() {
return {
isSharedEnabled,
hasStoredLocation,
getShared,
setShared,
getForModule,
+168
View File
@@ -21,6 +21,8 @@
</script>
<script>
window.INTERCEPT_SHARED_OBSERVER_LOCATION = {{ shared_observer_location | tojson }};
window.INTERCEPT_DEFAULT_LAT = {{ default_latitude | tojson }};
window.INTERCEPT_DEFAULT_LON = {{ default_longitude | tojson }};
</script>
<script src="{{ url_for('static', filename='js/core/observer-location.js') }}"></script>
<!-- Fonts - Conditional CDN/Local loading -->
@@ -14074,6 +14076,172 @@
<!-- Settings Modal -->
{% include 'partials/settings-modal.html' %}
<!-- Location Prompt (first-run) -->
<div id="locationPromptModal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.85); z-index:10001; backdrop-filter:blur(4px); align-items:center; justify-content:center;">
<div style="background:var(--bg-secondary); border:1px solid var(--accent-cyan); border-radius:10px; max-width:420px; width:90%; box-shadow:0 8px 32px rgba(0,0,0,0.6), 0 0 20px rgba(0,255,255,0.15); animation:locFadeIn 0.3s ease-out;">
<style>
@keyframes locFadeIn { from { transform:scale(0.95); opacity:0; } to { transform:scale(1); opacity:1; } }
@keyframes locSpin { to { transform:rotate(360deg); } }
</style>
<div style="padding:24px;">
<div style="text-align:center; margin-bottom:18px;">
<svg width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="var(--accent-cyan)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/>
</svg>
<h3 style="margin:10px 0 4px; color:var(--text-primary); font-size:16px;">Set Your Location</h3>
<p style="color:var(--text-dim); font-size:12px; margin:0; line-height:1.4;">
INTERCEPT needs your location for satellite pass predictions,<br>weather satellite scheduling, and distance calculations.
</p>
</div>
<div id="locPromptGpsSection" style="margin-bottom:16px;">
<button id="locPromptGpsBtn" onclick="locationPromptDetectGPS()" style="width:100%; padding:12px; background:var(--accent-cyan); color:#000; border:none; border-radius:6px; font-size:13px; font-weight:600; cursor:pointer; font-family:var(--font-mono); display:flex; align-items:center; justify-content:center; gap:8px;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="3"/><line x1="12" y1="2" x2="12" y2="6"/><line x1="12" y1="18" x2="12" y2="22"/><line x1="2" y1="12" x2="6" y2="12"/><line x1="18" y1="12" x2="22" y2="12"/>
</svg>
Auto-Detect (GPS)
</button>
<p id="locPromptGpsStatus" style="font-size:11px; color:var(--text-dim); text-align:center; margin:6px 0 0; min-height:16px;"></p>
</div>
<div style="display:flex; align-items:center; gap:10px; margin-bottom:14px;">
<div style="flex:1; height:1px; background:var(--border-color);"></div>
<span style="color:var(--text-dim); font-size:11px;">or enter manually</span>
<div style="flex:1; height:1px; background:var(--border-color);"></div>
</div>
<div style="display:flex; gap:10px; margin-bottom:16px;">
<div style="flex:1;">
<label style="display:block; font-size:11px; color:var(--text-dim); margin-bottom:4px;">Latitude</label>
<input type="number" id="locPromptLat" step="0.0001" min="-90" max="90" placeholder="51.5074" style="width:100%; padding:8px 10px; background:var(--bg-primary); border:1px solid var(--border-color); border-radius:4px; color:var(--text-primary); font-family:var(--font-mono); font-size:13px; box-sizing:border-box;">
</div>
<div style="flex:1;">
<label style="display:block; font-size:11px; color:var(--text-dim); margin-bottom:4px;">Longitude</label>
<input type="number" id="locPromptLon" step="0.0001" min="-180" max="180" placeholder="-0.1278" style="width:100%; padding:8px 10px; background:var(--bg-primary); border:1px solid var(--border-color); border-radius:4px; color:var(--text-primary); font-family:var(--font-mono); font-size:13px; box-sizing:border-box;">
</div>
</div>
<div style="display:flex; gap:10px;">
<button onclick="locationPromptSave()" style="flex:1; padding:10px; background:var(--accent-cyan); color:#000; border:none; border-radius:6px; font-size:12px; font-weight:600; cursor:pointer; font-family:var(--font-mono);">
Save Location
</button>
<button onclick="locationPromptSkip()" style="flex:1; padding:10px; background:transparent; color:var(--text-dim); border:1px solid var(--border-color); border-radius:6px; font-size:12px; cursor:pointer; font-family:var(--font-mono);">
Skip for Now
</button>
</div>
<p style="font-size:10px; color:var(--text-dim); text-align:center; margin:12px 0 0; opacity:0.7;">
Stored locally in your browser. Never sent to external servers.
</p>
</div>
</div>
</div>
<script>
(function() {
// Show location prompt on first load if no location has been set
function checkLocationPrompt() {
if (!window.ObserverLocation) return;
if (ObserverLocation.hasStoredLocation()) return;
// Don't show if disclaimer hasn't been accepted yet
if (localStorage.getItem('disclaimerAccepted') !== 'true') return;
// If server provided default lat/lon via env vars, auto-save without prompting
if (window.INTERCEPT_DEFAULT_LAT && window.INTERCEPT_DEFAULT_LON) {
ObserverLocation.setShared({
lat: window.INTERCEPT_DEFAULT_LAT,
lon: window.INTERCEPT_DEFAULT_LON
});
return;
}
// Don't show if user explicitly skipped
if (localStorage.getItem('locationPromptSkipped') === 'true') return;
var modal = document.getElementById('locationPromptModal');
if (modal) modal.style.display = 'flex';
}
// Run after a short delay so disclaimer can show first
document.addEventListener('DOMContentLoaded', function() {
setTimeout(checkLocationPrompt, 1500);
});
})();
function locationPromptDetectGPS() {
var btn = document.getElementById('locPromptGpsBtn');
var status = document.getElementById('locPromptGpsStatus');
if (!navigator.geolocation) {
status.textContent = 'Geolocation not supported by this browser.';
status.style.color = 'var(--accent-red, #ff4444)';
return;
}
if (!window.isSecureContext) {
status.textContent = 'GPS requires HTTPS or localhost.';
status.style.color = 'var(--accent-red, #ff4444)';
return;
}
btn.disabled = true;
btn.innerHTML = '<span style="display:inline-block; width:14px; height:14px; border:2px solid #000; border-top-color:transparent; border-radius:50%; animation:locSpin 0.8s linear infinite;"></span> Detecting...';
status.textContent = '';
navigator.geolocation.getCurrentPosition(
function(pos) {
var lat = pos.coords.latitude;
var lon = pos.coords.longitude;
document.getElementById('locPromptLat').value = lat.toFixed(4);
document.getElementById('locPromptLon').value = lon.toFixed(4);
status.textContent = 'Location detected! Click Save to confirm.';
status.style.color = 'var(--accent-green, #00ff88)';
btn.disabled = false;
btn.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"/></svg> Detected';
},
function(err) {
status.textContent = 'Could not detect: ' + err.message;
status.style.color = 'var(--accent-red, #ff4444)';
btn.disabled = false;
btn.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="3"/><line x1="12" y1="2" x2="12" y2="6"/><line x1="12" y1="18" x2="12" y2="22"/><line x1="2" y1="12" x2="6" y2="12"/><line x1="18" y1="12" x2="22" y2="12"/></svg> Auto-Detect (GPS)';
},
{ enableHighAccuracy: true, timeout: 15000 }
);
}
function locationPromptSave() {
var lat = parseFloat(document.getElementById('locPromptLat').value);
var lon = parseFloat(document.getElementById('locPromptLon').value);
if (isNaN(lat) || lat < -90 || lat > 90) {
document.getElementById('locPromptGpsStatus').textContent = 'Invalid latitude (-90 to 90)';
document.getElementById('locPromptGpsStatus').style.color = 'var(--accent-red, #ff4444)';
return;
}
if (isNaN(lon) || lon < -180 || lon > 180) {
document.getElementById('locPromptGpsStatus').textContent = 'Invalid longitude (-180 to 180)';
document.getElementById('locPromptGpsStatus').style.color = 'var(--accent-red, #ff4444)';
return;
}
if (window.ObserverLocation) {
ObserverLocation.setShared({ lat: lat, lon: lon });
}
localStorage.removeItem('locationPromptSkipped');
var modal = document.getElementById('locationPromptModal');
if (modal) modal.style.display = 'none';
if (typeof showNotification === 'function') {
showNotification('Location', 'Location set to ' + lat.toFixed(4) + ', ' + lon.toFixed(4));
}
}
function locationPromptSkip() {
localStorage.setItem('locationPromptSkipped', 'true');
var modal = document.getElementById('locationPromptModal');
if (modal) modal.style.display = 'none';
}
</script>
<!-- Toast Container -->
<div id="toastContainer"></div>