Remove legacy RF modes and add SignalID route/tests

This commit is contained in:
Smittix
2026-02-23 13:34:00 +00:00
parent 5f480caa3f
commit 7ea06caaa2
35 changed files with 3883 additions and 6920 deletions
+11 -11
View File
@@ -43,7 +43,7 @@
<div class="icon-item"><span class="icon icon--sm"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16v-2l-8-5V3.5a1.5 1.5 0 0 0-3 0V9l-8 5v2l8-2.5V19l-2 1.5V22l3.5-1 3.5 1v-1.5L13 19v-5.5l8 2.5z"/></svg></span><span class="desc">Aircraft - ADS-B tracking &amp; history</span></div>
<div class="icon-item"><span class="icon icon--sm"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 18l2 2h14l2-2"/><path d="M5 18v-4a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v4"/><path d="M12 12V6"/><path d="M12 6l4 3"/></svg></span><span class="desc">Vessels - AIS &amp; VHF DSC distress</span></div>
<div class="icon-item"><span class="icon icon--sm"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0Z"/><circle cx="12" cy="10" r="3"/></svg></span><span class="desc">APRS - Amateur radio tracking</span></div>
<div class="icon-item"><span class="icon icon--sm"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18"/><path d="M9 21V9"/></svg></span><span class="desc">Listening Post - SDR scanner</span></div>
<div class="icon-item"><span class="icon icon--sm"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12h4l3-8 3 16 3-8h4"/><path d="M2 18h20" opacity="0.4"/><path d="M2 21h20" opacity="0.2"/></svg></span><span class="desc">Waterfall - SDR receiver + signal ID</span></div>
<div class="icon-item"><span class="icon icon--sm"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4.9 19.1C1 15.2 1 8.8 4.9 4.9"/><circle cx="12" cy="12" r="2"/><path d="M19.1 4.9C23 8.8 23 15.1 19.1 19"/></svg></span><span class="desc">Spy Stations - Number stations database</span></div>
<div class="icon-item"><span class="icon icon--sm"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="3"/><path d="M12 2v4m0 12v4M2 12h4m12 0h4"/></svg></span><span class="desc">Meshtastic - LoRa mesh networking</span></div>
<div class="icon-item"><span class="icon icon--sm"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg></span><span class="desc">WebSDR - Remote SDR receivers</span></div>
@@ -114,14 +114,14 @@
<li>Interactive map shows station positions in real-time</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>Spectrum Waterfall 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>Spy Stations</h3>
<ul class="tip-list">
@@ -129,7 +129,7 @@
<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>
<li>Click "Tune" to listen via Spectrum Waterfall mode</li>
</ul>
<h3>Meshtastic Mode</h3>
@@ -330,7 +330,7 @@
<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>Listening Post:</strong> RTL-SDR or SoapySDR-compatible hardware</li>
<li><strong>Spectrum Waterfall:</strong> RTL-SDR or SoapySDR-compatible hardware</li>
<li><strong>Spy Stations:</strong> Internet connection (database lookup)</li>
<li><strong>Meshtastic:</strong> Meshtastic LoRa device, <code>pip install meshtastic</code></li>
<li><strong>WebSDR:</strong> Internet connection (remote receivers)</li>
-115
View File
@@ -1,115 +0,0 @@
<!-- FINGERPRINT MODE -->
<div id="fingerprintMode" class="mode-content">
<!-- Intro -->
<div class="section">
<div style="font-size:11px; color:var(--text-dim); line-height:1.6;">
RF Fingerprinting captures the baseline radio environment at a location.
Record a baseline when the environment is "clean", then compare later to
detect new transmitters, surveillance devices, or signal anomalies.
</div>
</div>
<!-- Workflow tab selector -->
<div class="section">
<h3>Workflow</h3>
<div style="display:flex; gap:4px;">
<button class="fp-tab-btn active" id="fpTabRecord" onclick="Fingerprint.showTab('record')">
1 — Record
</button>
<button class="fp-tab-btn" id="fpTabCompare" onclick="Fingerprint.showTab('compare')">
2 — Compare
</button>
</div>
<div id="fpTabHint" style="margin-top:8px; font-size:11px; color:var(--text-dim); line-height:1.5;">
Record a <strong style="color:var(--text-secondary);">baseline</strong> in a known-clean RF environment, then use <strong style="color:var(--text-secondary);">Compare</strong> later to detect new or anomalous signals.
</div>
</div>
<!-- Record tab -->
<div id="fpRecordPanel">
<div class="section">
<h3>Step 1 — Select Device</h3>
<div class="form-group">
<label>SDR Device</label>
<select id="fpDevice">
<option value="">Loading…</option>
</select>
</div>
</div>
<div class="section">
<h3>Step 2 — Scanner Status</h3>
<div style="display:flex; align-items:center; gap:8px; padding:6px 0;">
<span id="fpScannerDot" style="width:8px; height:8px; border-radius:50%; background:rgba(255,255,255,0.2); flex-shrink:0;"></span>
<span id="fpScannerStatusText" style="font-size:11px; color:var(--text-secondary); flex:1;">Checking…</span>
</div>
<div style="display:flex; gap:6px;">
<button class="run-btn" id="fpScannerStartBtn" onclick="Fingerprint.startScanner()" style="flex:1;">Start Scanner</button>
<button class="stop-btn" id="fpScannerStopBtn" onclick="Fingerprint.stopScanner()" style="flex:1; display:none;">Stop Scanner</button>
</div>
</div>
<div class="section">
<h3>Step 3 — Record Baseline</h3>
<div class="form-group">
<label>Session Name</label>
<input type="text" id="fpSessionName" placeholder="e.g. Office — Mon morning">
</div>
<div class="form-group">
<label>Location <span style="color:var(--text-dim); font-weight:normal;">(optional)</span></label>
<input type="text" id="fpSessionLocation" placeholder="e.g. 3rd floor, room 301">
</div>
<div style="display:flex; align-items:center; gap:10px; margin:6px 0;">
<span style="font-size:10px; color:var(--text-dim); text-transform:uppercase; letter-spacing:.05em;">Observations</span>
<span id="fpObsCount" style="font-size:14px; font-family:var(--font-mono); color:var(--accent-cyan, #4aa3ff);">0</span>
</div>
<div id="fpRecordStatus" style="font-size:11px; color:var(--text-dim); margin-bottom:6px; min-height:14px;"></div>
<button class="run-btn" id="fpStartBtn" onclick="Fingerprint.startRecording()">Start Recording</button>
<button class="stop-btn" id="fpStopBtn" style="display:none;" onclick="Fingerprint.stopRecording()">Stop &amp; Save</button>
</div>
</div>
<!-- Compare tab -->
<div id="fpComparePanel" style="display:none;">
<div class="section">
<h3>How It Works</h3>
<div style="font-size:11px; color:var(--text-dim); line-height:1.6;">
<div style="display:flex; gap:8px; align-items:flex-start; margin-bottom:6px;">
<span style="color:var(--accent-cyan); font-weight:700; flex-shrink:0;">1.</span>
<span>Ensure the scanner is running (switch to Record tab to start it).</span>
</div>
<div style="display:flex; gap:8px; align-items:flex-start; margin-bottom:6px;">
<span style="color:var(--accent-cyan); font-weight:700; flex-shrink:0;">2.</span>
<span>Select a previously recorded baseline below.</span>
</div>
<div style="display:flex; gap:8px; align-items:flex-start; margin-bottom:6px;">
<span style="color:var(--accent-cyan); font-weight:700; flex-shrink:0;">3.</span>
<span>Click <strong style="color:var(--text-secondary);">Compare Now</strong> — a 3-second live scan is collected.</span>
</div>
<div style="display:flex; gap:8px; align-items:flex-start;">
<span style="color:var(--accent-cyan); font-weight:700; flex-shrink:0;">4.</span>
<span>Anomalies are scored by z-score. <span style="color:#ef4444;">Red = strong deviation</span>, <span style="color:#a855f7;">purple = new signal</span>.</span>
</div>
</div>
</div>
<div class="section">
<h3>Baseline</h3>
<div class="form-group">
<label>Session</label>
<select id="fpBaselineSelect">
<option value="">No baselines saved yet</option>
</select>
</div>
<div id="fpCompareStatus" style="font-size:11px; color:var(--text-dim); margin-bottom:6px; min-height:14px;"></div>
<button class="run-btn" onclick="Fingerprint.compareNow()">Compare Now</button>
</div>
<div class="section" id="fpAnomalyList" style="display:none;">
<h3>Anomalies</h3>
<div id="fpAnomalyItems"></div>
</div>
</div>
</div>
@@ -1,68 +0,0 @@
<!-- LISTENING POST MODE -->
<div id="listeningPostMode" class="mode-content">
<div class="section">
<h3>Status</h3>
<!-- Dependency Warning -->
<div id="scannerToolsWarning" style="display: none; background: rgba(255, 100, 100, 0.1); border: 1px solid var(--accent-red); border-radius: 4px; padding: 10px; margin-bottom: 10px;">
<p style="color: var(--accent-red); margin: 0; font-size: 0.85em;">
<strong>Missing:</strong><br>
<span id="scannerToolsWarningText"></span>
</p>
</div>
<!-- Quick Status -->
<div style="background: rgba(0,0,0,0.3); border-radius: 6px; padding: 12px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
<span style="font-size: 10px; color: var(--text-muted); text-transform: uppercase;">Status</span>
<span id="lpQuickStatus" style="font-size: 11px; color: var(--accent-cyan);">IDLE</span>
</div>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
<span style="font-size: 10px; color: var(--text-muted); text-transform: uppercase;">Frequency</span>
<span id="lpQuickFreq" style="font-size: 14px; font-family: var(--font-mono); color: var(--text-primary);">---.--- MHz</span>
</div>
<div style="display: flex; justify-content: space-between; align-items: center;">
<span style="font-size: 10px; color: var(--text-muted); text-transform: uppercase;">Signals</span>
<span id="lpQuickSignals" style="font-size: 14px; font-weight: bold; color: var(--accent-green);">0</span>
</div>
</div>
</div>
<div class="section">
<h3>Bookmarks</h3>
<div style="display: flex; gap: 4px; margin-bottom: 8px;">
<input type="text" id="bookmarkFreqInput" placeholder="Freq (MHz)" style="flex: 1; padding: 6px; background: var(--bg-secondary); border: 1px solid var(--border-color); color: var(--text-primary); border-radius: 4px; font-size: 11px;">
<button class="preset-btn" onclick="addFrequencyBookmark()" style="background: var(--accent-green); color: #000; padding: 6px 10px;">+</button>
</div>
<div id="bookmarksList" style="max-height: 150px; overflow-y: auto; font-size: 11px;">
<div style="color: var(--text-muted); text-align: center; padding: 10px;">No bookmarks saved</div>
</div>
</div>
<div class="section">
<h3>Recent Signals</h3>
<div id="sidebarRecentSignals" style="max-height: 150px; overflow-y: auto; font-size: 11px;">
<div style="color: var(--text-muted); text-align: center; padding: 10px;">No signals yet</div>
</div>
</div>
<!-- Signal Identification -->
<div class="section">
<h3>Signal Identification</h3>
<div style="display: flex; gap: 4px; margin-bottom: 8px;">
<input type="text" id="signalGuessFreqInput" placeholder="Freq (MHz)" style="flex: 1; padding: 6px; background: var(--bg-secondary); border: 1px solid var(--border-color); color: var(--text-primary); border-radius: 4px; font-size: 11px;">
<button class="preset-btn" onclick="manualSignalGuess()" style="background: var(--accent-cyan); color: #000; padding: 6px 10px; font-weight: 600;">ID</button>
</div>
<div id="signalGuessPanel" style="display: none; background: rgba(0,0,0,0.3); border-radius: 6px; padding: 10px; font-size: 11px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px;">
<span id="signalGuessLabel" style="font-weight: bold; color: var(--text-primary);"></span>
<span id="signalGuessBadge" style="padding: 2px 8px; border-radius: 3px; font-size: 9px; font-weight: bold;"></span>
</div>
<div id="signalGuessExplanation" style="color: var(--text-muted); font-size: 10px; margin-bottom: 6px;"></div>
<div id="signalGuessTags" style="display: flex; flex-wrap: wrap; gap: 3px;"></div>
<div id="signalGuessAlternatives" style="margin-top: 6px; font-size: 10px; color: var(--text-muted);"></div>
<div id="signalGuessSendTo" style="margin-top: 8px; display: none;"></div>
</div>
</div>
</div>
-126
View File
@@ -1,126 +0,0 @@
<!-- RF HEATMAP MODE -->
<div id="rfheatmapMode" class="mode-content">
<!-- What is this? -->
<div class="section">
<h3>RF Heatmap</h3>
<div style="background:rgba(74,163,255,0.07); border:1px solid rgba(74,163,255,0.2); border-radius:6px; padding:10px; font-size:11px; color:var(--text-secondary); line-height:1.6;">
Walk around with INTERCEPT running. Your GPS position and the current signal strength are saved as a point on the map every few metres. The result is a <strong style="color:var(--accent-cyan);">coverage heatmap</strong> — bright areas have strong signal, dark areas are weak or absent.
</div>
</div>
<!-- Step 1 — Signal source -->
<div class="section">
<h3><span style="color:var(--accent-cyan); margin-right:6px;">1</span>What to Map</h3>
<div class="form-group">
<label>Signal Source</label>
<select id="rfhmSource" onchange="RFHeatmap.setSource(this.value); RFHeatmap.onSourceChange()">
<option value="wifi">WiFi — RSSI of nearby networks</option>
<option value="bluetooth">Bluetooth — RSSI of nearby devices</option>
<option value="scanner">SDR Scanner — broadband RF power</option>
</select>
</div>
<!-- SDR device picker — only shown for Scanner source -->
<div id="rfhmDeviceGroup" style="display:none;">
<div class="form-group">
<label>SDR Device</label>
<select id="rfhmDevice">
<option value="">Loading…</option>
</select>
</div>
</div>
<div id="rfhmSourceHint" style="font-size:11px; color:var(--text-dim); margin-top:4px; line-height:1.5;">
Walk near WiFi access points — their signal strength at each location is recorded.
</div>
<!-- Source running status + inline start/stop -->
<div id="rfhmSourceStatusRow" style="margin-top:10px; padding:8px 10px; background:rgba(0,0,0,0.3); border-radius:6px;">
<div style="display:flex; align-items:center; gap:7px; margin-bottom:6px;">
<span id="rfhmSourceDot" style="width:7px; height:7px; border-radius:50%; background:rgba(255,255,255,0.2); flex-shrink:0;"></span>
<span id="rfhmSourceStatusText" style="font-size:11px; color:var(--text-dim);">Checking…</span>
</div>
<button id="rfhmSourceStartBtn" class="run-btn" style="padding:6px; font-size:11px;" onclick="RFHeatmap.startSource()">Start Scanner</button>
<button id="rfhmSourceStopBtn" class="stop-btn" style="display:none; padding:6px; font-size:11px;" onclick="RFHeatmap.stopSource()">Stop Scanner</button>
</div>
</div>
<!-- Step 2 — Location -->
<div class="section">
<h3><span style="color:var(--accent-cyan); margin-right:6px;">2</span>Your Location</h3>
<div style="display:flex; justify-content:space-between; align-items:center; padding:6px 0; border-bottom:1px solid rgba(255,255,255,0.06);">
<span style="font-size:10px; color:var(--text-muted); text-transform:uppercase; letter-spacing:.05em;">GPS</span>
<span id="rfhmGpsPill" style="font-family:var(--font-mono); font-size:11px; color:var(--text-dim);">No Fix</span>
</div>
<div style="margin-top:8px;">
<div style="font-size:10px; color:var(--text-muted); margin-bottom:6px; line-height:1.5;">
No GPS? Enter a fixed location to map signals from a stationary point.
</div>
<div style="display:flex; gap:6px;">
<div class="form-group" style="flex:1; margin-bottom:0;">
<label>Latitude</label>
<input type="number" id="rfhmManualLat" step="0.0001" placeholder="37.7749" oninput="RFHeatmap.setManualCoords()">
</div>
<div class="form-group" style="flex:1; margin-bottom:0;">
<label>Longitude</label>
<input type="number" id="rfhmManualLon" step="0.0001" placeholder="-122.4194" oninput="RFHeatmap.setManualCoords()">
</div>
</div>
<button class="preset-btn" onclick="RFHeatmap.useObserverLocation()" style="font-size:10px; margin-top:5px;">
Use Saved Observer Location
</button>
</div>
</div>
<!-- Step 3 — Verify live signal -->
<div class="section">
<h3><span style="color:var(--accent-cyan); margin-right:6px;">3</span>Live Signal</h3>
<div style="background:rgba(0,0,0,0.3); border-radius:6px; padding:10px;">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:8px;">
<span style="font-size:10px; color:var(--text-muted); text-transform:uppercase; letter-spacing:.05em;">Current</span>
<span id="rfhmLiveSignal" style="font-family:var(--font-mono); font-size:16px; color:var(--text-dim);">— dBm</span>
</div>
<!-- Signal strength bar -->
<div style="height:4px; background:rgba(255,255,255,0.08); border-radius:2px; overflow:hidden;">
<div id="rfhmSignalBar" style="height:100%; width:0%; background:var(--accent-cyan); border-radius:2px; transition:width 0.3s ease;"></div>
</div>
<div id="rfhmSignalStatus" style="font-size:10px; color:var(--text-dim); margin-top:5px;">Waiting for signal data…</div>
</div>
</div>
<!-- Step 4 — Record -->
<div class="section">
<h3><span style="color:var(--accent-cyan); margin-right:6px;">4</span>Record</h3>
<div class="form-group">
<label>Sample Every</label>
<div style="display:flex; align-items:center; gap:8px;">
<input type="range" id="rfhmMinDist" min="1" max="50" value="5" step="1" style="flex:1;"
oninput="document.getElementById('rfhmMinDistVal').textContent=this.value+'m'; RFHeatmap.setMinDist(parseInt(this.value))">
<span id="rfhmMinDistVal" style="font-family:var(--font-mono); font-size:11px; color:var(--accent-cyan); min-width:28px; text-align:right;">5m</span>
</div>
<div style="font-size:10px; color:var(--text-dim); margin-top:3px;">A new point is added after you move this distance.</div>
</div>
<div style="display:flex; justify-content:space-between; align-items:center; padding:6px 0; margin-bottom:4px; border-top:1px solid rgba(255,255,255,0.06);">
<span style="font-size:10px; color:var(--text-muted); text-transform:uppercase; letter-spacing:.05em;">Points Captured</span>
<span id="rfhmPointCount" style="font-family:var(--font-mono); font-size:14px; color:var(--accent-cyan);">0</span>
</div>
<button class="run-btn" id="rfhmRecordBtn" onclick="RFHeatmap.startRecording()">Start Recording</button>
<button class="stop-btn" id="rfhmStopBtn" style="display:none;" onclick="RFHeatmap.stopRecording()">Stop Recording</button>
</div>
<!-- Map actions -->
<div class="section">
<h3>Map</h3>
<div style="display:flex; gap:6px;">
<button class="preset-btn" style="flex:1;" onclick="RFHeatmap.clearPoints()">Clear</button>
<button class="preset-btn" style="flex:1;" onclick="RFHeatmap.exportGeoJSON()">Export GeoJSON</button>
</div>
</div>
</div>
+213 -28
View File
@@ -1,10 +1,36 @@
<!-- WATERFALL MODE -->
<div id="waterfallMode" class="mode-content">
<div class="section">
<h3>Spectrum Waterfall</h3>
<div style="font-size:11px; color:var(--text-secondary); line-height:1.45;">
Click spectrum or waterfall to tune. Scroll to step-tune. Ctrl/Cmd + scroll to zoom span.
<div id="waterfallMode" class="mode-content wf-side">
<div class="section wf-side-hero">
<div class="wf-side-hero-title-row">
<div class="wf-side-hero-title">Spectrum Waterfall</div>
<div class="wf-side-chip" id="wfHeroVisualStatus">CONNECTING</div>
</div>
<div class="wf-side-hero-subtext">
Click spectrum/waterfall to tune. Scroll to step-tune. Ctrl/Cmd + scroll to zoom span.
</div>
<div class="wf-side-hero-stats">
<div class="wf-side-stat">
<div class="wf-side-stat-label">Tuned</div>
<div class="wf-side-stat-value" id="wfHeroFreq">100.0000 MHz</div>
</div>
<div class="wf-side-stat">
<div class="wf-side-stat-label">Mode</div>
<div class="wf-side-stat-value" id="wfHeroMode">WFM</div>
</div>
<div class="wf-side-stat">
<div class="wf-side-stat-label">Scan</div>
<div class="wf-side-stat-value" id="wfHeroScan">Idle</div>
</div>
<div class="wf-side-stat">
<div class="wf-side-stat-label">Hits</div>
<div class="wf-side-stat-value" id="wfHeroHits">0</div>
</div>
</div>
<div class="wf-side-hero-actions">
<button class="run-btn" id="wfStartBtn" onclick="Waterfall.start()">Start Waterfall</button>
<button class="stop-btn" id="wfStopBtn" onclick="Waterfall.stop()" style="display:none;">Stop Waterfall</button>
</div>
<div id="wfStatus" class="wf-side-status-line"></div>
</div>
<div class="section">
@@ -15,18 +41,18 @@
<option value="">Loading devices...</option>
</select>
</div>
<div id="wfDeviceInfo" style="display:none; background:rgba(0,0,0,0.32); border:1px solid rgba(74,163,255,0.22); border-radius:6px; padding:8px; margin-top:6px; font-size:11px;">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:4px;">
<span style="color:var(--text-muted); text-transform:uppercase; font-size:10px; letter-spacing:.05em;">Type</span>
<span id="wfDeviceType" style="color:var(--accent-cyan); font-family:var(--font-mono);">--</span>
<div id="wfDeviceInfo" class="wf-side-box" style="display:none;">
<div class="wf-side-kv">
<span class="wf-side-kv-label">Type</span>
<span id="wfDeviceType" class="wf-side-kv-value">--</span>
</div>
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:4px;">
<span style="color:var(--text-muted); text-transform:uppercase; font-size:10px; letter-spacing:.05em;">Range</span>
<span id="wfDeviceRange" style="color:var(--text-secondary); font-family:var(--font-mono);">--</span>
<div class="wf-side-kv">
<span class="wf-side-kv-label">Range</span>
<span id="wfDeviceRange" class="wf-side-kv-value">--</span>
</div>
<div style="display:flex; justify-content:space-between; align-items:center;">
<span style="color:var(--text-muted); text-transform:uppercase; font-size:10px; letter-spacing:.05em;">Capture SR</span>
<span id="wfDeviceBw" style="color:var(--text-secondary); font-family:var(--font-mono);">--</span>
<div class="wf-side-kv">
<span class="wf-side-kv-label">Capture SR</span>
<span id="wfDeviceBw" class="wf-side-kv-value">--</span>
</div>
</div>
</div>
@@ -41,7 +67,7 @@
<label>Span (MHz)</label>
<input type="number" id="wfSpanMhz" value="2.4" step="0.1" min="0.05" max="30">
</div>
<div class="button-group" style="display:grid; grid-template-columns:1fr 1fr; gap:6px;">
<div class="wf-side-grid-2">
<button class="preset-btn" onclick="Waterfall.applyPreset('fm')">FM Broadcast</button>
<button class="preset-btn" onclick="Waterfall.applyPreset('air')">Airband</button>
<button class="preset-btn" onclick="Waterfall.applyPreset('marine')">Marine</button>
@@ -49,10 +75,178 @@
</div>
</div>
<div class="section">
<h3>Quick Tune & Bookmarks</h3>
<div class="wf-side-grid-2">
<button class="preset-btn" onclick="Waterfall.quickTune(121.5, 'am')">121.5 Guard</button>
<button class="preset-btn" onclick="Waterfall.quickTune(156.8, 'fm')">156.8 CH16</button>
<button class="preset-btn" onclick="Waterfall.quickTune(145.5, 'fm')">145.5 2m</button>
<button class="preset-btn" onclick="Waterfall.quickTune(98.1, 'wfm')">98.1 FM</button>
<button class="preset-btn" onclick="Waterfall.quickTune(462.5625, 'fm')">462.56 FRS</button>
<button class="preset-btn" onclick="Waterfall.quickTune(446.0, 'fm')">446.0 PMR</button>
</div>
<div class="wf-side-divider"></div>
<div class="wf-bookmark-row">
<input type="number" id="wfBookmarkFreqInput" step="0.0001" min="0.001" max="6000" placeholder="Frequency MHz">
<select id="wfBookmarkMode">
<option value="auto" selected>Auto</option>
<option value="wfm">WFM</option>
<option value="fm">NFM</option>
<option value="am">AM</option>
<option value="usb">USB</option>
<option value="lsb">LSB</option>
</select>
</div>
<div class="wf-side-grid-2">
<button class="preset-btn" onclick="Waterfall.useTuneForBookmark()">Use Tuned</button>
<button class="preset-btn" onclick="Waterfall.addBookmarkFromInput()">Save Bookmark</button>
</div>
<div id="wfBookmarkList" class="wf-bookmark-list">
<div class="wf-empty">No bookmarks saved</div>
</div>
<div class="wf-side-inline-label">Recent Hits</div>
<div id="wfRecentSignals" class="wf-recent-list">
<div class="wf-empty">No recent signal hits</div>
</div>
</div>
<div class="section">
<h3>Handoff</h3>
<div class="wf-side-help">
Send current tuned frequency to another decoder/workflow.
</div>
<div class="wf-side-grid-2">
<button class="preset-btn" onclick="Waterfall.handoff('pager')">To Pager</button>
<button class="preset-btn" onclick="Waterfall.handoff('subghz')">To SubGHz</button>
<button class="preset-btn" onclick="Waterfall.handoff('subghz433')">433 Profile</button>
<button class="preset-btn" onclick="Waterfall.handoff('signalid')">Signal ID</button>
</div>
<div id="wfHandoffStatus" class="wf-side-status-line">Ready</div>
</div>
<div class="section">
<h3>Signal Identification</h3>
<div class="wf-side-help">
Identify current frequency using local catalog and SigID Wiki matches.
</div>
<div class="form-group">
<label>Frequency (MHz)</label>
<input type="number" id="wfSigIdFreq" value="100.0000" step="0.0001" min="0.001" max="6000">
</div>
<div class="form-group">
<label>Mode Hint</label>
<select id="wfSigIdMode">
<option value="auto" selected>Auto (Current Mode)</option>
<option value="wfm">WFM</option>
<option value="fm">NFM</option>
<option value="am">AM</option>
<option value="usb">USB</option>
<option value="lsb">LSB</option>
</select>
</div>
<div class="wf-side-grid-2">
<button class="preset-btn" onclick="Waterfall.useTuneForSignalId()">Use Tuned</button>
<button class="preset-btn" onclick="Waterfall.identifySignal()">Identify</button>
</div>
<div id="wfSigIdStatus" class="wf-side-status-line">Ready</div>
<div id="wfSigIdResult" class="wf-side-box" style="display:none;"></div>
<div id="wfSigIdExternal" class="wf-side-box wf-side-box-muted" style="display:none;"></div>
</div>
<div class="section">
<h3>Scan</h3>
<div class="form-group">
<label>Range Start (MHz)</label>
<input type="number" id="wfScanStart" value="98.8000" step="0.001" min="0.001" max="6000">
</div>
<div class="form-group">
<label>Range End (MHz)</label>
<input type="number" id="wfScanEnd" value="101.2000" step="0.001" min="0.001" max="6000">
</div>
<div class="form-group">
<label>Step (kHz)</label>
<select id="wfScanStepKhz">
<option value="5">5</option>
<option value="10">10</option>
<option value="12.5">12.5</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100" selected>100</option>
<option value="200">200</option>
</select>
</div>
<div class="form-group">
<label>Dwell (ms)</label>
<input type="number" id="wfScanDwellMs" value="450" min="60" max="10000" step="10">
</div>
<div class="form-group">
<label>Signal Threshold <span id="wfScanThresholdValue" class="wf-inline-value">170</span></label>
<input type="range" id="wfScanThreshold" min="0" max="255" value="170">
</div>
<div class="form-group">
<label>Hold On Hit (ms)</label>
<input type="number" id="wfScanHoldMs" value="2500" min="0" max="30000" step="100">
</div>
<div class="checkbox-group wf-scan-checkboxes">
<label>
<input type="checkbox" id="wfScanStopOnSignal" checked>
Pause scan when signal is above threshold
</label>
</div>
<div class="wf-side-grid-2 wf-side-grid-gap-top">
<button class="preset-btn" onclick="Waterfall.setScanRangeFromView()">Use Current Span</button>
<button class="preset-btn" id="wfScanStartBtn" onclick="Waterfall.startScan()">Start Scan</button>
<button class="preset-btn" id="wfScanStopBtn" onclick="Waterfall.stopScan()" disabled>Stop Scan</button>
</div>
<div id="wfScanState" class="wf-side-status-line">Scan idle</div>
</div>
<div class="section">
<h3>Scan Activity</h3>
<div class="wf-scan-metric-grid">
<div class="wf-scan-metric-card">
<div class="wf-scan-metric-label">Signals</div>
<div class="wf-scan-metric-value" id="wfScanSignalsCount">0</div>
</div>
<div class="wf-scan-metric-card">
<div class="wf-scan-metric-label">Scanned</div>
<div class="wf-scan-metric-value" id="wfScanStepsCount">0</div>
</div>
<div class="wf-scan-metric-card">
<div class="wf-scan-metric-label">Cycles</div>
<div class="wf-scan-metric-value" id="wfScanCyclesCount">0</div>
</div>
</div>
<div class="wf-side-grid-2 wf-side-grid-gap-top">
<button class="preset-btn" onclick="Waterfall.exportScanLog()">Export Log</button>
<button class="preset-btn" onclick="Waterfall.clearScanHistory()">Clear History</button>
</div>
<div class="wf-hit-table-wrap">
<table class="wf-hit-table">
<thead>
<tr>
<th>Time</th>
<th>Frequency</th>
<th>Level</th>
<th>Mode</th>
<th>Action</th>
</tr>
</thead>
<tbody id="wfSignalHitsBody">
<tr><td colspan="5" class="wf-empty">No signals detected</td></tr>
</tbody>
</table>
</div>
<div id="wfSignalHitCount" class="wf-side-inline-label">0 signals found</div>
<div id="wfActivityLog" class="wf-activity-log">
<div class="wf-empty">Ready</div>
</div>
</div>
<div class="section">
<h3>Capture</h3>
<div class="form-group">
<label>Gain <span style="color:var(--text-dim); font-weight:normal;">(dB or AUTO)</span></label>
<label>Gain <span class="wf-inline-value">(dB or AUTO)</span></label>
<input type="text" id="wfGain" value="AUTO" placeholder="AUTO or numeric">
</div>
<div class="form-group">
@@ -88,7 +282,7 @@
<label>PPM Correction</label>
<input type="number" id="wfPpm" value="0" step="1" min="-200" max="200" placeholder="0">
</div>
<div class="checkbox-group" style="margin-top:8px;">
<div class="checkbox-group wf-scan-checkboxes">
<label>
<input type="checkbox" id="wfBiasT">
Bias-T (antenna power)
@@ -115,7 +309,7 @@
<label>Ceiling (dB)</label>
<input type="number" id="wfDbMax" value="-20" step="5" disabled>
</div>
<div class="checkbox-group" style="margin-top:8px;">
<div class="checkbox-group wf-scan-checkboxes">
<label>
<input type="checkbox" id="wfPeakHold" onchange="Waterfall.togglePeakHold(this.checked)">
Peak Hold
@@ -130,13 +324,4 @@
</label>
</div>
</div>
<div class="section">
<button class="run-btn" id="wfStartBtn" onclick="Waterfall.start()">Start Waterfall</button>
<button class="stop-btn" id="wfStopBtn" style="display:none;" onclick="Waterfall.stop()">Stop Waterfall</button>
<div id="wfStatus" style="margin-top:8px; font-size:11px; color:var(--text-dim);"></div>
<div style="margin-top:6px; font-size:10px; color:var(--text-muted);">
Tune with click. Use Monitor in the top strip for audio listen.
</div>
</div>
</div>
-6
View File
@@ -65,7 +65,6 @@
{{ mode_item('pager', 'Pager', '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="5" width="16" height="14" rx="2"/><line x1="8" y1="10" x2="16" y2="10"/><line x1="8" y1="14" x2="12" y2="14"/></svg>') }}
{{ mode_item('sensor', '433MHz', '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="2"/><path d="M16.24 7.76a6 6 0 0 1 0 8.49m-8.48-.01a6 6 0 0 1 0-8.49"/></svg>') }}
{{ mode_item('rtlamr', 'Meters', '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>') }}
{{ mode_item('listening', 'Listening Post', '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18"/><path d="M9 21V9"/></svg>') }}
{{ mode_item('subghz', 'SubGHz', '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12h6l3-9 3 18 3-9h5"/></svg>') }}
{{ mode_item('waterfall', 'Waterfall', '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12h4l3-8 3 16 3-8h4"/><path d="M2 18h20" opacity="0.4"/><path d="M2 21h20" opacity="0.2"/></svg>') }}
</div>
@@ -136,8 +135,6 @@
{{ mode_item('tscm', 'TSCM', '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>') }}
{{ mode_item('spystations', 'Spy Stations', '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4.9 19.1C1 15.2 1 8.8 4.9 4.9"/><path d="M7.8 16.2c-2.3-2.3-2.3-6.1 0-8.5"/><circle cx="12" cy="12" r="2"/><path d="M16.2 7.8c2.3 2.3 2.3 6.1 0 8.5"/><path d="M19.1 4.9C23 8.8 23 15.1 19.1 19"/></svg>') }}
{{ mode_item('websdr', 'WebSDR', '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>') }}
{{ mode_item('rfheatmap', 'RF Heatmap', '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/><path d="M2 10h4M18 10h4" opacity="0.4"/></svg>') }}
{{ mode_item('fingerprint', 'RF Fingerprint', '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12C2 6.5 6.5 2 12 2a10 10 0 0 1 8 4"/><path d="M5 19.5C5.5 18 6 15 6 12c0-.7.12-1.37.34-2"/><path d="M17.29 21.02c.12-.6.43-2.3.5-3.02"/><path d="M12 10a2 2 0 0 0-2 2c0 1.02-.1 2.51-.26 4"/><path d="M8.65 22c.21-.66.45-1.32.57-2"/><path d="M14 13.12c0 2.38 0 6.38-1 8.88"/></svg>') }}
</div>
</div>
@@ -199,7 +196,6 @@
{{ mobile_item('pager', 'Pager', '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="4" y="5" width="16" height="14" rx="2"/><line x1="8" y1="10" x2="16" y2="10"/><line x1="8" y1="14" x2="12" y2="14"/></svg>') }}
{{ mobile_item('sensor', '433MHz', '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="2"/><path d="M16.24 7.76a6 6 0 0 1 0 8.49m-8.48-.01a6 6 0 0 1 0-8.49"/></svg>') }}
{{ mobile_item('rtlamr', 'Meters', '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>') }}
{{ mobile_item('listening', 'Scanner', '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18"/><path d="M9 21V9"/></svg>') }}
{{ mobile_item('subghz', 'SubGHz', '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M2 12h6l3-9 3 18 3-9h5"/></svg>') }}
{# Tracking #}
{{ mobile_item('adsb', 'Aircraft', '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 16v-2l-8-5V3.5a1.5 1.5 0 0 0-3 0V9l-8 5v2l8-2.5V19l-2 1.5V22l3.5-1 3.5 1v-1.5L13 19v-5.5l8 2.5z"/></svg>', '/adsb/dashboard') }}
@@ -227,8 +223,6 @@
{{ mobile_item('websdr', 'WebSDR', '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>') }}
{# New modes #}
{{ mobile_item('waterfall', 'Waterfall', '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M2 12h4l3-8 3 16 3-8h4"/></svg>') }}
{{ mobile_item('rfheatmap', 'RF Map', '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>') }}
{{ mobile_item('fingerprint', 'Fprint', '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M2 12C2 6.5 6.5 2 12 2a10 10 0 0 1 8 4"/><path d="M14 13.12c0 2.38 0 6.38-1 8.88"/></svg>') }}
</nav>
{# JavaScript stub for pages that don't have switchMode defined #}
-1
View File
@@ -484,7 +484,6 @@
<option value="tscm">TSCM</option>
<option value="sstv">SSTV</option>
<option value="sstv_general">SSTV General</option>
<option value="listening_scanner">Listening Post</option>
<option value="waterfall">Waterfall</option>
</select>
</div>