mirror of
https://github.com/smittix/intercept.git
synced 2026-07-19 06:48:11 -07:00
Add live monitoring status overlay with heartbeat updates
Backend: monitor_thread sends periodic monitor_heartbeat events (every 5s) with elapsed time, packet count, and device count so the frontend knows monitoring is active. Frontend: new monitoring overlay replaces scan progress bar when auto-monitor starts. Shows pulsing green indicator, ARFCN being monitored, live elapsed timer, packet/device counts, and "Listening..."/"Capturing" activity state. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1628,6 +1628,10 @@ def monitor_thread(process):
|
|||||||
stderr_thread = threading.Thread(target=read_stderr, daemon=True)
|
stderr_thread = threading.Thread(target=read_stderr, daemon=True)
|
||||||
stderr_thread.start()
|
stderr_thread.start()
|
||||||
|
|
||||||
|
monitor_start_time = time.time()
|
||||||
|
packets_captured = 0
|
||||||
|
last_heartbeat = time.time()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
while app_module.gsm_spy_monitor_process:
|
while app_module.gsm_spy_monitor_process:
|
||||||
# Check if process died
|
# Check if process died
|
||||||
@@ -1635,6 +1639,21 @@ def monitor_thread(process):
|
|||||||
logger.info(f"Monitor process exited (code: {process.returncode})")
|
logger.info(f"Monitor process exited (code: {process.returncode})")
|
||||||
break
|
break
|
||||||
|
|
||||||
|
# Send periodic heartbeat so frontend knows monitor is alive
|
||||||
|
now = time.time()
|
||||||
|
if now - last_heartbeat >= 5:
|
||||||
|
last_heartbeat = now
|
||||||
|
elapsed = int(now - monitor_start_time)
|
||||||
|
try:
|
||||||
|
app_module.gsm_spy_queue.put_nowait({
|
||||||
|
'type': 'monitor_heartbeat',
|
||||||
|
'elapsed': elapsed,
|
||||||
|
'packets': packets_captured,
|
||||||
|
'devices': len(app_module.gsm_spy_devices)
|
||||||
|
})
|
||||||
|
except queue.Full:
|
||||||
|
pass
|
||||||
|
|
||||||
# Get output from queue with timeout
|
# Get output from queue with timeout
|
||||||
try:
|
try:
|
||||||
msg_type, line = output_queue_local.get(timeout=1.0)
|
msg_type, line = output_queue_local.get(timeout=1.0)
|
||||||
@@ -1646,6 +1665,8 @@ def monitor_thread(process):
|
|||||||
|
|
||||||
parsed = parse_tshark_output(line)
|
parsed = parse_tshark_output(line)
|
||||||
if parsed:
|
if parsed:
|
||||||
|
packets_captured += 1
|
||||||
|
|
||||||
# Store in DataStore
|
# Store in DataStore
|
||||||
key = parsed.get('tmsi') or parsed.get('imsi') or str(time.time())
|
key = parsed.get('tmsi') or parsed.get('imsi') or str(time.time())
|
||||||
app_module.gsm_spy_devices[key] = parsed
|
app_module.gsm_spy_devices[key] = parsed
|
||||||
|
|||||||
@@ -525,6 +525,91 @@
|
|||||||
box-shadow: 0 0 8px rgba(0, 229, 255, 0.4);
|
box-shadow: 0 0 8px rgba(0, 229, 255, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Monitor Status Overlay */
|
||||||
|
.monitor-status-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
background: linear-gradient(180deg, rgba(10, 14, 20, 0.95) 0%, rgba(10, 14, 20, 0.85) 100%);
|
||||||
|
border-bottom: 1px solid var(--accent-green, #4caf50);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
padding: 10px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-status-inner {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-status-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-pulse {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--accent-green, #4caf50);
|
||||||
|
box-shadow: 0 0 6px var(--accent-green, #4caf50);
|
||||||
|
animation: pulse-glow 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse-glow {
|
||||||
|
0%, 100% { opacity: 1; box-shadow: 0 0 6px var(--accent-green, #4caf50); }
|
||||||
|
50% { opacity: 0.5; box-shadow: 0 0 12px var(--accent-green, #4caf50); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-label {
|
||||||
|
color: var(--accent-green, #4caf50);
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 1.5px;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-arfcn {
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-elapsed {
|
||||||
|
margin-left: auto;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-stats-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 5px;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
padding-left: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-stat-sep {
|
||||||
|
width: 3px;
|
||||||
|
height: 3px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--text-secondary);
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-listening {
|
||||||
|
animation: listening-fade 2.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes listening-fade {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
|
50% { opacity: 0.3; }
|
||||||
|
}
|
||||||
|
|
||||||
/* Right Sidebar */
|
/* Right Sidebar */
|
||||||
.right-sidebar {
|
.right-sidebar {
|
||||||
background: var(--bg-panel);
|
background: var(--bg-panel);
|
||||||
@@ -1293,6 +1378,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="monitorStatus" class="monitor-status-overlay" style="display:none;">
|
||||||
|
<div class="monitor-status-inner">
|
||||||
|
<div class="monitor-status-row">
|
||||||
|
<span class="monitor-pulse"></span>
|
||||||
|
<span class="monitor-label">MONITORING</span>
|
||||||
|
<span class="monitor-arfcn" id="monitorArfcn">ARFCN ---</span>
|
||||||
|
<span class="monitor-elapsed" id="monitorElapsed">00:00</span>
|
||||||
|
</div>
|
||||||
|
<div class="monitor-stats-row">
|
||||||
|
<span class="monitor-stat"><span id="monitorPackets">0</span> packets</span>
|
||||||
|
<span class="monitor-stat-sep"></span>
|
||||||
|
<span class="monitor-stat"><span id="monitorDevices">0</span> devices</span>
|
||||||
|
<span class="monitor-stat-sep"></span>
|
||||||
|
<span class="monitor-stat monitor-listening" id="monitorActivity">Listening...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div id="gsmMap"></div>
|
<div id="gsmMap"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1667,6 +1769,7 @@
|
|||||||
eventSource = null;
|
eventSource = null;
|
||||||
}
|
}
|
||||||
document.getElementById('scanProgress').style.display = 'none';
|
document.getElementById('scanProgress').style.display = 'none';
|
||||||
|
hideMonitorStatus();
|
||||||
console.log('[GSM SPY] Scanner stopped');
|
console.log('[GSM SPY] Scanner stopped');
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
@@ -1740,13 +1843,16 @@
|
|||||||
updateScanStatus('Scan #' + data.scan + ' complete (' + data.towers_found + ' towers, ' + data.duration + 's)');
|
updateScanStatus('Scan #' + data.scan + ' complete (' + data.towers_found + ' towers, ' + data.duration + 's)');
|
||||||
document.getElementById('scanProgressBar').style.width = '100%';
|
document.getElementById('scanProgressBar').style.width = '100%';
|
||||||
} else if (data.type === 'auto_monitor_started') {
|
} else if (data.type === 'auto_monitor_started') {
|
||||||
updateScanStatus('Monitoring ARFCN ' + data.arfcn + ' for devices...');
|
showMonitorStatus(data.arfcn);
|
||||||
console.log('[GSM SPY] Auto-monitor started on ARFCN', data.arfcn);
|
console.log('[GSM SPY] Auto-monitor started on ARFCN', data.arfcn);
|
||||||
|
} else if (data.type === 'monitor_heartbeat') {
|
||||||
|
updateMonitorStatus(data.elapsed, data.packets, data.devices);
|
||||||
} else if (data.type === 'error') {
|
} else if (data.type === 'error') {
|
||||||
console.error('[GSM SPY] Server error:', data.message);
|
console.error('[GSM SPY] Server error:', data.message);
|
||||||
updateScanStatus('Error: ' + data.message);
|
updateScanStatus('Error: ' + data.message);
|
||||||
} else if (data.type === 'disconnected') {
|
} else if (data.type === 'disconnected') {
|
||||||
console.warn('[GSM SPY] Server disconnected stream');
|
console.warn('[GSM SPY] Server disconnected stream');
|
||||||
|
hideMonitorStatus();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[GSM SPY] Error parsing event:', error, 'raw:', e.data);
|
console.error('[GSM SPY] Error parsing event:', error, 'raw:', e.data);
|
||||||
@@ -2002,6 +2108,57 @@
|
|||||||
statusText.textContent = message;
|
statusText.textContent = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let monitorStartTime = null;
|
||||||
|
let monitorTimerInterval = null;
|
||||||
|
|
||||||
|
function showMonitorStatus(arfcn) {
|
||||||
|
// Hide scan progress, show monitor status
|
||||||
|
document.getElementById('scanProgress').style.display = 'none';
|
||||||
|
const overlay = document.getElementById('monitorStatus');
|
||||||
|
overlay.style.display = 'block';
|
||||||
|
document.getElementById('monitorArfcn').textContent = 'ARFCN ' + arfcn;
|
||||||
|
document.getElementById('monitorPackets').textContent = '0';
|
||||||
|
document.getElementById('monitorDevices').textContent = '0';
|
||||||
|
document.getElementById('monitorActivity').textContent = 'Listening...';
|
||||||
|
|
||||||
|
// Start local elapsed timer for smooth updates between heartbeats
|
||||||
|
monitorStartTime = Date.now();
|
||||||
|
if (monitorTimerInterval) clearInterval(monitorTimerInterval);
|
||||||
|
monitorTimerInterval = setInterval(function() {
|
||||||
|
const elapsed = Math.floor((Date.now() - monitorStartTime) / 1000);
|
||||||
|
document.getElementById('monitorElapsed').textContent = formatElapsed(elapsed);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateMonitorStatus(elapsed, packets, devices) {
|
||||||
|
const overlay = document.getElementById('monitorStatus');
|
||||||
|
if (overlay.style.display === 'none') return;
|
||||||
|
document.getElementById('monitorElapsed').textContent = formatElapsed(elapsed);
|
||||||
|
document.getElementById('monitorPackets').textContent = packets;
|
||||||
|
document.getElementById('monitorDevices').textContent = devices;
|
||||||
|
// Sync local timer with server elapsed
|
||||||
|
monitorStartTime = Date.now() - (elapsed * 1000);
|
||||||
|
// Flash activity indicator on heartbeat
|
||||||
|
const activity = document.getElementById('monitorActivity');
|
||||||
|
activity.textContent = packets > 0 ? 'Capturing' : 'Listening...';
|
||||||
|
activity.style.color = packets > 0 ? 'var(--accent-green, #4caf50)' : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideMonitorStatus() {
|
||||||
|
document.getElementById('monitorStatus').style.display = 'none';
|
||||||
|
if (monitorTimerInterval) {
|
||||||
|
clearInterval(monitorTimerInterval);
|
||||||
|
monitorTimerInterval = null;
|
||||||
|
}
|
||||||
|
monitorStartTime = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatElapsed(seconds) {
|
||||||
|
const m = Math.floor(seconds / 60);
|
||||||
|
const s = seconds % 60;
|
||||||
|
return String(m).padStart(2, '0') + ':' + String(s).padStart(2, '0');
|
||||||
|
}
|
||||||
|
|
||||||
function updateTowersList() {
|
function updateTowersList() {
|
||||||
const listDiv = document.getElementById('towersList');
|
const listDiv = document.getElementById('towersList');
|
||||||
const towerCount = Object.keys(towers).length;
|
const towerCount = Object.keys(towers).length;
|
||||||
|
|||||||
Reference in New Issue
Block a user