mirror of
https://github.com/smittix/intercept.git
synced 2026-04-24 06:40:00 -07:00
Release v2.13.1 - Help modal and navigation improvements
- Add help modal system with keyboard shortcuts reference - Add Main Dashboard button in navigation bar - Make settings modal accessible from all dashboards - Dashboard CSS improvements and consistency fixes Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
12
config.py
12
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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
184
static/css/help-modal.css
Normal file
184
static/css/help-modal.css
Normal file
@@ -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);
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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', {
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/core/variables.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/responsive.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/global-nav.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/settings.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/help-modal.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/adsb_dashboard.css') }}">
|
||||
<script>
|
||||
window.INTERCEPT_SHARED_OBSERVER_LOCATION = {{ shared_observer_location | tojson }};
|
||||
@@ -49,8 +51,6 @@
|
||||
<input type="checkbox" id="showAllAgents" onchange="toggleShowAllAgents()"> All
|
||||
</label>
|
||||
</div>
|
||||
<a href="#" onclick="history.back(); return false;" class="back-link">Back</a>
|
||||
<a href="/?mode=aircraft" class="back-link">Main Dashboard</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -2372,7 +2372,7 @@ sudo make install</code>
|
||||
now.toISOString().substring(11, 19) + ' UTC';
|
||||
}
|
||||
|
||||
function initMap() {
|
||||
async function initMap() {
|
||||
radarMap = L.map('radarMap', {
|
||||
center: [observerLocation.lat, observerLocation.lon],
|
||||
zoom: 7,
|
||||
@@ -2382,7 +2382,9 @@ sudo make install</code>
|
||||
|
||||
// Use settings manager for tile layer (allows runtime changes)
|
||||
window.radarMap = radarMap;
|
||||
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(radarMap);
|
||||
Settings.registerMap(radarMap);
|
||||
} else {
|
||||
@@ -4521,6 +4523,14 @@ sudo make install</code>
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Settings Modal -->
|
||||
{% include 'partials/settings-modal.html' %}
|
||||
|
||||
<!-- Help Modal -->
|
||||
{% include 'partials/help-modal.html' %}
|
||||
|
||||
<script src="{{ url_for('static', filename='js/core/settings-manager.js') }}"></script>
|
||||
|
||||
<!-- Agent Manager -->
|
||||
<script src="{{ url_for('static', filename='js/core/agents.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/core/global-nav.js') }}"></script>
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
{% endif %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/responsive.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/global-nav.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/settings.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/help-modal.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/adsb_history.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
@@ -769,6 +771,14 @@
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Settings Modal -->
|
||||
{% include 'partials/settings-modal.html' %}
|
||||
|
||||
<!-- Help Modal -->
|
||||
{% include 'partials/help-modal.html' %}
|
||||
|
||||
<script src="{{ url_for('static', filename='js/core/settings-manager.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/core/global-nav.js') }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -6,9 +6,13 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>iNTERCEPT // Remote Agents</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/index.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/agents.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/core/variables.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/core/base.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/responsive.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/global-nav.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/agents.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/settings.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/help-modal.css') }}">
|
||||
<style>
|
||||
.agents-container {
|
||||
max-width: 1200px;
|
||||
@@ -224,26 +228,6 @@
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* Navigation links */
|
||||
.nav-links {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.back-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: var(--accent-cyan);
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.back-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Toast notifications */
|
||||
.toast {
|
||||
position: fixed;
|
||||
@@ -304,22 +288,6 @@
|
||||
{% include 'partials/nav.html' with context %}
|
||||
|
||||
<div class="agents-container">
|
||||
<div class="nav-links">
|
||||
<a href="#" onclick="history.back(); return false;" class="back-link">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M19 12H5M12 19l-7-7 7-7"/>
|
||||
</svg>
|
||||
Back
|
||||
</a>
|
||||
<a href="/" class="back-link">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
|
||||
<polyline points="9 22 9 12 15 12 15 22"/>
|
||||
</svg>
|
||||
Dashboard
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="agents-header">
|
||||
<h1>Remote Agents</h1>
|
||||
</div>
|
||||
@@ -587,6 +555,14 @@
|
||||
// Load agents on page load
|
||||
document.addEventListener('DOMContentLoaded', loadAgents);
|
||||
</script>
|
||||
|
||||
<!-- Settings Modal -->
|
||||
{% include 'partials/settings-modal.html' %}
|
||||
|
||||
<!-- Help Modal -->
|
||||
{% include 'partials/help-modal.html' %}
|
||||
|
||||
<script src="{{ url_for('static', filename='js/core/settings-manager.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/core/global-nav.js') }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/ais_dashboard.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/responsive.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/global-nav.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/settings.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/help-modal.css') }}">
|
||||
<script>
|
||||
window.INTERCEPT_SHARED_OBSERVER_LOCATION = {{ shared_observer_location | tojson }};
|
||||
</script>
|
||||
@@ -49,8 +51,6 @@
|
||||
<input type="checkbox" id="showAllAgents" onchange="toggleShowAllAgents()"> All
|
||||
</label>
|
||||
</div>
|
||||
<a href="#" onclick="history.back(); return false;" class="back-link">Back</a>
|
||||
<a href="/" class="back-link">Main Dashboard</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -390,7 +390,7 @@
|
||||
};
|
||||
|
||||
// Initialize map
|
||||
function initMap() {
|
||||
async function initMap() {
|
||||
if (observerLocation) {
|
||||
document.getElementById('obsLat').value = observerLocation.lat;
|
||||
document.getElementById('obsLon').value = observerLocation.lon;
|
||||
@@ -404,7 +404,9 @@
|
||||
|
||||
// Use settings manager for tile layer (allows runtime changes)
|
||||
window.vesselMap = vesselMap;
|
||||
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(vesselMap);
|
||||
Settings.registerMap(vesselMap);
|
||||
} else {
|
||||
@@ -1553,6 +1555,14 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Settings Modal -->
|
||||
{% include 'partials/settings-modal.html' %}
|
||||
|
||||
<!-- Help Modal -->
|
||||
{% include 'partials/help-modal.html' %}
|
||||
|
||||
<script src="{{ url_for('static', filename='js/core/settings-manager.js') }}"></script>
|
||||
|
||||
<!-- Agent Manager -->
|
||||
<script src="{{ url_for('static', filename='js/core/agents.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/core/global-nav.js') }}"></script>
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
|
||||
{% endif %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/responsive.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/global-nav.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/index.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/modes/acars.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/modes/aprs.css') }}">
|
||||
@@ -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 {
|
||||
|
||||
@@ -12,22 +12,25 @@
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/responsive.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/agents.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/global-nav.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/settings.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/help-modal.css') }}">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
: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;
|
||||
--bg-tertiary: #1a1a2e;
|
||||
--text-primary: #e0e0e0;
|
||||
--text-secondary: #888;
|
||||
--border-color: #2a2a3e;
|
||||
--accent-cyan: #00d4ff;
|
||||
--accent-green: #00ff88;
|
||||
--accent-red: #ff3366;
|
||||
--accent-orange: #ff9f1c;
|
||||
--bg-primary: #0a0c10;
|
||||
--bg-secondary: #0f1218;
|
||||
--bg-tertiary: #151a23;
|
||||
--text-primary: #e8eaed;
|
||||
--text-secondary: #9ca3af;
|
||||
--text-dim: #4b5563;
|
||||
--border-color: #1f2937;
|
||||
--accent-cyan: #4a9eff;
|
||||
--accent-green: #22c55e;
|
||||
--accent-red: #ef4444;
|
||||
--accent-orange: #f59e0b;
|
||||
--accent-purple: #a855f7;
|
||||
}
|
||||
|
||||
@@ -120,12 +123,13 @@
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
/* Header ~50px + Nav 44px + Status bar ~40px = ~134px, using 150px for safety */
|
||||
.main-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 300px;
|
||||
gap: 20px;
|
||||
padding: 20px;
|
||||
height: calc(100vh - 110px);
|
||||
height: calc(100vh - 150px);
|
||||
}
|
||||
|
||||
.data-panel {
|
||||
@@ -157,7 +161,7 @@
|
||||
.panel-count {
|
||||
font-size: 10px;
|
||||
padding: 2px 8px;
|
||||
background: rgba(0, 212, 255, 0.2);
|
||||
background: rgba(74, 158, 255, 0.2);
|
||||
color: var(--accent-cyan);
|
||||
border-radius: 10px;
|
||||
font-family: var(--font-mono);
|
||||
@@ -188,7 +192,7 @@
|
||||
}
|
||||
|
||||
.panel-tab.active {
|
||||
background: rgba(0, 212, 255, 0.1);
|
||||
background: rgba(74, 158, 255, 0.1);
|
||||
color: var(--accent-cyan);
|
||||
border-color: var(--accent-cyan);
|
||||
}
|
||||
@@ -226,7 +230,7 @@
|
||||
}
|
||||
|
||||
.data-table tr:hover {
|
||||
background: rgba(0, 212, 255, 0.05);
|
||||
background: rgba(74, 158, 255, 0.05);
|
||||
}
|
||||
|
||||
.mono {
|
||||
@@ -245,7 +249,7 @@
|
||||
gap: 4px;
|
||||
padding: 2px 6px;
|
||||
font-size: 9px;
|
||||
background: rgba(0, 212, 255, 0.15);
|
||||
background: rgba(74, 158, 255, 0.15);
|
||||
color: var(--accent-cyan);
|
||||
border-radius: 8px;
|
||||
font-family: var(--font-mono);
|
||||
@@ -260,7 +264,7 @@
|
||||
|
||||
/* Different colors for different agents */
|
||||
.source-badge:nth-child(2) {
|
||||
background: rgba(0, 255, 136, 0.15);
|
||||
background: rgba(34, 197, 94, 0.15);
|
||||
color: var(--accent-green);
|
||||
}
|
||||
.source-badge:nth-child(2) .dot {
|
||||
@@ -276,7 +280,7 @@
|
||||
}
|
||||
|
||||
.source-badge:nth-child(4) {
|
||||
background: rgba(255, 159, 28, 0.15);
|
||||
background: rgba(245, 158, 11, 0.15);
|
||||
color: var(--accent-orange);
|
||||
}
|
||||
.source-badge:nth-child(4) .dot {
|
||||
@@ -435,23 +439,23 @@
|
||||
gap: 6px;
|
||||
margin-top: 4px;
|
||||
padding: 3px 8px;
|
||||
background: rgba(0, 212, 255, 0.1);
|
||||
background: rgba(74, 158, 255, 0.1);
|
||||
border-radius: 4px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.location-estimate.high {
|
||||
background: rgba(0, 255, 136, 0.15);
|
||||
background: rgba(34, 197, 94, 0.15);
|
||||
border-left: 2px solid var(--accent-green);
|
||||
}
|
||||
|
||||
.location-estimate.medium {
|
||||
background: rgba(255, 159, 28, 0.15);
|
||||
background: rgba(245, 158, 11, 0.15);
|
||||
border-left: 2px solid var(--accent-orange);
|
||||
}
|
||||
|
||||
.location-estimate.low {
|
||||
background: rgba(255, 51, 102, 0.15);
|
||||
background: rgba(239, 68, 68, 0.15);
|
||||
border-left: 2px solid var(--accent-red);
|
||||
}
|
||||
|
||||
@@ -493,11 +497,11 @@
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.type-badge.type-wifi { background: rgba(0, 212, 255, 0.2); color: var(--accent-cyan); }
|
||||
.type-badge.type-wifi { background: rgba(74, 158, 255, 0.2); color: var(--accent-cyan); }
|
||||
.type-badge.type-bluetooth { background: rgba(168, 85, 247, 0.2); color: var(--accent-purple); }
|
||||
.type-badge.type-adsb { background: rgba(0, 255, 136, 0.2); color: var(--accent-green); }
|
||||
.type-badge.type-ais { background: rgba(255, 159, 28, 0.2); color: var(--accent-orange); }
|
||||
.type-badge.type-sensor { background: rgba(255, 51, 102, 0.2); color: var(--accent-red); }
|
||||
.type-badge.type-adsb { background: rgba(34, 197, 94, 0.2); color: var(--accent-green); }
|
||||
.type-badge.type-ais { background: rgba(245, 158, 11, 0.2); color: var(--accent-orange); }
|
||||
.type-badge.type-sensor { background: rgba(239, 68, 68, 0.2); color: var(--accent-red); }
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.main-grid {
|
||||
@@ -1106,6 +1110,14 @@
|
||||
connectStream();
|
||||
addLogEntry('system', 'Network Monitor initialized');
|
||||
</script>
|
||||
|
||||
<!-- Settings Modal -->
|
||||
{% include 'partials/settings-modal.html' %}
|
||||
|
||||
<!-- Help Modal -->
|
||||
{% include 'partials/help-modal.html' %}
|
||||
|
||||
<script src="{{ url_for('static', filename='js/core/settings-manager.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/core/global-nav.js') }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
318
templates/partials/help-modal.html
Normal file
318
templates/partials/help-modal.html
Normal file
@@ -0,0 +1,318 @@
|
||||
{#
|
||||
Help Modal Partial
|
||||
Provides consistent help modal across all pages
|
||||
#}
|
||||
|
||||
<!-- Help Modal -->
|
||||
<div id="helpModal" class="help-modal" onclick="if(event.target === this) hideHelp()">
|
||||
<div class="help-content">
|
||||
<button class="help-close" onclick="hideHelp()">×</button>
|
||||
<h2>iNTERCEPT Help</h2>
|
||||
|
||||
<div class="help-tabs">
|
||||
<button class="help-tab active" data-tab="icons" onclick="switchHelpTab('icons')">Icons</button>
|
||||
<button class="help-tab" data-tab="modes" onclick="switchHelpTab('modes')">Modes</button>
|
||||
<button class="help-tab" data-tab="wifi" onclick="switchHelpTab('wifi')">WiFi</button>
|
||||
<button class="help-tab" data-tab="tips" onclick="switchHelpTab('tips')">Tips</button>
|
||||
</div>
|
||||
|
||||
<!-- Icons Section -->
|
||||
<div id="help-icons" class="help-section active">
|
||||
<h3>Stats Bar Icons</h3>
|
||||
<div class="icon-grid">
|
||||
<div class="icon-item"><span class="icon">📟</span><span class="desc">POCSAG messages decoded</span></div>
|
||||
<div class="icon-item"><span class="icon">📠</span><span class="desc">FLEX messages decoded</span></div>
|
||||
<div class="icon-item"><span class="icon">📨</span><span class="desc">Total messages received</span></div>
|
||||
<div class="icon-item"><span class="icon">🌡️</span><span class="desc">Unique sensors detected</span></div>
|
||||
<div class="icon-item"><span class="icon">📊</span><span class="desc">Device types found</span></div>
|
||||
<div class="icon-item"><span class="icon">🛰️</span><span class="desc">Satellites monitored</span></div>
|
||||
<div class="icon-item"><span class="icon">📡</span><span class="desc">WiFi Access Points</span></div>
|
||||
<div class="icon-item"><span class="icon">👤</span><span class="desc">Connected WiFi clients</span></div>
|
||||
<div class="icon-item"><span class="icon">🤝</span><span class="desc">Captured handshakes</span></div>
|
||||
<div class="icon-item"><span class="icon">🚁</span><span class="desc">Detected drones (click for details)</span></div>
|
||||
<div class="icon-item"><span class="icon">⚠️</span><span class="desc">Rogue APs (click for details)</span></div>
|
||||
<div class="icon-item"><span class="icon">🔵</span><span class="desc">Bluetooth devices</span></div>
|
||||
<div class="icon-item"><span class="icon">📍</span><span class="desc">BLE beacons / APRS stations</span></div>
|
||||
</div>
|
||||
|
||||
<h3>Mode Tab Icons</h3>
|
||||
<div class="icon-grid">
|
||||
<div class="icon-item"><span class="icon">📟</span><span class="desc">Pager - POCSAG/FLEX decoder</span></div>
|
||||
<div class="icon-item"><span class="icon">📡</span><span class="desc">433MHz - Sensor decoder</span></div>
|
||||
<div class="icon-item"><span class="icon">⚡</span><span class="desc">Meters - Utility meter decoder</span></div>
|
||||
<div class="icon-item"><span class="icon">✈️</span><span class="desc">Aircraft - ADS-B tracking & history</span></div>
|
||||
<div class="icon-item"><span class="icon">🚢</span><span class="desc">Vessels - AIS & VHF DSC distress</span></div>
|
||||
<div class="icon-item"><span class="icon">📻</span><span class="desc">Spy Stations - Number stations database</span></div>
|
||||
<div class="icon-item"><span class="icon">📍</span><span class="desc">APRS - Amateur radio tracking</span></div>
|
||||
<div class="icon-item"><span class="icon">🛰️</span><span class="desc">Satellite - Pass prediction</span></div>
|
||||
<div class="icon-item"><span class="icon">📶</span><span class="desc">WiFi - Network scanner</span></div>
|
||||
<div class="icon-item"><span class="icon">🔵</span><span class="desc">Bluetooth - BT/BLE scanner</span></div>
|
||||
<div class="icon-item"><span class="icon">📻</span><span class="desc">Listening Post - SDR scanner</span></div>
|
||||
<div class="icon-item"><span class="icon">🔍</span><span class="desc">TSCM - Counter-surveillance</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modes Section -->
|
||||
<div id="help-modes" class="help-section">
|
||||
<h3>Pager Mode</h3>
|
||||
<ul class="tip-list">
|
||||
<li>Decodes POCSAG and FLEX pager signals using RTL-SDR</li>
|
||||
<li>Set frequency to local pager frequencies (common: 152-158 MHz)</li>
|
||||
<li>Messages are displayed in real-time as they're decoded</li>
|
||||
<li>Use presets for common pager frequencies</li>
|
||||
</ul>
|
||||
|
||||
<h3>433MHz Sensor Mode</h3>
|
||||
<ul class="tip-list">
|
||||
<li>Decodes wireless sensors on 433.92 MHz ISM band</li>
|
||||
<li>Detects temperature, humidity, weather stations, tire pressure monitors</li>
|
||||
<li>Supports many common protocols (Acurite, LaCrosse, Oregon Scientific, etc.)</li>
|
||||
<li>Device intelligence builds profiles of recurring devices</li>
|
||||
</ul>
|
||||
|
||||
<h3>Utility Meter Mode</h3>
|
||||
<ul class="tip-list">
|
||||
<li>Decodes utility meter transmissions (water, gas, electric) using rtlamr</li>
|
||||
<li>Supports ERT protocol on 912 MHz (North America) or 868 MHz (Europe)</li>
|
||||
<li>Displays meter IDs and consumption data in real-time</li>
|
||||
<li>Supports SCM, SCM+, IDM, NetIDM, and R900 message types</li>
|
||||
</ul>
|
||||
|
||||
<h3>Aircraft (Dashboard)</h3>
|
||||
<ul class="tip-list">
|
||||
<li>Opens the dedicated ADS-B Dashboard for aircraft tracking</li>
|
||||
<li>Features radar scope, map view, airband audio, and ACARS decoding</li>
|
||||
<li>Optional history mode persists data to Postgres for long-term analysis</li>
|
||||
<li>Access history dashboard at <code>/adsb/history</code></li>
|
||||
</ul>
|
||||
|
||||
<h3>Vessels (Dashboard)</h3>
|
||||
<ul class="tip-list">
|
||||
<li>Opens the AIS Dashboard for maritime vessel tracking</li>
|
||||
<li>Displays vessel name, MMSI, callsign, destination, and navigation data</li>
|
||||
<li><strong>VHF DSC Channel 70:</strong> Monitors maritime distress frequency (156.525 MHz)</li>
|
||||
<li>Decodes DSC messages: Distress, Urgency, Safety, and Routine calls</li>
|
||||
<li>MMSI country identification via Maritime Identification Digits (MID)</li>
|
||||
<li>Visual alerts for DISTRESS and URGENCY messages with map markers</li>
|
||||
</ul>
|
||||
|
||||
<h3>Spy Stations</h3>
|
||||
<ul class="tip-list">
|
||||
<li>Database of number stations and diplomatic HF networks</li>
|
||||
<li>Browse stations from priyom.org with frequencies and schedules</li>
|
||||
<li>Filter by type (number/diplomatic), country, and mode</li>
|
||||
<li>Famous stations: UVB-76 "The Buzzer", Cuban HM01, Israeli E17z</li>
|
||||
<li>Click "Tune" to listen via Listening Post mode</li>
|
||||
</ul>
|
||||
|
||||
<h3>APRS Mode</h3>
|
||||
<ul class="tip-list">
|
||||
<li>Decodes APRS (Automatic Packet Reporting System) on VHF</li>
|
||||
<li>Tracks amateur radio operators transmitting position data</li>
|
||||
<li>Regional frequencies: 144.390 MHz (N. America), 144.800 MHz (Europe)</li>
|
||||
<li>Uses Direwolf or multimon-ng for packet decoding</li>
|
||||
<li>Interactive map shows station positions in real-time</li>
|
||||
</ul>
|
||||
|
||||
<h3>Satellite Mode</h3>
|
||||
<ul class="tip-list">
|
||||
<li>Track satellites using TLE (Two-Line Element) data</li>
|
||||
<li>Add satellites manually or fetch from Celestrak by category</li>
|
||||
<li>Categories: Amateur, Weather, ISS, Starlink, GPS, and more</li>
|
||||
<li>View next pass predictions with elevation and duration</li>
|
||||
</ul>
|
||||
|
||||
<h3>WiFi Mode</h3>
|
||||
<ul class="tip-list">
|
||||
<li>Requires a WiFi adapter capable of monitor mode</li>
|
||||
<li>Click "Enable Monitor" to put adapter in monitor mode</li>
|
||||
<li>Scans all channels or lock to a specific channel</li>
|
||||
<li>Detects drones by SSID patterns and manufacturer OUI</li>
|
||||
<li>Rogue AP detection flags same SSID on multiple BSSIDs</li>
|
||||
<li>Click network rows to target for deauth or handshake capture</li>
|
||||
</ul>
|
||||
|
||||
<h3>Bluetooth Mode</h3>
|
||||
<ul class="tip-list">
|
||||
<li>Scans for classic Bluetooth and BLE devices</li>
|
||||
<li>Shows device names, addresses, and signal strength</li>
|
||||
<li>Manufacturer lookup from MAC address OUI</li>
|
||||
<li>Radar visualization shows device proximity</li>
|
||||
</ul>
|
||||
|
||||
<h3>Listening Post Mode</h3>
|
||||
<ul class="tip-list">
|
||||
<li>Wideband SDR scanner with spectrum visualization</li>
|
||||
<li>Tune to any frequency supported by your SDR hardware</li>
|
||||
<li>AM/FM/USB/LSB demodulation modes</li>
|
||||
<li>Bookmark frequencies for quick recall</li>
|
||||
<li>Quick tune presets for emergency and marine channels</li>
|
||||
</ul>
|
||||
|
||||
<h3>TSCM Mode</h3>
|
||||
<ul class="tip-list">
|
||||
<li>Technical Surveillance Countermeasures sweep</li>
|
||||
<li>Scans for unknown RF transmitters, WiFi devices, Bluetooth</li>
|
||||
<li>Baseline comparison to detect new/anomalous devices</li>
|
||||
<li>Threat classification: Critical, High, Medium, Low</li>
|
||||
<li>Useful for security audits and bug sweeps</li>
|
||||
<li><em style="color: var(--text-muted);">Note: This feature is in early development</em></li>
|
||||
</ul>
|
||||
|
||||
<h3>Meshtastic Mode</h3>
|
||||
<ul class="tip-list">
|
||||
<li>Integrates with Meshtastic LoRa mesh network devices</li>
|
||||
<li>Connect Heltec, T-Beam, RAK, or other compatible devices via USB</li>
|
||||
<li>Real-time message streaming with RSSI and SNR metrics</li>
|
||||
<li>Configure channels with encryption keys</li>
|
||||
<li>View connected nodes and message history</li>
|
||||
<li>Requires: Meshtastic device + <code>pip install meshtastic</code></li>
|
||||
</ul>
|
||||
|
||||
<h3>Network Monitor</h3>
|
||||
<ul class="tip-list">
|
||||
<li>Aggregates data from multiple remote INTERCEPT agents</li>
|
||||
<li>View all WiFi, Bluetooth, ADS-B, AIS data in one unified view</li>
|
||||
<li>Real-time streaming via Server-Sent Events (SSE)</li>
|
||||
<li>Location estimation using multi-agent trilateration</li>
|
||||
<li>Manage agents at <code>/controller/manage</code></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- WiFi Section -->
|
||||
<div id="help-wifi" class="help-section">
|
||||
<h3>Monitor Mode</h3>
|
||||
<ul class="tip-list">
|
||||
<li><strong>Enable Monitor:</strong> Puts WiFi adapter in monitor mode for passive scanning</li>
|
||||
<li><strong>Kill Processes:</strong> Optional - stops NetworkManager/wpa_supplicant (may drop other connections)</li>
|
||||
<li>Some adapters rename when entering monitor mode (e.g., wlan0 → wlan0mon)</li>
|
||||
</ul>
|
||||
|
||||
<h3>Handshake Capture</h3>
|
||||
<ul class="tip-list">
|
||||
<li>Click "Capture" on a network to start targeted handshake capture</li>
|
||||
<li>Status panel shows capture progress and file location</li>
|
||||
<li>Use deauth to force clients to reconnect (only on authorized networks!)</li>
|
||||
<li>Handshake files saved to /tmp/intercept_handshake_*.cap</li>
|
||||
</ul>
|
||||
|
||||
<h3>Drone Detection</h3>
|
||||
<ul class="tip-list">
|
||||
<li>Drones detected by SSID patterns (DJI, Parrot, Autel, etc.)</li>
|
||||
<li>Also detected by manufacturer OUI in MAC address</li>
|
||||
<li>Distance estimated from signal strength (approximate)</li>
|
||||
<li>Click drone count in stats bar to see all detected drones</li>
|
||||
</ul>
|
||||
|
||||
<h3>Rogue AP Detection</h3>
|
||||
<ul class="tip-list">
|
||||
<li>Flags networks where same SSID appears on multiple BSSIDs</li>
|
||||
<li>Could indicate evil twin attack or legitimate multi-AP setup</li>
|
||||
<li>Click rogue count to see which SSIDs are flagged</li>
|
||||
</ul>
|
||||
|
||||
<h3>Proximity Alerts</h3>
|
||||
<ul class="tip-list">
|
||||
<li>Add MAC addresses to watch list for alerts when detected</li>
|
||||
<li>Watch list persists in browser localStorage</li>
|
||||
<li>Useful for tracking specific devices</li>
|
||||
</ul>
|
||||
|
||||
<h3>Client Probe Analysis</h3>
|
||||
<ul class="tip-list">
|
||||
<li>Shows what networks client devices are looking for</li>
|
||||
<li>Orange highlights indicate sensitive/private network names</li>
|
||||
<li>Reveals user location history (home, work, hotels, airports)</li>
|
||||
<li>Useful for security awareness and pen test reports</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Tips Section -->
|
||||
<div id="help-tips" class="help-section">
|
||||
<h3>General Tips</h3>
|
||||
<ul class="tip-list">
|
||||
<li><strong>Collapsible sections:</strong> Click any section header (∇) to collapse/expand</li>
|
||||
<li><strong>Sound alerts:</strong> Toggle sound on/off in settings for each mode</li>
|
||||
<li><strong>Export data:</strong> Use export buttons to save captured data as JSON</li>
|
||||
<li><strong>Device Intelligence:</strong> Tracks device patterns over time</li>
|
||||
<li><strong>Theme toggle:</strong> Click the theme button in header to switch dark/light mode</li>
|
||||
<li><strong>Settings:</strong> Click the gear icon in the header to access settings</li>
|
||||
<li><strong>Offline mode:</strong> Enable in Settings to use local assets without internet</li>
|
||||
</ul>
|
||||
|
||||
<h3>Keyboard Shortcuts</h3>
|
||||
<ul class="tip-list">
|
||||
<li><strong>F1</strong> - Open this help page</li>
|
||||
<li><strong>?</strong> - Open help (when not typing in a field)</li>
|
||||
<li><strong>Escape</strong> - Close help and modal dialogs</li>
|
||||
</ul>
|
||||
|
||||
<h3>Requirements</h3>
|
||||
<ul class="tip-list">
|
||||
<li><strong>Pager:</strong> RTL-SDR, rtl_fm, multimon-ng</li>
|
||||
<li><strong>433MHz Sensors:</strong> RTL-SDR, rtl_433</li>
|
||||
<li><strong>Utility Meters:</strong> RTL-SDR, rtl_tcp, rtlamr</li>
|
||||
<li><strong>Aircraft (ADS-B):</strong> RTL-SDR, dump1090 or rtl_adsb</li>
|
||||
<li><strong>Aircraft (ACARS):</strong> Second RTL-SDR, acarsdec</li>
|
||||
<li><strong>Vessels (AIS):</strong> RTL-SDR, AIS-catcher</li>
|
||||
<li><strong>APRS:</strong> RTL-SDR, direwolf or multimon-ng</li>
|
||||
<li><strong>Satellite:</strong> Internet for Celestrak (optional), skyfield</li>
|
||||
<li><strong>WiFi:</strong> Monitor-mode adapter, aircrack-ng suite</li>
|
||||
<li><strong>Bluetooth:</strong> Bluetooth adapter, bluez (hcitool/bluetoothctl)</li>
|
||||
<li><strong>Listening Post:</strong> RTL-SDR or SoapySDR-compatible hardware</li>
|
||||
<li><strong>TSCM:</strong> WiFi adapter, Bluetooth adapter, RTL-SDR (all optional)</li>
|
||||
<li>Run as root/sudo for full hardware access</li>
|
||||
</ul>
|
||||
|
||||
<h3>Legal Notice</h3>
|
||||
<ul class="tip-list">
|
||||
<li>Only use on networks and devices you own or have authorization to test</li>
|
||||
<li>Passive monitoring may be legal; active attacks require authorization</li>
|
||||
<li>Check local laws regarding radio frequency monitoring</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Help modal functions - defined here so all pages have them
|
||||
(function() {
|
||||
// Only define if not already defined (index.html defines its own)
|
||||
if (typeof window.showHelp === 'undefined') {
|
||||
window.showHelp = function() {
|
||||
document.getElementById('helpModal').classList.add('active');
|
||||
document.body.style.overflow = 'hidden';
|
||||
};
|
||||
}
|
||||
|
||||
if (typeof window.hideHelp === 'undefined') {
|
||||
window.hideHelp = function() {
|
||||
document.getElementById('helpModal').classList.remove('active');
|
||||
document.body.style.overflow = '';
|
||||
};
|
||||
}
|
||||
|
||||
if (typeof window.switchHelpTab === 'undefined') {
|
||||
window.switchHelpTab = function(tab) {
|
||||
document.querySelectorAll('.help-tab').forEach(t => t.classList.remove('active'));
|
||||
document.querySelectorAll('.help-section').forEach(s => s.classList.remove('active'));
|
||||
document.querySelector('.help-tab[data-tab="' + tab + '"]').classList.add('active');
|
||||
document.getElementById('help-' + tab).classList.add('active');
|
||||
};
|
||||
}
|
||||
|
||||
// Keyboard shortcuts for help (only add once)
|
||||
if (!window._helpKeyboardSetup) {
|
||||
window._helpKeyboardSetup = true;
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') hideHelp();
|
||||
// Open help with F1 or ? key (when not typing in an input)
|
||||
var helpModal = document.getElementById('helpModal');
|
||||
if (helpModal && (e.key === 'F1' || (e.key === '?' && !e.target.matches('input, textarea, select'))) && !helpModal.classList.contains('active')) {
|
||||
e.preventDefault();
|
||||
showHelp();
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
@@ -134,6 +134,11 @@
|
||||
<span class="utc-time" id="headerUtcTime">--:--:--</span>
|
||||
</div>
|
||||
<div class="nav-divider"></div>
|
||||
<a href="/" class="nav-dashboard-btn" title="Return to Main Dashboard" style="text-decoration: none;">
|
||||
<span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg></span>
|
||||
<span class="nav-label">Main Dashboard</span>
|
||||
</a>
|
||||
<div class="nav-divider"></div>
|
||||
<div class="nav-tools">
|
||||
<button class="nav-tool-btn" onclick="toggleAnimations()" title="Toggle Animations">
|
||||
<span class="icon-effects-on icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg></span>
|
||||
@@ -235,17 +240,33 @@
|
||||
|
||||
if (typeof showSettings === 'undefined') {
|
||||
window.showSettings = function() {
|
||||
// Navigate to main page settings
|
||||
window.location.href = '/?settings=1';
|
||||
// Try to open settings modal if it exists on this page
|
||||
const modal = document.getElementById('settingsModal');
|
||||
if (modal) {
|
||||
modal.classList.add('active');
|
||||
if (typeof Settings !== 'undefined' && Settings.init) {
|
||||
Settings.init().then(() => {
|
||||
if (Settings.checkAssets) Settings.checkAssets();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Fall back to navigating to main page settings
|
||||
window.location.href = '/?settings=1';
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (typeof showHelp === 'undefined') {
|
||||
window.showHelp = function() {
|
||||
window.open('https://smittix.github.io/intercept', '_blank');
|
||||
if (typeof hideSettings === 'undefined') {
|
||||
window.hideSettings = function() {
|
||||
const modal = document.getElementById('settingsModal');
|
||||
if (modal) {
|
||||
modal.classList.remove('active');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// showHelp is defined by the help-modal.html partial
|
||||
|
||||
if (typeof logout === 'undefined') {
|
||||
window.logout = function(e) {
|
||||
if (e) e.preventDefault();
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/core/variables.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/responsive.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/global-nav.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/settings.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/help-modal.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/satellite_dashboard.css') }}">
|
||||
<script>
|
||||
window.INTERCEPT_SHARED_OBSERVER_LOCATION = {{ shared_observer_location | tojson }};
|
||||
@@ -69,7 +71,6 @@
|
||||
<span id="trackingStatus">TRACKING</span>
|
||||
</div>
|
||||
<div class="datetime" id="utcTime">--:--:-- UTC</div>
|
||||
<a href="/?mode=satellite" class="back-link">Main Dashboard</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -437,7 +438,7 @@
|
||||
now.toISOString().substring(11, 19) + ' UTC';
|
||||
}
|
||||
|
||||
function initGroundMap() {
|
||||
async function initGroundMap() {
|
||||
groundMap = L.map('groundMap', {
|
||||
center: [20, 0],
|
||||
zoom: 2,
|
||||
@@ -448,7 +449,9 @@
|
||||
|
||||
// Use settings manager for tile layer (allows runtime changes)
|
||||
window.groundMap = groundMap;
|
||||
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(groundMap);
|
||||
Settings.registerMap(groundMap);
|
||||
} else {
|
||||
@@ -1085,6 +1088,14 @@
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Settings Modal -->
|
||||
{% include 'partials/settings-modal.html' %}
|
||||
|
||||
<!-- Help Modal -->
|
||||
{% include 'partials/help-modal.html' %}
|
||||
|
||||
<script src="{{ url_for('static', filename='js/core/settings-manager.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/core/global-nav.js') }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user