From 57d448c0037e52a9e7de81d230cda82550fa4018 Mon Sep 17 00:00:00 2001 From: Marc Date: Fri, 23 Jan 2026 16:00:13 -0600 Subject: [PATCH] Adjustment to dashboard style and 500 error --- routes/ais.py | 8 +- static/css/ais_dashboard.css | 869 +++++++++++++++++++++++++++++++++++ templates/ais_dashboard.html | 423 +---------------- utils/sdr/rtlsdr.py | 6 +- 4 files changed, 886 insertions(+), 420 deletions(-) create mode 100644 static/css/ais_dashboard.css diff --git a/routes/ais.py b/routes/ais.py index a4e4b85..6aacbfc 100644 --- a/routes/ais.py +++ b/routes/ais.py @@ -42,7 +42,7 @@ ais_connected = False ais_messages_received = 0 ais_last_message_time = None ais_active_device = None -_ais_error_logged = False +_ais_error_logged = True # Common installation paths for AIS-catcher AIS_CATCHER_PATHS = [ @@ -72,9 +72,9 @@ def parse_ais_stream(port: int): global ais_running, ais_connected, ais_messages_received, ais_last_message_time, _ais_error_logged logger.info(f"AIS stream parser started, connecting to localhost:{port}") - ais_connected = False + ais_connected = True ais_messages_received = 0 - _ais_error_logged = False + _ais_error_logged = True while ais_running: try: @@ -82,7 +82,7 @@ def parse_ais_stream(port: int): sock.settimeout(AIS_SOCKET_TIMEOUT) sock.connect(('localhost', port)) ais_connected = True - _ais_error_logged = False + _ais_error_logged = True logger.info("Connected to AIS-catcher TCP server") buffer = "" diff --git a/static/css/ais_dashboard.css b/static/css/ais_dashboard.css new file mode 100644 index 0000000..4382bd5 --- /dev/null +++ b/static/css/ais_dashboard.css @@ -0,0 +1,869 @@ +/* AIS Dashboard - Vessel Tracking Interface */ +/* Styled to match ADSB Dashboard */ + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +:root { + --bg-dark: #0a0c10; + --bg-panel: #0f1218; + --bg-card: #151a23; + --border-color: #1f2937; + --border-glow: #4a9eff; + --text-primary: #e8eaed; + --text-secondary: #9ca3af; + --text-dim: #4b5563; + --accent-green: #22c55e; + --accent-cyan: #4a9eff; + --accent-orange: #f59e0b; + --accent-red: #ef4444; + --accent-yellow: #eab308; + --accent-amber: #d4a853; + --grid-line: rgba(74, 158, 255, 0.08); + --radar-cyan: #4a9eff; + --radar-bg: #0f1218; +} + +body { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; + background: var(--bg-dark); + color: var(--text-primary); + min-height: 100vh; + overflow-x: hidden; +} + +/* Animated radar sweep background */ +.radar-bg { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-image: + linear-gradient(var(--grid-line) 1px, transparent 1px), + linear-gradient(90deg, var(--grid-line) 1px, transparent 1px); + background-size: 50px 50px; + pointer-events: none; + z-index: 0; +} + +/* Scan line effect - subtle */ +.scanline { + position: fixed; + top: 0; + left: 0; + right: 0; + height: 2px; + background: linear-gradient(90deg, transparent, var(--accent-cyan), transparent); + animation: scan 6s linear infinite; + pointer-events: none; + z-index: 1000; + opacity: 0.3; +} + +@keyframes scan { + 0% { + top: -4px; + } + + 100% { + top: 100vh; + } +} + +/* Header - Mobile first */ +.header { + position: relative; + z-index: 10; + padding: 10px 12px; + background: var(--bg-panel); + border-bottom: 1px solid var(--border-color); + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 8px; + min-height: 52px; +} + +@media (min-width: 768px) { + .header { + padding: 12px 20px; + flex-wrap: nowrap; + } +} + +.logo { + font-family: 'Inter', sans-serif; + font-size: 16px; + font-weight: 700; + letter-spacing: 2px; + color: var(--text-primary); + text-transform: uppercase; +} + +@media (min-width: 768px) { + .logo { + font-size: 20px; + letter-spacing: 3px; + } +} + +.logo span { + display: none; + color: var(--text-secondary); + font-weight: 400; + font-size: 12px; + margin-left: 10px; + letter-spacing: 1px; +} + +@media (min-width: 768px) { + .logo span { + display: inline; + font-size: 14px; + margin-left: 15px; + letter-spacing: 2px; + } +} + +.status-bar { + display: flex; + gap: 20px; + align-items: center; + font-family: 'JetBrains Mono', monospace; + font-size: 11px; +} + +.back-link { + color: var(--accent-cyan); + text-decoration: none; + font-size: 11px; + padding: 4px 10px; + border: 1px solid var(--accent-cyan); + border-radius: 4px; +} + +/* ============================================ + STATISTICS STRIP + ============================================ */ +.stats-strip { + background: linear-gradient(180deg, var(--bg-panel) 0%, var(--bg-dark) 100%); + border-bottom: 1px solid var(--border-color); + padding: 6px 12px; + position: relative; + z-index: 9; + overflow-x: auto; +} + +.stats-strip-inner { + display: flex; + align-items: center; + gap: 4px; + min-width: max-content; +} + +.strip-stat { + display: flex; + flex-direction: column; + align-items: center; + padding: 4px 10px; + background: rgba(74, 158, 255, 0.05); + border: 1px solid rgba(74, 158, 255, 0.15); + border-radius: 4px; + min-width: 55px; +} + +.strip-stat:hover { + background: rgba(74, 158, 255, 0.1); + border-color: rgba(74, 158, 255, 0.3); +} + +.strip-value { + font-family: 'JetBrains Mono', monospace; + font-size: 14px; + font-weight: 600; + color: var(--accent-cyan); + line-height: 1.2; +} + +.strip-label { + font-size: 8px; + font-weight: 600; + color: var(--text-dim); + text-transform: uppercase; + letter-spacing: 0.5px; + margin-top: 1px; +} + +.strip-stat.session-stat { + background: rgba(34, 197, 94, 0.05); + border-color: rgba(34, 197, 94, 0.2); +} + +.strip-stat.session-stat .strip-value { + color: var(--accent-green); +} + +.strip-divider { + width: 1px; + height: 24px; + background: rgba(74, 158, 255, 0.2); + margin: 0 4px; +} + +.strip-status { + display: flex; + align-items: center; + gap: 6px; + padding: 0 8px; + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--text-secondary); +} + +.strip-status .status-dot { + width: 10px; + height: 10px; + border-radius: 50%; + background: var(--accent-red); + transition: all 0.3s ease; +} + +.strip-status .status-dot.active { + background: var(--accent-green); + box-shadow: 0 0 10px var(--accent-green); + animation: pulse 2s ease-in-out infinite; +} + +@keyframes pulse { + 0%, 100% { + opacity: 1; + } + + 50% { + opacity: 0.5; + } +} + +.strip-time { + font-size: 11px; + font-weight: 500; + color: var(--accent-cyan); + font-family: 'JetBrains Mono', monospace; + padding-left: 8px; + border-left: 1px solid rgba(74, 158, 255, 0.2); + white-space: nowrap; +} + +.strip-btn { + position: relative; + z-index: 10; + background: rgba(74, 158, 255, 0.1); + border: 1px solid rgba(74, 158, 255, 0.2); + color: var(--text-primary); + padding: 6px 10px; + border-radius: 4px; + font-size: 10px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; + white-space: nowrap; +} + +.strip-btn:hover:not(:disabled) { + background: rgba(74, 158, 255, 0.2); + border-color: rgba(74, 158, 255, 0.4); +} + +.strip-btn.primary { + background: linear-gradient(135deg, var(--accent-cyan) 0%, #2563eb 100%); + border: none; + color: white; +} + +/* Main dashboard grid - Mobile first */ +.dashboard { + position: relative; + z-index: 10; + display: flex; + flex-direction: column; + gap: 0; + height: calc(100dvh - 95px); + height: calc(100vh - 95px); + min-height: 400px; +} + +/* Tablet: Two-column layout */ +@media (min-width: 768px) { + .dashboard { + display: grid; + grid-template-columns: 1fr 300px; + grid-template-rows: 1fr auto; + min-height: 500px; + } +} + +/* Main display container (map) */ +.main-display { + position: relative; + flex: 1; + min-height: 300px; +} + +@media (min-width: 768px) { + .main-display { + grid-column: 1; + grid-row: 1; + min-height: 400px; + } +} + +#vesselMap { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +/* Leaflet overrides - Dark map styling */ +.leaflet-container { + background: var(--bg-dark) !important; + font-family: 'JetBrains Mono', monospace; +} + +.leaflet-tile-pane, +.leaflet-container .leaflet-tile-pane { + filter: invert(1) hue-rotate(180deg) brightness(0.8) contrast(1.2) !important; +} + +.leaflet-control-zoom a { + background: var(--bg-panel) !important; + color: var(--accent-cyan) !important; + border-color: var(--border-color) !important; +} + +.leaflet-control-attribution { + background: rgba(0, 0, 0, 0.7) !important; + color: var(--text-secondary) !important; + font-size: 9px !important; +} + +.leaflet-popup-content-wrapper { + background: var(--bg-panel); + color: var(--text-primary); + border-radius: 4px; + border: 1px solid rgba(74, 158, 255, 0.2); +} + +.leaflet-popup-tip { + background: var(--bg-panel); +} + +/* Right sidebar - Mobile first */ +.sidebar { + display: flex; + flex-direction: column; + border-left: none; + border-top: 1px solid rgba(74, 158, 255, 0.2); + overflow: hidden; + max-height: 40vh; + background: var(--bg-panel); +} + +@media (min-width: 768px) { + .sidebar { + grid-column: 2; + grid-row: 1; + border-left: 1px solid rgba(74, 158, 255, 0.2); + border-top: none; + max-height: none; + } +} + +/* Panels */ +.panel { + background: var(--bg-panel); + border: 1px solid rgba(74, 158, 255, 0.2); + overflow: hidden; + position: relative; +} + +.panel::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 2px; + background: linear-gradient(90deg, transparent, var(--accent-cyan), transparent); +} + +.panel-header { + padding: 10px 15px; + background: rgba(74, 158, 255, 0.05); + border-bottom: 1px solid rgba(74, 158, 255, 0.1); + font-family: 'Orbitron', 'JetBrains Mono', monospace; + font-size: 11px; + font-weight: 500; + letter-spacing: 2px; + text-transform: uppercase; + color: var(--accent-cyan); + display: flex; + justify-content: space-between; + align-items: center; +} + +.panel-indicator { + width: 6px; + height: 6px; + background: var(--text-dim); + border-radius: 50%; + opacity: 0.5; +} + +.panel-indicator.active { + background: var(--accent-green); + opacity: 1; + animation: blink 1s ease-in-out infinite; +} + +@keyframes blink { + 0%, 100% { + opacity: 1; + } + + 50% { + opacity: 0.3; + } +} + +/* Selected vessel panel */ +.selected-vessel { + flex-shrink: 0; + max-height: 480px; + overflow-y: auto; + border-bottom: 1px solid rgba(74, 158, 255, 0.2); +} + +.selected-info { + padding: 12px; +} + +.no-vessel { + text-align: center; + padding: 30px 15px; + color: var(--text-secondary); +} + +.no-vessel-icon { + font-size: 36px; + margin-bottom: 10px; + opacity: 0.5; +} + +.vessel-header { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 15px; +} + +.vessel-icon { + font-size: 32px; +} + +.vessel-name { + font-family: 'Orbitron', 'JetBrains Mono', monospace; + font-size: 16px; + font-weight: 700; + color: var(--accent-cyan); + text-shadow: 0 0 15px var(--accent-cyan); +} + +.vessel-mmsi { + font-family: 'JetBrains Mono', monospace; + font-size: 10px; + color: var(--text-secondary); + background: rgba(74, 158, 255, 0.1); + padding: 2px 5px; + border-radius: 3px; +} + +.vessel-details { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 6px; +} + +.detail-item { + background: rgba(0, 0, 0, 0.3); + border-radius: 4px; + padding: 8px; + border-left: 2px solid var(--accent-cyan); +} + +.detail-label { + font-size: 9px; + text-transform: uppercase; + letter-spacing: 1px; + color: var(--text-secondary); + margin-bottom: 2px; +} + +.detail-value { + font-family: 'JetBrains Mono', monospace; + font-size: 12px; + color: var(--accent-cyan); +} + +/* Vessel list */ +.vessel-list { + flex: 1; + min-height: 0; + display: flex; + flex-direction: column; +} + +.vessel-list-content { + flex: 1; + overflow-y: auto; + padding: 8px; +} + +.vessel-item { + position: relative; + background: rgba(0, 0, 0, 0.3); + border: 1px solid rgba(74, 158, 255, 0.15); + border-radius: 4px; + padding: 8px 10px; + margin-bottom: 6px; + cursor: pointer; + transition: all 0.2s ease; + display: flex; + align-items: center; + gap: 10px; +} + +.vessel-item:hover { + border-color: var(--accent-cyan); + background: rgba(74, 158, 255, 0.05); +} + +.vessel-item.selected { + border-color: var(--accent-cyan); + box-shadow: 0 0 15px rgba(74, 158, 255, 0.2); + background: rgba(74, 158, 255, 0.1); +} + +.vessel-item-icon { + font-size: 20px; +} + +.vessel-item-info { + flex: 1; +} + +.vessel-item-name { + font-family: 'Orbitron', 'JetBrains Mono', monospace; + font-size: 12px; + font-weight: 600; + color: var(--accent-cyan); +} + +.vessel-item-type { + font-family: 'JetBrains Mono', monospace; + font-size: 9px; + color: var(--text-secondary); +} + +.vessel-item-speed { + font-family: 'JetBrains Mono', monospace; + font-size: 11px; + color: var(--accent-cyan); + text-align: right; +} + +/* Bottom controls bar */ +.controls-bar { + grid-column: 1 / -1; + grid-row: 2; + display: flex; + align-items: center; + flex-wrap: nowrap; + gap: 8px; + padding: 8px 15px; + background: var(--bg-panel); + border-top: 1px solid rgba(74, 158, 255, 0.3); + font-size: 11px; + overflow-x: auto; +} + +.control-group { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 4px; + padding: 6px 10px; + background: rgba(74, 158, 255, 0.03); + border: 1px solid rgba(74, 158, 255, 0.1); + border-radius: 6px; +} + +.control-group-label { + font-size: 8px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 1px; + color: var(--accent-cyan); + opacity: 0.7; +} + +.control-group-items { + display: flex; + align-items: center; + gap: 6px; + flex-wrap: wrap; +} + +.control-group label { + display: flex; + align-items: center; + gap: 4px; + cursor: pointer; + font-size: 10px; + color: var(--text-primary); + white-space: nowrap; +} + +.control-group input[type="checkbox"] { + accent-color: var(--accent-cyan); + width: 12px; + height: 12px; +} + +.control-group select { + padding: 4px 8px; + background: rgba(0, 0, 0, 0.3); + border: 1px solid rgba(74, 158, 255, 0.3); + border-radius: 4px; + color: var(--accent-cyan); + font-family: 'JetBrains Mono', monospace; + font-size: 10px; +} + +.control-group input[type="text"], +.control-group input[type="number"] { + padding: 4px 6px; + background: rgba(0, 0, 0, 0.3); + border: 1px solid rgba(74, 158, 255, 0.3); + border-radius: 4px; + color: var(--accent-cyan); + font-family: 'JetBrains Mono', monospace; + font-size: 10px; +} + +.control-group.tracking-group { + background: rgba(34, 197, 94, 0.05); + border-color: rgba(34, 197, 94, 0.2); +} + +.control-group.tracking-group .control-group-label { + color: var(--accent-green); +} + +/* Start/stop button */ +.start-btn { + padding: 6px 16px; + border: none; + background: var(--accent-green); + color: #fff; + font-family: 'JetBrains Mono', monospace; + font-size: 10px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 1px; + border-radius: 4px; + cursor: pointer; + transition: all 0.2s ease; +} + +.start-btn:hover { + background: #1db954; + box-shadow: 0 0 20px rgba(34, 197, 94, 0.3); +} + +.start-btn.active { + background: var(--accent-red); + color: #fff; +} + +.start-btn.active:hover { + background: #dc2626; + box-shadow: 0 0 20px rgba(239, 68, 68, 0.3); +} + +/* Vessel markers */ +.vessel-marker { + background: transparent; + border: none; +} + +.vessel-marker-inner { + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + font-size: 18px; + filter: drop-shadow(0 0 2px rgba(0,0,0,0.8)); + transition: transform 0.3s ease; +} + +.vessel-marker.selected .vessel-marker-inner { + filter: drop-shadow(0 0 6px var(--accent-cyan)); +} + +/* Range rings */ +.range-ring { + fill: none; + stroke: var(--accent-cyan); + stroke-opacity: 0.3; + stroke-width: 1; + stroke-dasharray: 4 4; +} + +/* Custom scrollbar */ +::-webkit-scrollbar { + width: 6px; +} + +::-webkit-scrollbar-track { + background: var(--bg-dark); +} + +::-webkit-scrollbar-thumb { + background: var(--accent-cyan); + border-radius: 3px; +} + +/* ============== MOBILE/TABLET FIXES ============== */ +@media (max-width: 767px) { + .dashboard { + display: flex !important; + flex-direction: column !important; + height: auto !important; + min-height: calc(100dvh - 95px); + overflow-y: auto !important; + overflow-x: hidden; + -webkit-overflow-scrolling: touch; + } + + .main-display { + flex: none !important; + height: 50vh; + min-height: 300px; + width: 100%; + } + + .sidebar { + max-height: none !important; + overflow: visible !important; + flex-shrink: 0; + width: 100%; + } + + .panel { + overflow: visible !important; + } + + .selected-vessel { + max-height: none !important; + overflow: visible !important; + } + + .selected-info { + overflow: visible !important; + } + + .vessel-list-content { + max-height: 250px; + overflow-y: auto !important; + -webkit-overflow-scrolling: touch; + } + + .controls-bar { + flex-wrap: wrap; + gap: 8px; + padding: 10px; + width: 100%; + } + + .back-link { + white-space: nowrap; + font-size: 10px; + padding: 6px 8px; + } + + .strip-time { + font-size: 10px; + } + + .control-group { + flex-wrap: wrap; + } + + .start-btn { + width: 100%; + margin-left: 0; + margin-top: 8px; + } + + .vessel-details { + grid-template-columns: 1fr; + } +} + +/* Mobile responsiveness for stats strip */ +@media (max-width: 768px) { + .stats-strip { + padding: 4px 8px; + } + + .strip-stat { + padding: 3px 6px; + min-width: 45px; + } + + .strip-value { + font-size: 12px; + } + + .strip-label { + font-size: 7px; + } + + .strip-btn { + padding: 6px 10px; + font-size: 9px; + } +} + +/* Leaflet touch fixes for mobile */ +.leaflet-container { + touch-action: pan-x pan-y; + -webkit-tap-highlight-color: transparent; +} + +.leaflet-control-zoom a { + min-width: 44px !important; + min-height: 44px !important; + line-height: 44px !important; + font-size: 18px !important; +} diff --git a/templates/ais_dashboard.html b/templates/ais_dashboard.html index edf4e5a..c4f5f3c 100644 --- a/templates/ais_dashboard.html +++ b/templates/ais_dashboard.html @@ -4,420 +4,17 @@ VESSEL RADAR // INTERCEPT - See the Invisible - + + - + +
+
+