mirror of
https://github.com/smittix/intercept.git
synced 2026-06-09 06:31:55 -07:00
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:
@@ -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
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user