Add dropdown navigation menus and fix TSCM baseline recording

- Convert flat mode nav buttons into dropdown menus by category (SDR/RF, Wireless, Security)
- Add CSS styles for dropdown animations and active state highlighting
- Fix baseline recording by feeding device data to recorder endpoints
- Remove redundant threat summary section from TSCM sidebar

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Smittix
2026-01-14 16:51:45 +00:00
parent 66c7db73e2
commit 30126b1709
2 changed files with 213 additions and 38 deletions
+103
View File
@@ -470,6 +470,109 @@ header h1 {
color: var(--bg-primary);
}
/* Dropdown Navigation */
.mode-nav-dropdown {
position: relative;
}
.mode-nav-dropdown-btn {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 14px;
background: transparent;
border: 1px solid transparent;
border-radius: 4px;
color: var(--text-secondary);
font-family: 'Inter', sans-serif;
font-size: 11px;
font-weight: 500;
cursor: pointer;
transition: all 0.15s ease;
}
.mode-nav-dropdown-btn:hover {
background: var(--bg-elevated);
color: var(--text-primary);
border-color: var(--border-color);
}
.mode-nav-dropdown-btn .nav-icon {
font-size: 14px;
}
.mode-nav-dropdown-btn .nav-label {
text-transform: uppercase;
letter-spacing: 0.5px;
}
.mode-nav-dropdown-btn .dropdown-arrow {
font-size: 8px;
margin-left: 4px;
transition: transform 0.2s ease;
}
.mode-nav-dropdown.open .mode-nav-dropdown-btn {
background: var(--bg-elevated);
color: var(--text-primary);
border-color: var(--border-color);
}
.mode-nav-dropdown.open .dropdown-arrow {
transform: rotate(180deg);
}
.mode-nav-dropdown.has-active .mode-nav-dropdown-btn {
background: var(--accent-cyan);
color: var(--bg-primary);
border-color: var(--accent-cyan);
}
.mode-nav-dropdown.has-active .mode-nav-dropdown-btn .nav-icon {
filter: brightness(0);
}
.mode-nav-dropdown-menu {
position: absolute;
top: 100%;
left: 0;
margin-top: 4px;
min-width: 180px;
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 6px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
opacity: 0;
visibility: hidden;
transform: translateY(-8px);
transition: all 0.15s ease;
z-index: 1000;
padding: 6px;
}
.mode-nav-dropdown.open .mode-nav-dropdown-menu {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
.mode-nav-dropdown-menu .mode-nav-btn {
width: 100%;
justify-content: flex-start;
padding: 10px 12px;
border-radius: 4px;
margin: 0;
}
.mode-nav-dropdown-menu .mode-nav-btn:hover {
background: var(--bg-elevated);
}
.mode-nav-dropdown-menu .mode-nav-btn.active {
background: var(--accent-cyan);
color: var(--bg-primary);
}
.version-badge {
font-size: 0.6rem;
font-weight: 500;
+110 -38
View File
@@ -295,25 +295,41 @@
<!-- Mode Navigation Bar -->
<nav class="mode-nav">
<div class="mode-nav-group">
<span class="mode-nav-label">SDR / RF</span>
<button class="mode-nav-btn active" onclick="switchMode('pager')"><span class="nav-icon">📟</span><span class="nav-label">Pager</span></button>
<button class="mode-nav-btn" onclick="switchMode('sensor')"><span class="nav-icon">📡</span><span class="nav-label">433MHz</span></button>
<button class="mode-nav-btn" onclick="switchMode('aircraft')"><span class="nav-icon">✈️</span><span class="nav-label">Aircraft</span></button>
<button class="mode-nav-btn" onclick="switchMode('aprs')"><span class="nav-icon">📍</span><span class="nav-label">APRS</span></button>
<button class="mode-nav-btn" onclick="switchMode('satellite')"><span class="nav-icon">🛰️</span><span class="nav-label">Satellite</span></button>
<button class="mode-nav-btn" onclick="switchMode('listening')"><span class="nav-icon">📻</span><span class="nav-label">Listening Post</span></button>
<div class="mode-nav-dropdown" data-group="sdr">
<button class="mode-nav-dropdown-btn" onclick="toggleNavDropdown('sdr')">
<span class="nav-icon">📡</span>
<span class="nav-label">SDR / RF</span>
<span class="dropdown-arrow"></span>
</button>
<div class="mode-nav-dropdown-menu">
<button class="mode-nav-btn active" onclick="switchMode('pager')"><span class="nav-icon">📟</span><span class="nav-label">Pager</span></button>
<button class="mode-nav-btn" onclick="switchMode('sensor')"><span class="nav-icon">📡</span><span class="nav-label">433MHz</span></button>
<button class="mode-nav-btn" onclick="switchMode('aircraft')"><span class="nav-icon">✈️</span><span class="nav-label">Aircraft</span></button>
<button class="mode-nav-btn" onclick="switchMode('aprs')"><span class="nav-icon">📍</span><span class="nav-label">APRS</span></button>
<button class="mode-nav-btn" onclick="switchMode('satellite')"><span class="nav-icon">🛰️</span><span class="nav-label">Satellite</span></button>
<button class="mode-nav-btn" onclick="switchMode('listening')"><span class="nav-icon">📻</span><span class="nav-label">Listening Post</span></button>
</div>
</div>
<div class="mode-nav-divider"></div>
<div class="mode-nav-group">
<span class="mode-nav-label">Wireless</span>
<button class="mode-nav-btn" onclick="switchMode('wifi')"><span class="nav-icon">📶</span><span class="nav-label">WiFi</span></button>
<button class="mode-nav-btn" onclick="switchMode('bluetooth')"><span class="nav-icon">🔵</span><span class="nav-label">Bluetooth</span></button>
<div class="mode-nav-dropdown" data-group="wireless">
<button class="mode-nav-dropdown-btn" onclick="toggleNavDropdown('wireless')">
<span class="nav-icon">📶</span>
<span class="nav-label">Wireless</span>
<span class="dropdown-arrow"></span>
</button>
<div class="mode-nav-dropdown-menu">
<button class="mode-nav-btn" onclick="switchMode('wifi')"><span class="nav-icon">📶</span><span class="nav-label">WiFi</span></button>
<button class="mode-nav-btn" onclick="switchMode('bluetooth')"><span class="nav-icon">🔵</span><span class="nav-label">Bluetooth</span></button>
</div>
</div>
<div class="mode-nav-divider"></div>
<div class="mode-nav-group">
<span class="mode-nav-label">Security</span>
<button class="mode-nav-btn" onclick="switchMode('tscm')"><span class="nav-icon">🔍</span><span class="nav-label">TSCM</span></button>
<div class="mode-nav-dropdown" data-group="security">
<button class="mode-nav-dropdown-btn" onclick="toggleNavDropdown('security')">
<span class="nav-icon">🔍</span>
<span class="nav-label">Security</span>
<span class="dropdown-arrow"></span>
</button>
<div class="mode-nav-dropdown-menu">
<button class="mode-nav-btn" onclick="switchMode('tscm')"><span class="nav-icon">🔍</span><span class="nav-label">TSCM</span></button>
</div>
</div>
<div class="mode-nav-actions">
<a href="/adsb/dashboard" target="_blank" class="nav-action-btn" id="adsbDashboardBtn" style="display: none;">
@@ -1104,16 +1120,6 @@
<div id="tscmBaselineStatus" style="margin-top: 8px; font-size: 11px; color: var(--text-muted);"></div>
</div>
<div class="section">
<h3>Threat Summary</h3>
<div id="tscmThreatSummary" style="display: grid; grid-template-columns: 1fr 1fr; gap: 6px;">
<div class="threat-card critical"><span class="count">0</span><span class="label">Critical</span></div>
<div class="threat-card high"><span class="count">0</span><span class="label">High</span></div>
<div class="threat-card medium"><span class="count">0</span><span class="label">Medium</span></div>
<div class="threat-card low"><span class="count">0</span><span class="label">Low</span></div>
</div>
</div>
<button class="run-btn" id="startTscmBtn" onclick="startTscmSweep()">
Start Sweep
</button>
@@ -3353,6 +3359,9 @@
// Load pager message filters
loadPagerFilters();
// Initialize dropdown nav active state
updateDropdownActiveState();
});
// Toggle section collapse
@@ -3360,6 +3369,51 @@
el.closest('.section').classList.toggle('collapsed');
}
// Dropdown navigation
function toggleNavDropdown(group) {
const dropdown = document.querySelector(`.mode-nav-dropdown[data-group="${group}"]`);
const isOpen = dropdown.classList.contains('open');
// Close all dropdowns first
document.querySelectorAll('.mode-nav-dropdown').forEach(d => d.classList.remove('open'));
// Open this one if it was closed
if (!isOpen) {
dropdown.classList.add('open');
}
}
function closeAllDropdowns() {
document.querySelectorAll('.mode-nav-dropdown').forEach(d => d.classList.remove('open'));
}
function updateDropdownActiveState() {
// Map modes to their dropdown groups
const modeGroups = {
'pager': 'sdr', 'sensor': 'sdr', 'aircraft': 'sdr',
'aprs': 'sdr', 'satellite': 'sdr', 'listening': 'sdr',
'wifi': 'wireless', 'bluetooth': 'wireless',
'tscm': 'security'
};
// Remove has-active from all dropdowns
document.querySelectorAll('.mode-nav-dropdown').forEach(d => d.classList.remove('has-active'));
// Add has-active to the dropdown containing the current mode
const activeGroup = modeGroups[currentMode];
if (activeGroup) {
const dropdown = document.querySelector(`.mode-nav-dropdown[data-group="${activeGroup}"]`);
if (dropdown) dropdown.classList.add('has-active');
}
}
// Close dropdowns when clicking outside
document.addEventListener('click', function(e) {
if (!e.target.closest('.mode-nav-dropdown')) {
closeAllDropdowns();
}
});
// Mode switching
function switchMode(mode) {
// Stop any running scans when switching modes
@@ -3372,6 +3426,11 @@
if (isTscmRunning) stopTscmSweep();
currentMode = mode;
// Close dropdowns and update active state
closeAllDropdowns();
updateDropdownActiveState();
// Remove active from all nav buttons, then add to the correct one
document.querySelectorAll('.mode-nav-btn').forEach(btn => btn.classList.remove('active'));
const modeMap = {
@@ -10400,6 +10459,14 @@
if (device.score >= 3) {
addHighInterestDevice(device, 'wifi');
}
// Feed to baseline recorder if recording
if (isRecordingBaseline) {
fetch('/tscm/feed/wifi', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(device)
}).catch(e => console.error('Baseline feed error:', e));
}
}
}
@@ -10414,6 +10481,14 @@
if (device.score >= 3) {
addHighInterestDevice(device, 'bluetooth');
}
// Feed to baseline recorder if recording
if (isRecordingBaseline) {
fetch('/tscm/feed/bluetooth', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(device)
}).catch(e => console.error('Baseline feed error:', e));
}
}
}
@@ -10433,6 +10508,14 @@
if (signal.score >= 3) {
addHighInterestDevice(signal, 'rf');
}
// Feed to baseline recorder if recording
if (isRecordingBaseline) {
fetch('/tscm/feed/rf', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(signal)
}).catch(e => console.error('Baseline feed error:', e));
}
}
}
@@ -10530,17 +10613,6 @@
statusText = parts.length > 0 ? parts.join(' | ') : 'SCANNING...';
}
document.getElementById('tscmProgressLabel').textContent = statusText;
// Update counts in sidebar (from severity_counts object)
const counts = data.severity_counts || {};
const criticalEl = document.querySelector('#tscmThreatSummary .threat-card.critical .count');
const highEl = document.querySelector('#tscmThreatSummary .threat-card.high .count');
const mediumEl = document.querySelector('#tscmThreatSummary .threat-card.medium .count');
const lowEl = document.querySelector('#tscmThreatSummary .threat-card.low .count');
if (criticalEl) criticalEl.textContent = counts.critical || 0;
if (highEl) highEl.textContent = counts.high || 0;
if (mediumEl) mediumEl.textContent = counts.medium || 0;
if (lowEl) lowEl.textContent = counts.low || 0;
}
function addTscmThreat(threat) {