mirror of
https://github.com/smittix/intercept.git
synced 2026-06-15 17:11:56 -07:00
Fix TSCM sweep scan resilience and add per-device error isolation
The sweep loop's WiFi/BT/RF scan processing had unprotected timeline_manager.add_observation() calls that could crash an entire scan iteration, silently preventing all device events from reaching the frontend. Additionally, scan interval timestamps were only updated at the end of processing, causing tight retry loops on persistent errors. - Wrap timeline observation calls in try/except for all three protocols - Move last_*_scan timestamp updates immediately after scan completes - Add per-device try/except so one bad device doesn't block others - Emit sweep_progress after WiFi scan for real-time status visibility - Log warning when WiFi scan returns 0 networks for easier diagnosis - Add known_device and score_modifier fields to correlation engine - Add TSCM scheduling, cases, known devices, and advanced WiFi indicators Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -45,6 +45,7 @@
|
||||
padding: 12px;
|
||||
background: rgba(0,0,0,0.3);
|
||||
border-radius: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.tscm-threat-banner .threat-card {
|
||||
flex: 1;
|
||||
@@ -200,6 +201,17 @@
|
||||
margin-left: 6px;
|
||||
font-size: 10px;
|
||||
}
|
||||
.known-badge {
|
||||
margin-left: 6px;
|
||||
font-size: 9px;
|
||||
padding: 1px 4px;
|
||||
border-radius: 3px;
|
||||
background: rgba(0, 255, 136, 0.2);
|
||||
color: #00ff88;
|
||||
border: 1px solid rgba(0, 255, 136, 0.4);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.4px;
|
||||
}
|
||||
.tscm-device-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -465,6 +477,18 @@
|
||||
color: var(--text-dim);
|
||||
width: 40%;
|
||||
}
|
||||
.device-detail-id {
|
||||
display: inline-block;
|
||||
margin-left: 6px;
|
||||
font-size: 10px;
|
||||
color: var(--text-muted);
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
.tscm-more-hint {
|
||||
margin-top: 6px;
|
||||
font-size: 10px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
.indicator-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -882,6 +906,42 @@
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* Filters */
|
||||
.tscm-filter-bar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
margin-bottom: 12px;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 4px;
|
||||
align-items: flex-end;
|
||||
}
|
||||
.tscm-filter-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
.tscm-filter-group label {
|
||||
font-size: 9px;
|
||||
color: var(--text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.6px;
|
||||
}
|
||||
.tscm-filter-group select {
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
color: var(--text-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
padding: 4px 6px;
|
||||
font-size: 10px;
|
||||
}
|
||||
.tscm-filter-status {
|
||||
margin-left: auto;
|
||||
font-size: 10px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Advanced Modal Styles */
|
||||
.tscm-advanced-modal {
|
||||
max-width: 600px;
|
||||
@@ -1461,3 +1521,156 @@
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
/* Meeting banner actions */
|
||||
.tscm-meeting-banner {
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.tscm-meeting-banner .meeting-actions {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* Case linking */
|
||||
.tscm-case-link-banner {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 10px;
|
||||
margin-bottom: 10px;
|
||||
background: rgba(74, 158, 255, 0.12);
|
||||
border: 1px solid rgba(74, 158, 255, 0.3);
|
||||
border-radius: 6px;
|
||||
font-size: 11px;
|
||||
}
|
||||
.case-actions {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.tscm-case-link-btn {
|
||||
margin-left: auto;
|
||||
font-size: 9px;
|
||||
padding: 2px 6px;
|
||||
background: rgba(74, 158, 255, 0.2);
|
||||
color: #9ed0ff;
|
||||
border: 1px solid rgba(74, 158, 255, 0.4);
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Schedules */
|
||||
.tscm-schedule-form {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
.tscm-schedule-list {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
.tscm-schedule-item {
|
||||
padding: 10px;
|
||||
border-radius: 6px;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
.tscm-schedule-item.enabled {
|
||||
border-color: rgba(0, 255, 136, 0.35);
|
||||
}
|
||||
.tscm-schedule-item.disabled {
|
||||
opacity: 0.7;
|
||||
}
|
||||
.tscm-schedule-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.tscm-schedule-status {
|
||||
font-size: 9px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
.tscm-schedule-meta {
|
||||
font-size: 10px;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.tscm-schedule-actions {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
/* Meeting summary */
|
||||
.tscm-summary-list {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
.tscm-summary-item {
|
||||
padding: 8px 10px;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
}
|
||||
.tscm-summary-meta {
|
||||
font-size: 10px;
|
||||
color: var(--text-muted);
|
||||
margin-top: 4px;
|
||||
}
|
||||
.tscm-summary-risk {
|
||||
font-size: 10px;
|
||||
color: #ff9933;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* Case notes */
|
||||
.tscm-case-notes {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.tscm-case-note {
|
||||
padding: 8px 10px;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
}
|
||||
.tscm-case-note-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 9px;
|
||||
color: var(--text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.tscm-case-note-type {
|
||||
color: var(--accent-cyan);
|
||||
}
|
||||
.tscm-case-note-content {
|
||||
font-size: 11px;
|
||||
line-height: 1.4;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.tscm-case-note-author {
|
||||
font-size: 9px;
|
||||
color: var(--text-muted);
|
||||
margin-top: 4px;
|
||||
}
|
||||
.tscm-case-note-form {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.tscm-case-note-form textarea {
|
||||
min-height: 80px;
|
||||
}
|
||||
.tscm-case-note-actions {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
@@ -865,15 +865,12 @@ function connectAgentStream(mode, onMessage) {
|
||||
}
|
||||
|
||||
let streamUrl;
|
||||
if (currentAgent === 'local') {
|
||||
streamUrl = `/${mode}/stream`;
|
||||
} else {
|
||||
// For remote agents, we could either:
|
||||
// 1. Use the multi-agent stream: /controller/stream/all
|
||||
// 2. Or proxy through controller (not implemented yet)
|
||||
// For now, use multi-agent stream which includes agent_name tagging
|
||||
streamUrl = '/controller/stream/all';
|
||||
}
|
||||
if (currentAgent === 'local') {
|
||||
streamUrl = `/${mode}/stream`;
|
||||
} else {
|
||||
// For remote agents, proxy SSE through controller
|
||||
streamUrl = `/controller/agents/${currentAgent}/${mode}/stream`;
|
||||
}
|
||||
|
||||
agentEventSource = new EventSource(streamUrl);
|
||||
|
||||
@@ -881,15 +878,7 @@ function connectAgentStream(mode, onMessage) {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
|
||||
// If using multi-agent stream, filter by current agent if needed
|
||||
if (streamUrl === '/controller/stream/all' && currentAgent !== 'local') {
|
||||
const agent = agents.find(a => a.id == currentAgent);
|
||||
if (agent && data.agent_name && data.agent_name !== agent.name) {
|
||||
return; // Skip messages from other agents
|
||||
}
|
||||
}
|
||||
|
||||
onMessage(data);
|
||||
onMessage(data);
|
||||
} catch (e) {
|
||||
console.error('Error parsing SSE message:', e);
|
||||
}
|
||||
|
||||
@@ -879,6 +879,7 @@ const WiFiMode = (function() {
|
||||
updateNetworkRow(network);
|
||||
updateStats();
|
||||
updateProximityRadar();
|
||||
updateChannelChart();
|
||||
|
||||
if (onNetworkUpdate) onNetworkUpdate(network);
|
||||
}
|
||||
@@ -1420,9 +1421,15 @@ const WiFiMode = (function() {
|
||||
return Object.values(stats).filter(s => s.ap_count > 0 || [1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161, 165].includes(s.channel));
|
||||
}
|
||||
|
||||
function updateChannelChart(band = '2.4') {
|
||||
function updateChannelChart(band) {
|
||||
if (typeof ChannelChart === 'undefined') return;
|
||||
|
||||
// Use the currently active band tab if no band specified
|
||||
if (!band) {
|
||||
const activeTab = elements.channelBandTabs && elements.channelBandTabs.querySelector('.channel-band-tab.active');
|
||||
band = activeTab ? activeTab.dataset.band : '2.4';
|
||||
}
|
||||
|
||||
// Recalculate channel stats from networks if needed
|
||||
if (channelStats.length === 0 && networks.size > 0) {
|
||||
channelStats = calculateChannelStats();
|
||||
|
||||
Reference in New Issue
Block a user