Add drone ops mode and retire DMR support

This commit is contained in:
Smittix
2026-02-20 17:02:16 +00:00
parent 97af1c82a1
commit 9cf74f93ca
36 changed files with 5338 additions and 2418 deletions
+95 -84
View File
@@ -66,6 +66,7 @@
aprs: "{{ url_for('static', filename='css/modes/aprs.css') }}",
tscm: "{{ url_for('static', filename='css/modes/tscm.css') }}",
analytics: "{{ url_for('static', filename='css/modes/analytics.css') }}",
droneops: "{{ url_for('static', filename='css/modes/droneops.css') }}?v={{ version }}&r=droneops6",
spystations: "{{ url_for('static', filename='css/modes/spy-stations.css') }}",
meshtastic: "{{ url_for('static', filename='css/modes/meshtastic.css') }}",
sstv: "{{ url_for('static', filename='css/modes/sstv.css') }}",
@@ -281,6 +282,10 @@
<span class="mode-icon icon"><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></span>
<span class="mode-name">TSCM</span>
</button>
<button class="mode-card mode-card-sm" onclick="selectMode('droneops')">
<span class="mode-icon icon"><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="3"/><circle cx="5" cy="8" r="2"/><circle cx="19" cy="8" r="2"/><circle cx="5" cy="16" r="2"/><circle cx="19" cy="16" r="2"/></svg></span>
<span class="mode-name">Drone Ops</span>
</button>
<button class="mode-card mode-card-sm" onclick="selectMode('analytics')">
<span class="mode-icon icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12V7H5a2 2 0 0 1 0-4h14v4"/><path d="M3 5v14a2 2 0 0 0 2 2h16v-5"/><path d="M18 12a2 2 0 0 0 0 4h4v-4Z"/></svg></span>
<span class="mode-name">Analytics</span>
@@ -606,14 +611,14 @@
{% include 'partials/modes/analytics.html' %}
{% include 'partials/modes/droneops.html' %}
{% include 'partials/modes/ais.html' %}
{% include 'partials/modes/spy-stations.html' %}
{% include 'partials/modes/meshtastic.html' %}
{% include 'partials/modes/dmr.html' %}
{% include 'partials/modes/websdr.html' %}
{% include 'partials/modes/subghz.html' %}
@@ -1883,76 +1888,6 @@
</div>
</div>
<!-- DMR / Digital Voice Dashboard -->
<div id="dmrVisuals" style="display: none; padding: 12px; flex-direction: column; gap: 12px; flex: 1; min-height: 0;">
<!-- DMR Synthesizer -->
<div class="radio-module-box" style="padding: 10px;">
<div class="module-header" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; font-size: 10px;">
<span>SIGNAL ACTIVITY</span>
<span id="dmrSynthStatus" style="color: var(--text-muted); font-size: 9px; font-family: var(--font-mono);">IDLE</span>
</div>
<canvas id="dmrSynthCanvas" style="width: 100%; height: 70px; background: rgba(0,0,0,0.4); border-radius: 4px; display: block;"></canvas>
</div>
<!-- Audio Output -->
<div class="radio-module-box" style="padding: 8px 12px;">
<audio id="dmrAudioPlayer" style="display: none;"></audio>
<div style="display: flex; align-items: center; gap: 10px;">
<span style="font-size: 10px; color: var(--text-muted); text-transform: uppercase;">AUDIO</span>
<span id="dmrAudioStatus" style="font-size: 9px; font-family: var(--font-mono); color: var(--text-muted);">OFF</span>
<div style="display: flex; align-items: center; gap: 4px; margin-left: auto;">
<span style="font-size: 10px; color: var(--text-muted);">VOL</span>
<input type="range" id="dmrAudioVolume" min="0" max="100" value="80" style="width: 80px;" oninput="setDmrAudioVolume(this.value)">
</div>
</div>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px;">
<!-- Call History Panel -->
<div class="radio-module-box" style="padding: 10px;">
<div class="module-header" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; font-size: 10px;">
<span>CALL HISTORY</span>
<span id="dmrHistoryCount" style="color: var(--accent-cyan);">0 calls</span>
</div>
<div style="max-height: 400px; overflow-y: auto;">
<table style="width: 100%; font-size: 10px; border-collapse: collapse;">
<thead>
<tr style="color: var(--text-muted); border-bottom: 1px solid var(--border-color);">
<th style="text-align: left; padding: 4px;">Time</th>
<th style="text-align: left; padding: 4px;">Talkgroup</th>
<th style="text-align: left; padding: 4px;">Source</th>
<th style="text-align: left; padding: 4px;">Protocol</th>
</tr>
</thead>
<tbody id="dmrHistoryBody">
<tr><td colspan="4" style="padding: 15px; text-align: center; color: var(--text-muted);">No calls recorded</td></tr>
</tbody>
</table>
</div>
</div>
<!-- Protocol Info Panel -->
<div class="radio-module-box" style="padding: 10px;">
<div class="module-header" style="margin-bottom: 8px; font-size: 10px;">
<span>DECODER STATUS</span>
</div>
<div style="display: flex; flex-direction: column; gap: 12px; padding: 10px;">
<div style="text-align: center;">
<div style="font-size: 9px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 2px; margin-bottom: 4px;">PROTOCOL</div>
<div id="dmrMainProtocol" style="font-size: 28px; font-weight: bold; color: var(--accent-cyan); font-family: var(--font-mono);">--</div>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
<div style="text-align: center; padding: 8px; background: rgba(0,0,0,0.3); border-radius: 4px;">
<div style="font-size: 9px; color: var(--text-muted);">CALLS</div>
<div id="dmrMainCallCount" style="font-size: 22px; font-weight: bold; color: var(--accent-green); font-family: var(--font-mono);">0</div>
</div>
<div style="text-align: center; padding: 8px; background: rgba(0,0,0,0.3); border-radius: 4px;">
<div style="font-size: 9px; color: var(--text-muted);">SYNCS</div>
<div id="dmrSyncCount" style="font-size: 22px; font-weight: bold; color: var(--accent-orange); font-family: var(--font-mono);">0</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- SubGHz Transceiver Dashboard -->
<div id="subghzVisuals" class="subghz-visuals-container" style="display: none;">
@@ -3204,6 +3139,74 @@
<div id="sensorTimelineContainer" style="display: none; margin-bottom: 12px;"></div>
<div id="droneOpsMainPane" class="droneops-main-pane" style="display: none;">
<div class="droneops-main-top">
<div class="droneops-main-map-card">
<div class="droneops-main-card-header">
<h4>Operational Map</h4>
<span id="droneOpsMapMeta" class="droneops-main-card-meta">Waiting for telemetry</span>
</div>
<div id="droneOpsMap" class="droneops-main-map"></div>
</div>
<div class="droneops-main-side">
<div class="droneops-main-card">
<div class="droneops-main-card-header">
<h4>Live Summary</h4>
<span id="droneOpsMainLastUpdate" class="droneops-main-card-meta">--</span>
</div>
<div class="droneops-main-kpis">
<div class="droneops-main-kpi">
<span>Detections</span>
<strong id="droneOpsMainSummaryDetections">0</strong>
</div>
<div class="droneops-main-kpi">
<span>Sources</span>
<strong id="droneOpsMainSummarySources">0</strong>
</div>
<div class="droneops-main-kpi">
<span>Track Points</span>
<strong id="droneOpsMainSummaryTracks">0</strong>
</div>
<div class="droneops-main-kpi">
<span>Telemetry IDs</span>
<strong id="droneOpsMainSummaryTelemetry">0</strong>
</div>
<div class="droneops-main-kpi">
<span>Correlations</span>
<strong id="droneOpsMainSummaryCorrelations">0</strong>
</div>
</div>
</div>
<div class="droneops-main-card">
<div class="droneops-main-card-header">
<h4>Operator Correlations</h4>
</div>
<div id="droneOpsMainCorrelations" class="droneops-main-list compact">
<div class="droneops-empty">No correlations yet</div>
</div>
</div>
</div>
</div>
<div class="droneops-main-bottom">
<div class="droneops-main-card">
<div class="droneops-main-card-header">
<h4>Detections</h4>
</div>
<div id="droneOpsMainDetections" class="droneops-main-list">
<div class="droneops-empty">No detections yet</div>
</div>
</div>
<div class="droneops-main-card">
<div class="droneops-main-card-header">
<h4>Telemetry Stream</h4>
</div>
<div id="droneOpsMainTelemetry" class="droneops-main-list">
<div class="droneops-empty">No telemetry yet</div>
</div>
</div>
</div>
</div>
<div class="output-content signal-feed" id="output">
<div class="placeholder signal-empty-state">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
@@ -3270,11 +3273,11 @@
<script src="{{ url_for('static', filename='js/modes/weather-satellite.js') }}"></script>
<script src="{{ url_for('static', filename='js/modes/sstv-general.js') }}"></script>
<script src="{{ url_for('static', filename='js/modes/gps.js') }}"></script>
<script src="{{ url_for('static', filename='js/modes/dmr.js') }}"></script>
<script src="{{ url_for('static', filename='js/modes/websdr.js') }}"></script>
<script src="{{ url_for('static', filename='js/modes/subghz.js') }}?v={{ version }}&r=subghz_layout9"></script>
<script src="{{ url_for('static', filename='js/modes/bt_locate.js') }}?v={{ version }}&r=btlocate4"></script>
<script src="{{ url_for('static', filename='js/modes/analytics.js') }}"></script>
<script src="{{ url_for('static', filename='js/modes/droneops.js') }}?v={{ version }}&r=droneops6"></script>
<script src="{{ url_for('static', filename='js/modes/space-weather.js') }}"></script>
<script>
@@ -3426,6 +3429,7 @@
bt_locate: { label: 'BT Locate', indicator: 'BT LOCATE', outputTitle: 'BT Locate — SAR Tracker', group: 'wireless' },
meshtastic: { label: 'Meshtastic', indicator: 'MESHTASTIC', outputTitle: 'Meshtastic Mesh Monitor', group: 'wireless' },
tscm: { label: 'TSCM', indicator: 'TSCM', outputTitle: 'TSCM Counter-Surveillance', group: 'intel' },
droneops: { label: 'Drone Ops', indicator: 'DRONE OPS', outputTitle: 'Drone Ops', group: 'intel' },
analytics: { label: 'Analytics', indicator: 'ANALYTICS', outputTitle: 'Cross-Mode Analytics', group: 'intel' },
spystations: { label: 'Spy Stations', indicator: 'SPY STATIONS', outputTitle: 'Spy Stations', group: 'intel' },
websdr: { label: 'WebSDR', indicator: 'WEBSDR', outputTitle: 'HF/Shortwave WebSDR', group: 'intel' },
@@ -4016,10 +4020,10 @@
document.getElementById('aisMode')?.classList.toggle('active', mode === 'ais');
document.getElementById('spystationsMode')?.classList.toggle('active', mode === 'spystations');
document.getElementById('meshtasticMode')?.classList.toggle('active', mode === 'meshtastic');
document.getElementById('dmrMode')?.classList.toggle('active', mode === 'dmr');
document.getElementById('websdrMode')?.classList.toggle('active', mode === 'websdr');
document.getElementById('subghzMode')?.classList.toggle('active', mode === 'subghz');
document.getElementById('analyticsMode')?.classList.toggle('active', mode === 'analytics');
document.getElementById('droneOpsMode')?.classList.toggle('active', mode === 'droneops');
document.getElementById('spaceWeatherMode')?.classList.toggle('active', mode === 'spaceweather');
@@ -4057,11 +4061,11 @@
const weatherSatVisuals = document.getElementById('weatherSatVisuals');
const sstvGeneralVisuals = document.getElementById('sstvGeneralVisuals');
const gpsVisuals = document.getElementById('gpsVisuals');
const dmrVisuals = document.getElementById('dmrVisuals');
const websdrVisuals = document.getElementById('websdrVisuals');
const subghzVisuals = document.getElementById('subghzVisuals');
const btLocateVisuals = document.getElementById('btLocateVisuals');
const spaceWeatherVisuals = document.getElementById('spaceWeatherVisuals');
const droneOpsMainPane = document.getElementById('droneOpsMainPane');
if (wifiLayoutContainer) wifiLayoutContainer.style.display = mode === 'wifi' ? 'flex' : 'none';
if (btLayoutContainer) btLayoutContainer.style.display = mode === 'bluetooth' ? 'flex' : 'none';
if (satelliteVisuals) satelliteVisuals.style.display = mode === 'satellite' ? 'block' : 'none';
@@ -4074,11 +4078,11 @@
if (weatherSatVisuals) weatherSatVisuals.style.display = mode === 'weathersat' ? 'flex' : 'none';
if (sstvGeneralVisuals) sstvGeneralVisuals.style.display = mode === 'sstv_general' ? 'flex' : 'none';
if (gpsVisuals) gpsVisuals.style.display = mode === 'gps' ? 'flex' : 'none';
if (dmrVisuals) dmrVisuals.style.display = mode === 'dmr' ? 'flex' : 'none';
if (websdrVisuals) websdrVisuals.style.display = mode === 'websdr' ? 'flex' : 'none';
if (subghzVisuals) subghzVisuals.style.display = mode === 'subghz' ? 'flex' : 'none';
if (btLocateVisuals) btLocateVisuals.style.display = mode === 'bt_locate' ? 'flex' : 'none';
if (spaceWeatherVisuals) spaceWeatherVisuals.style.display = mode === 'spaceweather' ? 'flex' : 'none';
if (droneOpsMainPane) droneOpsMainPane.style.display = mode === 'droneops' ? 'flex' : 'none';
// Hide sidebar by default for Meshtastic mode, show for others
const mainContent = document.querySelector('.main-content');
@@ -4120,6 +4124,17 @@
if (typeof Analytics !== 'undefined' && Analytics.destroy) Analytics.destroy();
}
// Initialize/destroy Drone Ops mode
if (mode === 'droneops') {
document.querySelectorAll('#droneOpsMode .section.collapsed').forEach(s => s.classList.remove('collapsed'));
if (typeof DroneOps !== 'undefined' && DroneOps.init) {
DroneOps.init();
if (DroneOps.invalidateMap) setTimeout(() => DroneOps.invalidateMap(), 120);
}
} else {
if (typeof DroneOps !== 'undefined' && DroneOps.destroy) DroneOps.destroy();
}
// Initialize/destroy Space Weather mode
if (mode !== 'spaceweather') {
if (typeof SpaceWeather !== 'undefined' && SpaceWeather.destroy) SpaceWeather.destroy();
@@ -4134,7 +4149,7 @@
const reconBtn = document.getElementById('reconBtn');
const intelBtn = document.querySelector('[onclick="exportDeviceDB()"]');
const reconPanel = document.getElementById('reconPanel');
if (mode === 'satellite' || mode === 'sstv' || mode === 'weathersat' || mode === 'sstv_general' || mode === 'gps' || mode === 'listening' || mode === 'aprs' || mode === 'tscm' || mode === 'spystations' || mode === 'meshtastic' || mode === 'dmr' || mode === 'websdr' || mode === 'subghz' || mode === 'analytics' || mode === 'spaceweather') {
if (mode === 'satellite' || mode === 'sstv' || mode === 'weathersat' || mode === 'sstv_general' || mode === 'gps' || mode === 'listening' || mode === 'aprs' || mode === 'tscm' || mode === 'spystations' || mode === 'meshtastic' || mode === 'websdr' || mode === 'subghz' || mode === 'analytics' || mode === 'droneops' || mode === 'spaceweather') {
if (reconPanel) reconPanel.style.display = 'none';
if (reconBtn) reconBtn.style.display = 'none';
if (intelBtn) intelBtn.style.display = 'none';
@@ -4154,7 +4169,7 @@
// Show RTL-SDR device section for modes that use it
const rtlDeviceSection = document.getElementById('rtlDeviceSection');
if (rtlDeviceSection) rtlDeviceSection.style.display = (mode === 'pager' || mode === 'sensor' || mode === 'rtlamr' || mode === 'listening' || mode === 'aprs' || mode === 'sstv' || mode === 'weathersat' || mode === 'sstv_general' || mode === 'dmr') ? 'block' : 'none';
if (rtlDeviceSection) rtlDeviceSection.style.display = (mode === 'pager' || mode === 'sensor' || mode === 'rtlamr' || mode === 'listening' || mode === 'aprs' || mode === 'sstv' || mode === 'weathersat' || mode === 'sstv_general') ? 'block' : 'none';
// Show waterfall panel if running in listening mode
const waterfallPanel = document.getElementById('waterfallPanel');
@@ -4172,8 +4187,8 @@
// Hide output console for modes with their own visualizations
const outputEl = document.getElementById('output');
const statusBar = document.querySelector('.status-bar');
if (outputEl) outputEl.style.display = (mode === 'satellite' || mode === 'sstv' || mode === 'weathersat' || mode === 'sstv_general' || mode === 'aprs' || mode === 'wifi' || mode === 'bluetooth' || mode === 'listening' || mode === 'tscm' || mode === 'spystations' || mode === 'meshtastic' || mode === 'dmr' || mode === 'websdr' || mode === 'subghz' || mode === 'analytics' || mode === 'spaceweather') ? 'none' : 'block';
if (statusBar) statusBar.style.display = (mode === 'satellite' || mode === 'websdr' || mode === 'dmr' || mode === 'subghz' || mode === 'spaceweather') ? 'none' : 'flex';
if (outputEl) outputEl.style.display = (mode === 'satellite' || mode === 'sstv' || mode === 'weathersat' || mode === 'sstv_general' || mode === 'aprs' || mode === 'wifi' || mode === 'bluetooth' || mode === 'listening' || mode === 'tscm' || mode === 'spystations' || mode === 'meshtastic' || mode === 'websdr' || mode === 'subghz' || mode === 'analytics' || mode === 'droneops' || mode === 'spaceweather') ? 'none' : 'block';
if (statusBar) statusBar.style.display = (mode === 'satellite' || mode === 'websdr' || mode === 'subghz' || mode === 'spaceweather') ? 'none' : 'flex';
// Restore sidebar when leaving Meshtastic mode (user may have collapsed it)
if (mode !== 'meshtastic') {
@@ -4232,10 +4247,6 @@
SSTVGeneral.init();
} else if (mode === 'gps') {
GPS.init();
} else if (mode === 'dmr') {
if (typeof checkDmrTools === 'function') checkDmrTools();
if (typeof checkDmrStatus === 'function') checkDmrStatus();
if (typeof initDmrSynthesizer === 'function') setTimeout(initDmrSynthesizer, 100);
} else if (mode === 'websdr') {
if (typeof initWebSDR === 'function') initWebSDR();
} else if (mode === 'subghz') {
-114
View File
@@ -1,114 +0,0 @@
<!-- DMR / DIGITAL VOICE MODE -->
<div id="dmrMode" class="mode-content">
<div class="section">
<h3>Digital Voice</h3>
<div class="alpha-mode-notice">
ALPHA: Digital Voice decoding is still in active development. Expect occasional decode instability and false protocol locks.
</div>
<!-- Dependency Warning -->
<div id="dmrToolsWarning" 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="dmrToolsWarningText"></span>
</p>
</div>
<div class="form-group">
<label>Frequency (MHz)</label>
<input type="number" id="dmrFrequency" value="462.5625" step="0.0001" style="width: 100%;">
</div>
<div class="form-group">
<label>Protocol</label>
<select id="dmrProtocol">
<option value="auto" selected>Auto Detect (DMR/P25/D-STAR)</option>
<option value="dmr">DMR</option>
<option value="p25">P25</option>
<option value="nxdn">NXDN</option>
<option value="dstar">D-STAR</option>
<option value="provoice">ProVoice</option>
</select>
<span style="font-size: 0.75em; color: var(--text-muted); display: block; margin-top: 2px;">
For NXDN and ProVoice, use manual protocol selection for best lock reliability
</span>
</div>
<div class="form-group">
<label>Gain</label>
<input type="number" id="dmrGain" value="40" min="0" max="50" style="width: 100%;">
</div>
<div class="form-group">
<label>PPM Correction</label>
<input type="number" id="dmrPPM" value="0" min="-200" max="200" step="1" style="width: 100%;"
title="Frequency error correction for your RTL-SDR dongle. Digital voice is very sensitive to frequency offset.">
</div>
<div class="form-group" style="margin-top: 4px;">
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
<input type="checkbox" id="dmrRelaxCrc" style="width: auto; accent-color: var(--accent-cyan);">
<span>Relax CRC (weak signals)</span>
</label>
<span style="font-size: 0.75em; color: var(--text-muted); display: block; margin-top: 2px;">
Allows more frames through on marginal signals at the cost of occasional errors
</span>
</div>
</div>
<!-- Bookmarks -->
<div class="section" style="margin-top: 8px;">
<h3>Bookmarks</h3>
<div style="display: flex; gap: 4px; margin-bottom: 6px;">
<input type="number" id="dmrBookmarkFreq" placeholder="Freq MHz" step="0.0001"
style="flex: 1; font-size: 11px; padding: 4px 6px;">
<button class="preset-btn" onclick="addDmrBookmark()" style="font-size: 10px; padding: 4px 8px;"
title="Add bookmark">+</button>
</div>
<div style="display: flex; gap: 4px; margin-bottom: 6px;">
<input type="text" id="dmrBookmarkLabel" placeholder="Label (optional)"
style="flex: 1; font-size: 11px; padding: 4px 6px;">
<button class="preset-btn" onclick="addCurrentDmrFreqBookmark()" style="font-size: 9px; padding: 4px 6px;"
title="Save current frequency">Save current</button>
</div>
<div id="dmrBookmarksList" style="max-height: 150px; overflow-y: auto;">
<div style="color: var(--text-muted); text-align: center; padding: 10px; font-size: 11px;">No bookmarks saved</div>
</div>
</div>
<!-- Current Call -->
<div class="section" style="margin-top: 12px;">
<h3>Current Call</h3>
<div id="dmrCurrentCall" style="background: rgba(0,0,0,0.3); border-radius: 6px; padding: 10px; font-size: 11px;">
<div style="color: var(--text-muted); text-align: center;">No active call</div>
</div>
</div>
<!-- Status -->
<div class="section" style="margin-top: 12px;">
<h3>Status</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: 6px;">
<span style="font-size: 10px; color: var(--text-muted); text-transform: uppercase;">Status</span>
<span id="dmrStatus" style="font-size: 11px; color: var(--accent-cyan);">IDLE</span>
</div>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px;">
<span style="font-size: 10px; color: var(--text-muted); text-transform: uppercase;">Protocol</span>
<span id="dmrActiveProtocol" style="font-size: 11px; color: var(--text-primary);">--</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;">Calls</span>
<span id="dmrCallCount" style="font-size: 14px; font-weight: bold; color: var(--accent-green);">0</span>
</div>
</div>
</div>
<div class="mode-actions-bottom">
<button class="run-btn" id="startDmrBtn" onclick="startDmr()">
Start Decoder
</button>
<button class="stop-btn" id="stopDmrBtn" onclick="stopDmr()" style="display: none;">
Stop Decoder
</button>
</div>
</div>
+152
View File
@@ -0,0 +1,152 @@
<!-- DRONE OPS MODE -->
<div id="droneOpsMode" class="mode-content">
<div class="section">
<h3 class="section-header collapsible" onclick="toggleSection(this)">
<span>Drone Ops Status</span>
<span class="collapse-icon">&#9660;</span>
</h3>
<div class="section-content">
<div class="droneops-status-grid" id="droneOpsStatusGrid">
<div class="droneops-status-card">
<div class="droneops-status-label">Session</div>
<div class="droneops-status-value" id="droneOpsSessionValue">Idle</div>
</div>
<div class="droneops-status-card">
<div class="droneops-status-label">Armed</div>
<div class="droneops-status-value" id="droneOpsArmedValue">No</div>
</div>
<div class="droneops-status-card">
<div class="droneops-status-label">Detections</div>
<div class="droneops-status-value" id="droneOpsDetectionCount">0</div>
</div>
<div class="droneops-status-card">
<div class="droneops-status-label">Open Incidents</div>
<div class="droneops-status-value" id="droneOpsIncidentCount">0</div>
</div>
</div>
<div class="droneops-row droneops-row--actions">
<button class="preset-btn" onclick="DroneOps.startSession('passive')">Start Passive Session</button>
<button class="preset-btn" onclick="DroneOps.startSession('active')">Start Active Session</button>
<button class="clear-btn" onclick="DroneOps.stopSession()">Stop Session</button>
</div>
<div class="droneops-row droneops-row--actions">
<label class="droneops-check">
<input id="droneOpsDetectWifi" type="checkbox" checked>
<span>WiFi</span>
</label>
<label class="droneops-check">
<input id="droneOpsDetectBluetooth" type="checkbox" checked>
<span>Bluetooth</span>
</label>
<button class="preset-btn" onclick="DroneOps.startDetection()">Start Detection</button>
<button class="clear-btn" onclick="DroneOps.stopDetection()">Stop Detection</button>
</div>
<div class="droneops-source-block">
<div class="droneops-source-title">Detection Sources</div>
<div class="droneops-row droneops-row--sources">
<label class="droneops-field">
<span class="droneops-field-label">WiFi Interface</span>
<select id="droneOpsWifiInterfaceSelect" class="droneops-input" title="WiFi source interface">
<option value="">Auto WiFi source</option>
</select>
</label>
<label class="droneops-field">
<span class="droneops-field-label">Bluetooth Adapter</span>
<select id="droneOpsBtAdapterSelect" class="droneops-input" title="Bluetooth source adapter">
<option value="">Auto Bluetooth source</option>
</select>
</label>
<button class="preset-btn droneops-source-refresh" onclick="DroneOps.refreshDetectionSources()">Refresh Sources</button>
</div>
</div>
<div class="droneops-subtext">Sensors: <span id="droneOpsSensorsState">Idle</span></div>
<div class="droneops-row droneops-row--actions">
<input id="droneOpsArmIncident" class="droneops-input" type="number" placeholder="Incident ID" min="1">
<input id="droneOpsArmReason" class="droneops-input" type="text" placeholder="Arming reason">
<button class="preset-btn" onclick="DroneOps.arm()">Arm Actions</button>
<button class="clear-btn" onclick="DroneOps.disarm()">Disarm</button>
</div>
</div>
</div>
<div class="section">
<h3 class="section-header collapsible" onclick="toggleSection(this)">
<span>Detections</span>
<span class="collapse-icon">&#9660;</span>
</h3>
<div class="section-content">
<div class="droneops-row">
<select id="droneOpsSourceFilter" class="droneops-input" onchange="DroneOps.refreshDetections()">
<option value="">All Sources</option>
<option value="wifi">WiFi</option>
<option value="bluetooth">Bluetooth</option>
<option value="rf">RF</option>
</select>
<input id="droneOpsConfidenceFilter" class="droneops-input" type="number" min="0" max="1" step="0.05" value="0.5" placeholder="Min confidence">
<button class="preset-btn" onclick="DroneOps.refreshDetections()">Refresh</button>
</div>
<div id="droneOpsDetections" class="droneops-list">
<div class="droneops-empty">No detections yet</div>
</div>
</div>
</div>
<div class="section">
<h3 class="section-header collapsible" onclick="toggleSection(this)">
<span>Incidents</span>
<span class="collapse-icon">&#9660;</span>
</h3>
<div class="section-content">
<div class="droneops-row">
<input id="droneOpsIncidentTitle" class="droneops-input" type="text" placeholder="Incident title">
<select id="droneOpsIncidentSeverity" class="droneops-input">
<option value="low">Low</option>
<option value="medium" selected>Medium</option>
<option value="high">High</option>
<option value="critical">Critical</option>
</select>
<button class="preset-btn" onclick="DroneOps.createIncident()">Create Incident</button>
</div>
<div id="droneOpsIncidents" class="droneops-list">
<div class="droneops-empty">No incidents</div>
</div>
</div>
</div>
<div class="section">
<h3 class="section-header collapsible" onclick="toggleSection(this)">
<span>Actions</span>
<span class="collapse-icon">&#9660;</span>
</h3>
<div class="section-content">
<div class="droneops-row">
<input id="droneOpsActionIncident" class="droneops-input" type="number" placeholder="Incident ID" min="1">
<input id="droneOpsActionType" class="droneops-input" type="text" placeholder="Action type (e.g. wifi_deauth_test)">
<button class="preset-btn" onclick="DroneOps.requestAction()">Request Action</button>
</div>
<div id="droneOpsActions" class="droneops-list">
<div class="droneops-empty">No action requests</div>
</div>
</div>
</div>
<div class="section">
<h3 class="section-header collapsible" onclick="toggleSection(this)">
<span>Evidence</span>
<span class="collapse-icon">&#9660;</span>
</h3>
<div class="section-content">
<div class="droneops-row">
<input id="droneOpsManifestIncident" class="droneops-input" type="number" placeholder="Incident ID" min="1">
<button class="preset-btn" onclick="DroneOps.generateManifest()">Generate Manifest</button>
</div>
<div id="droneOpsManifests" class="droneops-list">
<div class="droneops-empty">No manifests</div>
</div>
</div>
</div>
</div>
+2
View File
@@ -133,6 +133,7 @@
<div class="mode-nav-dropdown-menu">
{{ 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('droneops', 'Drone Ops', '<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="3"/><circle cx="5" cy="8" r="2"/><circle cx="19" cy="8" r="2"/><circle cx="5" cy="16" r="2"/><circle cx="19" cy="16" r="2"/><path d="M9 9 7 8"/><path d="m15 9 2-1"/><path d="m9 15-2 1"/><path d="m15 15 2 1"/></svg>') }}
{{ mode_item('analytics', 'Analytics', '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12V7H5a2 2 0 0 1 0-4h14v4"/><path d="M3 5v14a2 2 0 0 0 2 2h16v-5"/><path d="M18 12a2 2 0 0 0 0 4h4v-4Z"/></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>') }}
@@ -215,6 +216,7 @@
{{ mobile_item('meshtastic', 'Mesh', '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="3"/><path d="M12 2v4m0 12v4M2 12h4m12 0h4"/></svg>') }}
{# Intel #}
{{ mobile_item('tscm', 'TSCM', '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>') }}
{{ mobile_item('droneops', 'Drone Ops', '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><circle cx="5" cy="8" r="2"/><circle cx="19" cy="8" r="2"/><circle cx="5" cy="16" r="2"/><circle cx="19" cy="16" r="2"/></svg>') }}
{{ mobile_item('analytics', 'Analytics', '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12V7H5a2 2 0 0 1 0-4h14v4"/><path d="M3 5v14a2 2 0 0 0 2 2h16v-5"/><path d="M18 12a2 2 0 0 0 0 4h4v-4Z"/></svg>') }}
{{ mobile_item('spystations', 'Spy', '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><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>') }}
{{ 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>') }}