diff --git a/config.py b/config.py index 32d8b73..1b0c76e 100644 --- a/config.py +++ b/config.py @@ -7,10 +7,20 @@ import os import sys # Application version -VERSION = "2.13.0" +VERSION = "2.13.1" # Changelog - latest release notes (shown on welcome screen) CHANGELOG = [ + { + "version": "2.13.1", + "date": "February 2026", + "highlights": [ + "Help modal system with keyboard shortcuts reference", + "Main Dashboard button in navigation bar", + "Settings modal accessible from all dashboards", + "Dashboard CSS improvements and consistency fixes", + ] + }, { "version": "2.13.0", "date": "February 2026", diff --git a/pyproject.toml b/pyproject.toml index c810cd2..2456ad3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "intercept" -version = "2.13.0" +version = "2.13.1" description = "Signal Intelligence Platform - Pager/433MHz/ADS-B/Satellite/WiFi/Bluetooth" readme = "README.md" requires-python = ">=3.9" diff --git a/static/css/adsb_dashboard.css b/static/css/adsb_dashboard.css index f0dfca3..b05d5f1 100644 --- a/static/css/adsb_dashboard.css +++ b/static/css/adsb_dashboard.css @@ -73,7 +73,7 @@ body { } } -/* Header - Mobile first */ +/* Header */ .header { position: relative; z-index: 10; @@ -83,15 +83,14 @@ body { display: flex; justify-content: space-between; align-items: center; - flex-wrap: wrap; - gap: 8px; + flex-wrap: nowrap; + gap: 12px; min-height: 52px; } @media (min-width: 768px) { .header { padding: 12px 20px; - flex-wrap: nowrap; } } @@ -128,14 +127,52 @@ body { letter-spacing: 2px; } } -} .status-bar { display: flex; - gap: 20px; + gap: 12px; align-items: center; font-family: var(--font-mono); font-size: 11px; + flex-wrap: nowrap; +} + +.agent-selector-compact { + display: flex; + align-items: center; + gap: 8px; +} + +.agent-selector-compact .agent-select-sm { + padding: 4px 8px; + background: var(--bg-card); + border: 1px solid var(--border-color); + border-radius: 4px; + color: var(--text-primary); + font-size: 11px; + font-family: var(--font-mono); +} + +.agent-selector-compact .agent-status-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--accent-green); + box-shadow: 0 0 6px var(--accent-green); +} + +.agent-selector-compact .agent-status-dot.offline { + background: var(--accent-red); + box-shadow: 0 0 6px var(--accent-red); +} + +.agent-selector-compact .show-all-label { + display: flex; + align-items: center; + gap: 4px; + font-size: 11px; + color: var(--text-secondary); + cursor: pointer; } .status-dot { @@ -174,15 +211,15 @@ body { } /* Main dashboard grid - Mobile first */ -/* Header ~55px + Stats strip ~55px = ~110px, using 115px for safety */ +/* Header ~52px + Nav 44px + Stats strip ~55px = ~151px, using 160px for safety */ .dashboard { position: relative; z-index: 10; display: flex; flex-direction: column; gap: 0; - height: calc(100dvh - 115px); - height: calc(100vh - 115px); /* Fallback */ + height: calc(100dvh - 160px); + height: calc(100vh - 160px); /* Fallback */ min-height: 400px; } @@ -218,7 +255,7 @@ body { @media (min-width: 1024px) { .acars-sidebar { display: flex; - max-height: calc(100dvh - 115px); + max-height: calc(100dvh - 160px); } } @@ -1219,7 +1256,7 @@ body { display: flex !important; flex-direction: column !important; height: auto !important; - min-height: calc(100dvh - 115px); + min-height: calc(100dvh - 160px); overflow-y: auto !important; overflow-x: hidden; -webkit-overflow-scrolling: touch; @@ -1288,12 +1325,6 @@ body { padding: 6px 8px; } - /* Status bar - compact on mobile */ - .status-bar { - flex-wrap: wrap; - gap: 6px; - } - /* Strip time smaller on mobile */ .strip-time { font-size: 10px; diff --git a/static/css/agents.css b/static/css/agents.css index 8abaaba..9b5ea47 100644 --- a/static/css/agents.css +++ b/static/css/agents.css @@ -1,23 +1,9 @@ /* * Agents Management CSS * Styles for the remote agent management interface + * Inherits CSS variables from core/variables.css */ -/* CSS Variables (inherited from main theme) */ -:root { - --font-sans: 'Space Mono', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; - --font-mono: 'Space Mono', 'Fira Code', 'Consolas', monospace; - --bg-primary: #0a0a0f; - --bg-secondary: #12121a; - --text-primary: #e0e0e0; - --text-secondary: #888; - --border-color: #1a1a2e; - --accent-cyan: #00d4ff; - --accent-green: #00ff88; - --accent-red: #ff3366; - --accent-orange: #ff9f1c; -} - /* Agent indicator in navigation */ .agent-indicator { display: flex; diff --git a/static/css/ais_dashboard.css b/static/css/ais_dashboard.css index aa355bd..b1eb9f3 100644 --- a/static/css/ais_dashboard.css +++ b/static/css/ais_dashboard.css @@ -134,10 +134,49 @@ body { .status-bar { display: flex; - gap: 20px; + gap: 12px; align-items: center; font-family: var(--font-mono); font-size: 11px; + flex-wrap: nowrap; +} + +.agent-selector-compact { + display: flex; + align-items: center; + gap: 8px; +} + +.agent-selector-compact .agent-select-sm { + padding: 4px 8px; + background: var(--bg-card); + border: 1px solid var(--border-color); + border-radius: 4px; + color: var(--text-primary); + font-size: 11px; + font-family: var(--font-mono); +} + +.agent-selector-compact .agent-status-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--accent-green); + box-shadow: 0 0 6px var(--accent-green); +} + +.agent-selector-compact .agent-status-dot.offline { + background: var(--accent-red); + box-shadow: 0 0 6px var(--accent-red); +} + +.agent-selector-compact .show-all-label { + display: flex; + align-items: center; + gap: 4px; + font-size: 11px; + color: var(--text-secondary); + cursor: pointer; } .back-link { @@ -322,14 +361,15 @@ body { } /* Main dashboard grid - Mobile first */ +/* Header ~52px + Nav 44px + Stats strip ~55px = ~151px, using 160px for safety */ .dashboard { position: relative; z-index: 10; display: flex; flex-direction: column; gap: 0; - height: calc(100dvh - 95px); - height: calc(100vh - 95px); + height: calc(100dvh - 160px); + height: calc(100vh - 160px); min-height: 400px; } @@ -836,7 +876,7 @@ body { display: flex !important; flex-direction: column !important; height: auto !important; - min-height: calc(100dvh - 95px); + min-height: calc(100dvh - 160px); overflow-y: auto !important; overflow-x: hidden; -webkit-overflow-scrolling: touch; diff --git a/static/css/global-nav.css b/static/css/global-nav.css index b8735fc..84e2bb8 100644 --- a/static/css/global-nav.css +++ b/static/css/global-nav.css @@ -394,3 +394,46 @@ [data-animations="off"] .nav-tool-btn .icon-effects-off { display: flex; } + +/* Main Dashboard Button in Nav */ +a.nav-dashboard-btn, +a.nav-dashboard-btn:link, +a.nav-dashboard-btn:visited { + display: inline-flex !important; + align-items: center; + gap: 6px; + padding: 6px 12px; + border-radius: 6px; + background: rgba(20, 33, 53, 0.6) !important; + border: 1px solid rgba(77, 125, 191, 0.12) !important; + color: #b7c1cf !important; + font-size: 11px; + font-weight: 500; + cursor: pointer; + transition: all 0.15s ease; + white-space: nowrap; + text-decoration: none !important; +} + +a.nav-dashboard-btn:hover { + background: rgba(27, 36, 51, 0.9) !important; + border-color: #4d7dbf !important; + color: #4d7dbf !important; + box-shadow: 0 6px 14px rgba(5, 9, 15, 0.35); +} + +.nav-dashboard-btn .icon { + width: 14px; + height: 14px; +} + +.nav-dashboard-btn .icon svg { + width: 100%; + height: 100%; + stroke: currentColor; +} + +.nav-dashboard-btn .nav-label { + font-family: var(--font-mono, 'JetBrains Mono', monospace); + letter-spacing: 0.5px; +} diff --git a/static/css/help-modal.css b/static/css/help-modal.css new file mode 100644 index 0000000..561f632 --- /dev/null +++ b/static/css/help-modal.css @@ -0,0 +1,184 @@ +/** + * Help Modal Styles + * Shared across all pages that include the help modal partial + */ + +.help-modal { + display: none; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.85); + z-index: 10000; + overflow-y: auto; + padding: 40px 20px; +} + +.help-modal.active { + display: block; +} + +.help-content { + max-width: 800px; + margin: 0 auto; + background: var(--bg-card, var(--bg-secondary, #0f1218)); + border: 1px solid var(--border-color, #1f2937); + border-radius: 8px; + padding: 30px; + position: relative; +} + +.help-content h2 { + color: var(--accent-cyan, #4a9eff); + margin-bottom: 20px; + font-size: 24px; + letter-spacing: 2px; +} + +.help-content h3 { + color: var(--text-primary, #e8eaed); + margin: 25px 0 15px 0; + font-size: 14px; + text-transform: uppercase; + letter-spacing: 1px; + border-bottom: 1px solid var(--border-color, #1f2937); + padding-bottom: 8px; +} + +.help-close { + position: absolute; + top: 15px; + right: 15px; + background: none; + border: none; + color: var(--text-dim, #4b5563); + font-size: 24px; + cursor: pointer; + transition: color 0.2s; +} + +.help-close:hover { + color: var(--accent-red, #ef4444); +} + +.help-modal .icon-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 12px; + margin: 15px 0; +} + +.help-modal .icon-item { + display: flex; + align-items: center; + gap: 10px; + padding: 10px; + background: var(--bg-primary, #0a0c10); + border: 1px solid var(--border-color, #1f2937); + border-radius: 4px; + font-size: 12px; +} + +.help-modal .icon-item .icon { + font-size: 18px; + width: 30px; + text-align: center; +} + +.help-modal .icon-item .desc { + color: var(--text-secondary, #9ca3af); +} + +.help-modal .tip-list { + list-style: none; + padding: 0; + margin: 15px 0; +} + +.help-modal .tip-list li { + padding: 8px 0; + padding-left: 20px; + position: relative; + color: var(--text-secondary, #9ca3af); + font-size: 13px; + border-bottom: 1px solid var(--border-color, #1f2937); +} + +.help-modal .tip-list li:last-child { + border-bottom: none; +} + +.help-modal .tip-list li::before { + content: '\203A'; + position: absolute; + left: 0; + color: var(--accent-cyan, #4a9eff); + font-weight: bold; +} + +.help-tabs { + display: flex; + gap: 0; + margin-bottom: 20px; + border: 1px solid var(--border-color, #1f2937); + border-radius: 4px; + overflow: hidden; +} + +.help-tab { + flex: 1; + padding: 10px; + background: var(--bg-primary, #0a0c10); + border: none; + color: var(--text-secondary, #9ca3af); + cursor: pointer; + font-size: 11px; + text-transform: uppercase; + letter-spacing: 1px; + transition: all 0.15s ease; + position: relative; +} + +.help-tab:not(:last-child) { + border-right: 1px solid var(--border-color, #1f2937); +} + +.help-tab:hover { + background: var(--bg-tertiary, #151a23); + color: var(--text-primary, #e8eaed); +} + +.help-tab.active { + background: var(--bg-tertiary, #151a23); + color: var(--accent-cyan, #4a9eff); +} + +.help-tab.active::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 2px; + background: var(--accent-cyan, #4a9eff); +} + +.help-section { + display: none; +} + +.help-section.active { + display: block; +} + +/* Ensure code tags are styled */ +.help-modal code { + background: var(--bg-tertiary, #151a23); + padding: 2px 6px; + border-radius: 3px; + font-family: var(--font-mono, 'JetBrains Mono', monospace); + font-size: 11px; + color: var(--accent-cyan, #4a9eff); +} diff --git a/static/css/satellite_dashboard.css b/static/css/satellite_dashboard.css index ffc94fc..427b3da 100644 --- a/static/css/satellite_dashboard.css +++ b/static/css/satellite_dashboard.css @@ -164,10 +164,45 @@ body { .status-bar { display: flex; - gap: 20px; + gap: 12px; align-items: center; font-family: var(--font-mono); font-size: 11px; + flex-wrap: nowrap; +} + +.location-selector { + display: flex; + align-items: center; + gap: 8px; +} + +.location-selector .location-label { + color: var(--text-secondary); + font-size: 10px; +} + +.location-selector .location-select { + padding: 4px 8px; + background: var(--bg-card); + border: 1px solid var(--border-color); + border-radius: 4px; + color: var(--text-primary); + font-size: 11px; + font-family: var(--font-mono); +} + +.location-selector .location-status-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--accent-green); + box-shadow: 0 0 6px var(--accent-green); +} + +.location-selector .location-status-dot.offline { + background: var(--accent-red); + box-shadow: 0 0 6px var(--accent-red); } .status-item { @@ -213,6 +248,7 @@ body { } /* Main dashboard grid */ +/* Header ~52px + Nav 44px = ~96px, using 100px for safety */ .dashboard { position: relative; z-index: 10; @@ -220,7 +256,7 @@ body { grid-template-columns: 1fr 1fr 340px; grid-template-rows: 1fr auto; gap: 0; - height: calc(100vh - 60px); + height: calc(100vh - 100px); min-height: 500px; } @@ -698,7 +734,7 @@ body { display: flex; flex-direction: column; height: auto; - min-height: calc(100vh - 60px); + min-height: calc(100vh - 100px); } .polar-container, diff --git a/static/js/core/settings-manager.js b/static/js/core/settings-manager.js index ab6f5c6..11fae2d 100644 --- a/static/js/core/settings-manager.js +++ b/static/js/core/settings-manager.js @@ -325,12 +325,12 @@ const Settings = { window.map, window.leafletMap, window.aprsMap, - window.adsbMap, window.radarMap, window.vesselMap, window.groundMap, window.groundTrackMap, - window.meshMap + window.meshMap, + window.issMap ].filter(m => m && typeof m.eachLayer === 'function'); // Combine with registered maps, removing duplicates diff --git a/static/js/modes/meshtastic.js b/static/js/modes/meshtastic.js index 7d0f32f..1e3387f 100644 --- a/static/js/modes/meshtastic.js +++ b/static/js/modes/meshtastic.js @@ -97,7 +97,7 @@ const Meshtastic = (function() { /** * Initialize the Leaflet map */ - function initMap() { + async function initMap() { if (meshMap) return; const mapContainer = document.getElementById('meshMap'); @@ -111,7 +111,9 @@ const Meshtastic = (function() { window.meshMap = meshMap; // Use settings manager for tile layer (allows runtime changes) - if (typeof Settings !== 'undefined' && Settings.createTileLayer) { + if (typeof Settings !== 'undefined') { + // Wait for settings to load from server before applying tiles + await Settings.init(); Settings.createTileLayer().addTo(meshMap); Settings.registerMap(meshMap); } else { diff --git a/static/js/modes/sstv.js b/static/js/modes/sstv.js index 617f626..2392afc 100644 --- a/static/js/modes/sstv.js +++ b/static/js/modes/sstv.js @@ -37,20 +37,20 @@ const SSTV = (function() { /** * Load location into input fields */ - 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; + 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); @@ -60,23 +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) { - if (window.ObserverLocation && ObserverLocation.isSharedEnabled()) { - ObserverLocation.setShared({ lat, lon }); - } else { - 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 + } } /** @@ -103,12 +103,12 @@ const SSTV = (function() { if (latInput) latInput.value = lat; if (lonInput) lonInput.value = lon; - if (window.ObserverLocation && ObserverLocation.isSharedEnabled()) { - ObserverLocation.setShared({ lat: parseFloat(lat), lon: parseFloat(lon) }); - } else { - 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; @@ -159,7 +159,7 @@ const SSTV = (function() { /** * Initialize Leaflet map for ISS tracking */ - function initMap() { + async function initMap() { const mapContainer = document.getElementById('sstvIssMap'); if (!mapContainer || issMap) return; @@ -173,10 +173,14 @@ const SSTV = (function() { attributionControl: false, worldCopyJump: true }); + window.issMap = issMap; // Add tile layer using settings manager if available - if (typeof Settings !== 'undefined' && Settings.createTileLayer) { + if (typeof Settings !== 'undefined') { + // Wait for settings to load from server before applying tiles + await Settings.init(); Settings.createTileLayer().addTo(issMap); + Settings.registerMap(issMap); } else { // Fallback to dark theme tiles L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', { diff --git a/templates/adsb_dashboard.html b/templates/adsb_dashboard.html index c906bf2..7f690e0 100644 --- a/templates/adsb_dashboard.html +++ b/templates/adsb_dashboard.html @@ -22,6 +22,8 @@ + + + diff --git a/templates/adsb_history.html b/templates/adsb_history.html index 5abbb57..33f9a48 100644 --- a/templates/adsb_history.html +++ b/templates/adsb_history.html @@ -11,6 +11,8 @@ {% endif %} + + @@ -769,6 +771,14 @@ } }); + + + {% include 'partials/settings-modal.html' %} + + + {% include 'partials/help-modal.html' %} + + diff --git a/templates/agents.html b/templates/agents.html index 5cdd4d8..2a7076e 100644 --- a/templates/agents.html +++ b/templates/agents.html @@ -6,9 +6,13 @@ iNTERCEPT // Remote Agents - - + + + + + + + + {% include 'partials/settings-modal.html' %} + + + {% include 'partials/help-modal.html' %} + + + diff --git a/templates/index.html b/templates/index.html index 16ddabe..a230e89 100644 --- a/templates/index.html +++ b/templates/index.html @@ -39,6 +39,7 @@ {% endif %} + @@ -2070,6 +2071,24 @@ } } + function applySettingsFromQuery() { + const params = new URLSearchParams(window.location.search); + if (params.get('settings') === '1') { + // Remove settings param from URL to avoid reopening on refresh + params.delete('settings'); + const newUrl = params.toString() + ? window.location.pathname + '?' + params.toString() + : window.location.pathname; + window.history.replaceState({}, '', newUrl); + // Open settings modal after a brief delay to ensure page is ready + setTimeout(() => { + if (typeof showSettings === 'function') { + showSettings(); + } + }, 100); + } + } + function acceptDisclaimer() { localStorage.setItem('disclaimerAccepted', 'true'); document.getElementById('disclaimerModal').classList.add('disclaimer-hidden'); @@ -2410,6 +2429,9 @@ // Apply mode from URL query (e.g., /?mode=wifi) applyModeFromQuery(); + + // Check for settings=1 query param (from dashboard settings button) + applySettingsFromQuery(); }); // Toggle section collapse @@ -7923,7 +7945,7 @@ }); } - function initAprsMap() { + async function initAprsMap() { if (aprsMap) return; const mapContainer = document.getElementById('aprsMap'); @@ -7938,7 +7960,9 @@ window.aprsMap = aprsMap; // Use settings manager for tile layer (allows runtime changes) - if (typeof Settings !== 'undefined' && Settings.createTileLayer) { + if (typeof Settings !== 'undefined') { + // Wait for settings to load from server before applying tiles + await Settings.init(); Settings.createTileLayer().addTo(aprsMap); Settings.registerMap(aprsMap); } else { @@ -8846,7 +8870,7 @@ let observerMarker = null; let satPositionInterval = null; - function initGroundTrackMap() { + async function initGroundTrackMap() { const mapContainer = document.getElementById('groundTrackMap'); if (!mapContainer || groundTrackMap) return; @@ -8859,7 +8883,9 @@ window.groundTrackMap = groundTrackMap; // Use settings manager for tile layer (allows runtime changes) - if (typeof Settings !== 'undefined' && Settings.createTileLayer) { + if (typeof Settings !== 'undefined') { + // Wait for settings to load from server before applying tiles + await Settings.init(); Settings.createTileLayer().addTo(groundTrackMap); Settings.registerMap(groundTrackMap); } else { diff --git a/templates/network_monitor.html b/templates/network_monitor.html index 982c964..c4c283d 100644 --- a/templates/network_monitor.html +++ b/templates/network_monitor.html @@ -12,22 +12,25 @@ + +