Revert TSCM menu changes - restore original layout

The simplified layout was causing display issues. Reverting to
the original working version.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Smittix
2026-01-16 17:05:38 +00:00
parent a8bb56a109
commit 6354911c54
6 changed files with 239 additions and 173 deletions

Binary file not shown.

View File

@@ -36,7 +36,11 @@ from utils.database import (
set_active_tscm_baseline,
update_tscm_sweep,
)
from utils.tscm.baseline import BaselineComparator, BaselineRecorder
from utils.tscm.baseline import (
BaselineComparator,
BaselineRecorder,
get_comparison_for_active_baseline,
)
from utils.tscm.correlation import (
CorrelationEngine,
get_correlation_engine,
@@ -1447,6 +1451,20 @@ def _run_sweep(
correlations = correlation.correlate_devices()
findings = correlation.get_all_findings()
# Run baseline comparison if a baseline was provided
baseline_comparison = None
if baseline:
comparator = BaselineComparator(baseline)
baseline_comparison = comparator.compare_all(
wifi_devices=list(all_wifi.values()),
bt_devices=list(all_bt.values()),
rf_signals=all_rf
)
logger.info(
f"Baseline comparison: {baseline_comparison['total_new']} new, "
f"{baseline_comparison['total_missing']} missing"
)
# Finalize identity engine and get MAC-randomization resistant clusters
identity_engine.finalize_all_sessions()
identity_summary = identity_engine.get_summary()
@@ -1462,6 +1480,7 @@ def _run_sweep(
'severity_counts': severity_counts,
'correlation_summary': findings.get('summary', {}),
'identity_summary': identity_summary.get('statistics', {}),
'baseline_comparison': baseline_comparison,
},
threats_found=threats_found,
completed=True
@@ -1474,6 +1493,18 @@ def _run_sweep(
'needs_review_count': findings['summary'].get('needs_review', 0),
})
# Emit baseline comparison if a baseline was used
if baseline_comparison:
_emit_event('baseline_comparison', {
'baseline_id': baseline.get('id'),
'baseline_name': baseline.get('name'),
'total_new': baseline_comparison['total_new'],
'total_missing': baseline_comparison['total_missing'],
'wifi': baseline_comparison.get('wifi'),
'bluetooth': baseline_comparison.get('bluetooth'),
'rf': baseline_comparison.get('rf'),
})
# Emit device identity cluster findings (MAC-randomization resistant)
_emit_event('identity_clusters', {
'total_clusters': identity_summary['statistics'].get('total_clusters', 0),
@@ -1494,6 +1525,8 @@ def _run_sweep(
'needs_review_devices': findings['summary'].get('needs_review', 0),
'correlations_found': len(correlations),
'identity_clusters': identity_summary['statistics'].get('total_clusters', 0),
'baseline_new_devices': baseline_comparison['total_new'] if baseline_comparison else 0,
'baseline_missing_devices': baseline_comparison['total_missing'] if baseline_comparison else 0,
})
except Exception as e:
@@ -1625,6 +1658,43 @@ def get_active_baseline():
return jsonify({'status': 'success', 'baseline': baseline})
@tscm_bp.route('/baseline/compare', methods=['POST'])
def compare_against_baseline():
"""
Compare provided device data against the active baseline.
Expects JSON body with:
- wifi_devices: list of WiFi devices (optional)
- bt_devices: list of Bluetooth devices (optional)
- rf_signals: list of RF signals (optional)
Returns comparison showing new, missing, and matching devices.
"""
data = request.get_json() or {}
wifi_devices = data.get('wifi_devices')
bt_devices = data.get('bt_devices')
rf_signals = data.get('rf_signals')
# Use the convenience function that gets active baseline
comparison = get_comparison_for_active_baseline(
wifi_devices=wifi_devices,
bt_devices=bt_devices,
rf_signals=rf_signals
)
if comparison is None:
return jsonify({
'status': 'error',
'message': 'No active baseline set'
}), 400
return jsonify({
'status': 'success',
'comparison': comparison
})
# =============================================================================
# Threat Endpoints
# =============================================================================

View File

@@ -1,50 +1,5 @@
/* TSCM Styles */
/* TSCM Collapsible Sections */
.tscm-collapsible {
padding: 0 !important;
overflow: hidden;
}
.tscm-collapsible-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 12px;
cursor: pointer;
font-size: 12px;
font-weight: 600;
color: var(--text-secondary);
transition: background 0.2s;
}
.tscm-collapsible-header:hover {
background: rgba(255, 255, 255, 0.03);
}
.tscm-collapse-icon {
font-size: 14px;
font-weight: 400;
color: var(--text-muted);
transition: transform 0.2s;
}
.tscm-collapsible-content {
padding: 0 12px 12px 12px;
}
.tscm-collapsible.expanded .tscm-collapse-icon {
transform: rotate(45deg);
}
/* TSCM Tool Buttons */
.tscm-tool-btn {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 8px;
min-width: 0;
}
.tscm-tool-btn:hover {
background: rgba(74, 158, 255, 0.15);
}
/* TSCM Threat Cards */
.threat-card {
display: flex;

View File

@@ -194,7 +194,8 @@
<label title="Show aircraft trails"><input type="checkbox" id="showTrails" onchange="toggleTrails()"> Trails</label>
<label title="Show range rings"><input type="checkbox" id="showRangeRings" checked onchange="drawRangeRings()"> Rings</label>
<label title="Radar sweep overlay"><input type="checkbox" id="radarOverlay" onchange="toggleRadarOverlay()"> Radar</label>
<label title="Audio alerts"><input type="checkbox" id="alertToggle" checked onchange="toggleAlerts()"> Alerts</label>
<label title="Audio alerts for military/emergency"><input type="checkbox" id="alertToggle" checked onchange="toggleAlerts()"> Alerts</label>
<label title="Sound when new aircraft detected"><input type="checkbox" id="detectionSoundToggle" onchange="toggleDetectionSound()"> Ping</label>
<select id="aircraftFilter" onchange="applyFilter()" title="Filter aircraft">
<option value="all">All Aircraft</option>
<option value="military">Military</option>
@@ -298,6 +299,8 @@
let currentFilter = 'all';
let alertedAircraft = {};
let alertsEnabled = true;
let detectionSoundEnabled = localStorage.getItem('adsb_detectionSound') !== 'false'; // Default on
let soundedAircraft = {}; // Track aircraft we've played detection sound for
let currentView = 'map'; // 'map' or 'radar'
// Watchlist - persisted to localStorage
@@ -576,6 +579,31 @@
alertsEnabled = document.getElementById('alertToggle').checked;
}
function toggleDetectionSound() {
detectionSoundEnabled = document.getElementById('detectionSoundToggle').checked;
localStorage.setItem('adsb_detectionSound', detectionSoundEnabled);
}
function playDetectionSound() {
if (!detectionSoundEnabled) return;
try {
const ctx = getAudioContext();
const oscillator = ctx.createOscillator();
const gainNode = ctx.createGain();
oscillator.connect(gainNode);
gainNode.connect(ctx.destination);
// Soft, quick ping - 1000Hz for 100ms
oscillator.frequency.setValueAtTime(1000, ctx.currentTime);
oscillator.type = 'sine';
gainNode.gain.setValueAtTime(0.1, ctx.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.1);
oscillator.start(ctx.currentTime);
oscillator.stop(ctx.currentTime + 0.1);
} catch (e) {
console.warn('Detection sound failed:', e);
}
}
// ============================================
// WATCHLIST FUNCTIONS
// ============================================
@@ -1920,6 +1948,10 @@ ACARS: ${r.statistics.acarsMessages} messages`;
if (obsLatInput) obsLatInput.value = observerLocation.lat.toFixed(4);
if (obsLonInput) obsLonInput.value = observerLocation.lon.toFixed(4);
// Initialize detection sound toggle from localStorage
const detectionToggle = document.getElementById('detectionSoundToggle');
if (detectionToggle) detectionToggle.checked = detectionSoundEnabled;
initMap();
initDeviceSelectors();
updateClock();
@@ -2485,6 +2517,13 @@ sudo make install</code>
const icao = data.icao;
if (!icao) return;
// Check if this is a new aircraft we haven't seen this session
const isNewAircraft = !soundedAircraft[icao];
if (isNewAircraft) {
soundedAircraft[icao] = true;
playDetectionSound();
}
aircraft[icao] = {
...aircraft[icao],
...data,

View File

@@ -7530,23 +7530,6 @@
}
}
// TSCM collapsible section toggle
function toggleTscmSection(contentId) {
const content = document.getElementById(contentId);
const icon = document.getElementById(contentId + 'Icon');
const section = content.closest('.tscm-collapsible');
if (content.style.display === 'none') {
content.style.display = 'block';
icon.textContent = '-';
section.classList.add('expanded');
} else {
content.style.display = 'none';
icon.textContent = '+';
section.classList.remove('expanded');
}
}
async function startTscmSweep() {
const sweepType = document.getElementById('tscmSweepType').value;
const baselineId = document.getElementById('tscmBaselineSelect').value || null;

View File

@@ -1,25 +1,137 @@
<!-- TSCM MODE (Counter-Surveillance) -->
<div id="tscmMode" class="mode-content">
<!-- Primary Action Area -->
<div class="section">
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px;">
<h3 style="margin: 0;">TSCM Sweep</h3>
<span style="font-size: 9px; background: var(--accent-orange); color: #000; padding: 2px 6px; border-radius: 3px; text-transform: uppercase;">Alpha</span>
<h3 style="display: flex; align-items: center; gap: 8px;">TSCM Sweep <span style="font-size: 9px; font-weight: normal; background: var(--accent-orange); color: #000; padding: 2px 6px; border-radius: 3px; text-transform: uppercase; letter-spacing: 0.5px;">Alpha</span></h3>
<div class="form-group">
<label>Sweep Type</label>
<select id="tscmSweepType">
<option value="quick">Quick Scan (2 min)</option>
<option value="standard" selected>Standard (5 min)</option>
<option value="full">Full Sweep (15 min)</option>
<option value="wireless_cameras">Wireless Cameras</option>
<option value="body_worn">Body-Worn Devices</option>
<option value="gps_trackers">GPS Trackers</option>
</select>
</div>
<select id="tscmSweepType" style="width: 100%; margin-bottom: 12px;">
<option value="quick">Quick Scan (2 min)</option>
<option value="standard" selected>Standard (5 min)</option>
<option value="full">Full Sweep (15 min)</option>
<option value="wireless_cameras">Wireless Cameras</option>
<option value="body_worn">Body-Worn Devices</option>
<option value="gps_trackers">GPS Trackers</option>
</select>
<button class="run-btn" id="startTscmBtn" onclick="startTscmSweep()" style="width: 100%;">
Start Sweep
<div class="form-group">
<label>Compare Against</label>
<select id="tscmBaselineSelect">
<option value="">No Baseline</option>
</select>
</div>
</div>
<div class="section">
<h3>Scan Sources</h3>
<div class="form-group" style="margin-bottom: 8px;">
<div style="display: flex; align-items: center; gap: 8px;">
<input type="checkbox" id="tscmWifiEnabled" checked style="margin: 0;">
<label for="tscmWifiEnabled" style="flex: 1; margin: 0;">WiFi Networks</label>
</div>
<select id="tscmWifiInterface" style="width: 100%; margin-top: 4px; font-size: 11px;">
<option value="">Select WiFi interface...</option>
</select>
</div>
<div class="form-group" style="margin-bottom: 8px;">
<div style="display: flex; align-items: center; gap: 8px;">
<input type="checkbox" id="tscmBtEnabled" checked style="margin: 0;">
<label for="tscmBtEnabled" style="flex: 1; margin: 0;">Bluetooth Devices</label>
</div>
<select id="tscmBtInterface" style="width: 100%; margin-top: 4px; font-size: 11px;">
<option value="">Select Bluetooth adapter...</option>
</select>
</div>
<div class="form-group" style="margin-bottom: 8px;">
<div style="display: flex; align-items: center; gap: 8px;">
<input type="checkbox" id="tscmRfEnabled" style="margin: 0;">
<label for="tscmRfEnabled" style="flex: 1; margin: 0;">RF Signals</label>
</div>
<select id="tscmSdrDevice" style="width: 100%; margin-top: 4px; font-size: 11px;">
<option value="">Select SDR device...</option>
</select>
</div>
<button class="preset-btn" onclick="refreshTscmDevices()" style="width: 100%; margin-top: 8px; font-size: 10px;">
🔄 Refresh Devices
</button>
<button class="stop-btn" id="stopTscmBtn" onclick="stopTscmSweep()" style="display: none; width: 100%;">
Stop Sweep
</div>
<div class="section">
<h3>Baseline Management</h3>
<div class="form-group">
<input type="text" id="tscmBaselineName" placeholder="Baseline name...">
</div>
<button class="run-btn" id="tscmRecordBaselineBtn" onclick="tscmRecordBaseline()" style="width: 100%; padding: 8px;">
Record New Baseline
</button>
<button class="stop-btn" id="tscmStopBaselineBtn" onclick="tscmStopBaseline()" style="width: 100%; padding: 8px; display: none;">
Stop Recording
</button>
<div id="tscmBaselineStatus" style="margin-top: 8px; font-size: 11px; color: var(--text-muted);"></div>
</div>
<button class="run-btn" id="startTscmBtn" onclick="startTscmSweep()">
Start Sweep
</button>
<button class="stop-btn" id="stopTscmBtn" onclick="stopTscmSweep()" style="display: none;">
Stop Sweep
</button>
<button class="preset-btn" id="tscmReportBtn" onclick="generateTscmReport()" style="display: none; width: 100%; margin-top: 8px; background: var(--accent-cyan); color: #000; font-weight: 600;">
📄 Generate Report
</button>
<!-- Meeting Window Control -->
<div class="section" id="tscmMeetingSection" style="margin-top: 12px;">
<h3>Meeting Window</h3>
<div id="tscmMeetingStatus" style="font-size: 11px; color: var(--text-muted); margin-bottom: 8px;">
No active meeting
</div>
<div class="form-group">
<input type="text" id="tscmMeetingName" placeholder="Meeting name (optional)">
</div>
<button class="run-btn" id="tscmStartMeetingBtn" onclick="tscmStartMeeting()" style="width: 100%; padding: 8px;">
🎯 Start Meeting Window
</button>
<button class="stop-btn" id="tscmEndMeetingBtn" onclick="tscmEndMeeting()" style="width: 100%; padding: 8px; display: none;">
⏹ End Meeting Window
</button>
<div style="font-size: 9px; color: var(--text-muted); margin-top: 4px;">
Devices detected during meetings get flagged
</div>
</div>
<!-- Quick Actions -->
<div class="section" style="margin-top: 12px;">
<h3>Quick Actions</h3>
<div style="display: flex; flex-direction: column; gap: 6px;">
<button class="preset-btn" onclick="tscmShowCapabilities()" style="width: 100%; font-size: 10px;">
⚙️ View Capabilities
</button>
<button class="preset-btn" onclick="tscmShowKnownDevices()" style="width: 100%; font-size: 10px;">
✅ Known Devices
</button>
<button class="preset-btn" onclick="tscmShowCases()" style="width: 100%; font-size: 10px;">
📁 Cases
</button>
<button class="preset-btn" onclick="tscmShowPlaybooks()" style="width: 100%; font-size: 10px;">
📋 Playbooks
</button>
</div>
</div>
<!-- Export Options -->
<div class="section" id="tscmExportSection" style="margin-top: 12px; display: none;">
<h3>Export Report</h3>
<div style="display: flex; gap: 6px;">
<button class="preset-btn" onclick="tscmDownloadPdf()" style="flex: 1; font-size: 10px;">
📄 PDF
</button>
<button class="preset-btn" onclick="tscmDownloadAnnex('json')" style="flex: 1; font-size: 10px;">
📊 JSON
</button>
<button class="preset-btn" onclick="tscmDownloadAnnex('csv')" style="flex: 1; font-size: 10px;">
📈 CSV
</button>
</div>
</div>
<!-- Futuristic Scanner Progress -->
@@ -44,99 +156,6 @@
</div>
</div>
<!-- Report Button (shown after scan) -->
<button class="preset-btn" id="tscmReportBtn" onclick="generateTscmReport()" style="display: none; width: 100%; margin-top: 8px; background: var(--accent-cyan); color: #000; font-weight: 600;">
Generate Report
</button>
<!-- Export Options (shown after scan) -->
<div id="tscmExportSection" style="display: none; margin-top: 8px;">
<div style="display: flex; gap: 6px;">
<button class="preset-btn" onclick="tscmDownloadPdf()" style="flex: 1; font-size: 10px;">PDF</button>
<button class="preset-btn" onclick="tscmDownloadAnnex('json')" style="flex: 1; font-size: 10px;">JSON</button>
<button class="preset-btn" onclick="tscmDownloadAnnex('csv')" style="flex: 1; font-size: 10px;">CSV</button>
</div>
</div>
<!-- Scan Sources -->
<div class="section" style="margin-top: 12px;">
<div style="display: flex; flex-direction: column; gap: 6px;">
<label style="display: flex; align-items: center; gap: 8px; font-size: 12px; cursor: pointer;">
<input type="checkbox" id="tscmWifiEnabled" checked style="margin: 0;">
<span>WiFi</span>
<select id="tscmWifiInterface" style="margin-left: auto; font-size: 10px; padding: 2px 4px; max-width: 100px;">
<option value="">Auto</option>
</select>
</label>
<label style="display: flex; align-items: center; gap: 8px; font-size: 12px; cursor: pointer;">
<input type="checkbox" id="tscmBtEnabled" checked style="margin: 0;">
<span>Bluetooth</span>
<select id="tscmBtInterface" style="margin-left: auto; font-size: 10px; padding: 2px 4px; max-width: 100px;">
<option value="">Auto</option>
</select>
</label>
<label style="display: flex; align-items: center; gap: 8px; font-size: 12px; cursor: pointer;">
<input type="checkbox" id="tscmRfEnabled" style="margin: 0;">
<span>RF/SDR</span>
<select id="tscmSdrDevice" style="margin-left: auto; font-size: 10px; padding: 2px 4px; max-width: 100px;">
<option value="">None</option>
</select>
</label>
</div>
<button class="preset-btn" onclick="refreshTscmDevices()" style="width: 100%; margin-top: 8px; font-size: 10px; padding: 4px;">
Refresh Devices
</button>
</div>
<!-- Baseline -->
<div class="section" style="margin-top: 8px;">
<div class="form-group" style="margin-bottom: 8px;">
<label style="font-size: 11px;">Compare Against</label>
<select id="tscmBaselineSelect" style="width: 100%;">
<option value="">No Baseline</option>
</select>
</div>
<input type="text" id="tscmBaselineName" placeholder="New baseline name..." style="width: 100%; margin-bottom: 6px; font-size: 11px;">
<button class="preset-btn" id="tscmRecordBaselineBtn" onclick="tscmRecordBaseline()" style="width: 100%; font-size: 10px; padding: 4px;">
Record Baseline
</button>
<button class="stop-btn" id="tscmStopBaselineBtn" onclick="tscmStopBaseline()" style="width: 100%; display: none; font-size: 10px; padding: 4px;">
Stop Recording
</button>
<div id="tscmBaselineStatus" style="margin-top: 4px; font-size: 10px; color: var(--text-muted);"></div>
</div>
<!-- Meeting Window -->
<div class="section" id="tscmMeetingSection" style="margin-top: 8px;">
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px;">
<span style="font-size: 11px; color: var(--text-secondary);">Meeting Window</span>
<span id="tscmMeetingStatus" style="font-size: 10px; color: var(--text-muted);">Inactive</span>
</div>
<input type="text" id="tscmMeetingName" placeholder="Meeting name (optional)" style="width: 100%; margin-bottom: 6px; font-size: 11px;">
<button class="preset-btn" id="tscmStartMeetingBtn" onclick="tscmStartMeeting()" style="width: 100%; font-size: 10px; padding: 4px;">
Start Meeting
</button>
<button class="stop-btn" id="tscmEndMeetingBtn" onclick="tscmEndMeeting()" style="width: 100%; display: none; font-size: 10px; padding: 4px;">
End Meeting
</button>
</div>
<!-- Compact Tools Row -->
<div style="display: flex; gap: 6px; margin-top: 12px;">
<button class="preset-btn tscm-tool-btn" onclick="tscmShowCapabilities()" title="Capabilities">
<span style="font-size: 14px;">⚙️</span>
</button>
<button class="preset-btn tscm-tool-btn" onclick="tscmShowKnownDevices()" title="Known Devices">
<span style="font-size: 14px;"></span>
</button>
<button class="preset-btn tscm-tool-btn" onclick="tscmShowCases()" title="Cases">
<span style="font-size: 14px;">📁</span>
</button>
<button class="preset-btn tscm-tool-btn" onclick="tscmShowPlaybooks()" title="Playbooks">
<span style="font-size: 14px;">📋</span>
</button>
</div>
<!-- Device Warnings -->
<div id="tscmDeviceWarnings" style="display: none; margin-top: 8px; padding: 8px; background: rgba(255,153,51,0.1); border: 1px solid rgba(255,153,51,0.3); border-radius: 4px;"></div>
</div>