mirror of
https://github.com/smittix/intercept.git
synced 2026-07-08 17:48:13 -07:00
Improve mode stop responsiveness and timeout handling
This commit is contained in:
@@ -944,21 +944,36 @@ const BluetoothMode = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function stopScan() {
|
async function stopScan() {
|
||||||
const isAgentMode = typeof currentAgent !== 'undefined' && currentAgent !== 'local';
|
const isAgentMode = typeof currentAgent !== 'undefined' && currentAgent !== 'local';
|
||||||
|
const timeoutMs = isAgentMode ? 8000 : 2200;
|
||||||
try {
|
const controller = (typeof AbortController !== 'undefined') ? new AbortController() : null;
|
||||||
if (isAgentMode) {
|
const timeoutId = controller ? setTimeout(() => controller.abort(), timeoutMs) : null;
|
||||||
await fetch(`/controller/agents/${currentAgent}/bluetooth/stop`, { method: 'POST' });
|
|
||||||
} else {
|
// Optimistic UI teardown keeps mode changes responsive.
|
||||||
await fetch('/api/bluetooth/scan/stop', { method: 'POST' });
|
setScanning(false);
|
||||||
}
|
stopEventStream();
|
||||||
setScanning(false);
|
|
||||||
stopEventStream();
|
try {
|
||||||
} catch (err) {
|
if (isAgentMode) {
|
||||||
console.error('Failed to stop scan:', err);
|
await fetch(`/controller/agents/${currentAgent}/bluetooth/stop`, {
|
||||||
}
|
method: 'POST',
|
||||||
}
|
...(controller ? { signal: controller.signal } : {}),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await fetch('/api/bluetooth/scan/stop', {
|
||||||
|
method: 'POST',
|
||||||
|
...(controller ? { signal: controller.signal } : {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to stop scan:', err);
|
||||||
|
} finally {
|
||||||
|
if (timeoutId) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function setScanning(scanning) {
|
function setScanning(scanning) {
|
||||||
isScanning = scanning;
|
isScanning = scanning;
|
||||||
|
|||||||
+37
-22
@@ -572,8 +572,8 @@ const WiFiMode = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function stopScan() {
|
async function stopScan() {
|
||||||
console.log('[WiFiMode] Stopping scan...');
|
console.log('[WiFiMode] Stopping scan...');
|
||||||
|
|
||||||
// Stop polling
|
// Stop polling
|
||||||
if (pollTimer) {
|
if (pollTimer) {
|
||||||
@@ -585,26 +585,41 @@ const WiFiMode = (function() {
|
|||||||
stopAgentDeepScanPolling();
|
stopAgentDeepScanPolling();
|
||||||
|
|
||||||
// Close event stream
|
// Close event stream
|
||||||
if (eventSource) {
|
if (eventSource) {
|
||||||
eventSource.close();
|
eventSource.close();
|
||||||
eventSource = null;
|
eventSource = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop scan on server (local or agent)
|
// Update UI immediately so mode transitions are responsive even if the
|
||||||
const isAgentMode = typeof currentAgent !== 'undefined' && currentAgent !== 'local';
|
// backend needs extra time to terminate subprocesses.
|
||||||
|
setScanning(false);
|
||||||
try {
|
|
||||||
if (isAgentMode) {
|
// Stop scan on server (local or agent)
|
||||||
await fetch(`/controller/agents/${currentAgent}/wifi/stop`, { method: 'POST' });
|
const isAgentMode = typeof currentAgent !== 'undefined' && currentAgent !== 'local';
|
||||||
} else if (scanMode === 'deep') {
|
const timeoutMs = isAgentMode ? 8000 : 2200;
|
||||||
await fetch(`${CONFIG.apiBase}/scan/stop`, { method: 'POST' });
|
const controller = (typeof AbortController !== 'undefined') ? new AbortController() : null;
|
||||||
}
|
const timeoutId = controller ? setTimeout(() => controller.abort(), timeoutMs) : null;
|
||||||
} catch (error) {
|
|
||||||
console.warn('[WiFiMode] Error stopping scan:', error);
|
try {
|
||||||
}
|
if (isAgentMode) {
|
||||||
|
await fetch(`/controller/agents/${currentAgent}/wifi/stop`, {
|
||||||
setScanning(false);
|
method: 'POST',
|
||||||
}
|
...(controller ? { signal: controller.signal } : {}),
|
||||||
|
});
|
||||||
|
} else if (scanMode === 'deep') {
|
||||||
|
await fetch(`${CONFIG.apiBase}/scan/stop`, {
|
||||||
|
method: 'POST',
|
||||||
|
...(controller ? { signal: controller.signal } : {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[WiFiMode] Error stopping scan:', error);
|
||||||
|
} finally {
|
||||||
|
if (timeoutId) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function setScanning(scanning, mode = null) {
|
function setScanning(scanning, mode = null) {
|
||||||
isScanning = scanning;
|
isScanning = scanning;
|
||||||
|
|||||||
+177
-125
@@ -3602,26 +3602,89 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const LOCAL_STOP_TIMEOUT_MS = 2200;
|
||||||
|
const REMOTE_STOP_TIMEOUT_MS = 8000;
|
||||||
|
|
||||||
|
function postStopRequest(url, timeoutMs = LOCAL_STOP_TIMEOUT_MS) {
|
||||||
|
const controller = (typeof AbortController !== 'undefined') ? new AbortController() : null;
|
||||||
|
const timeoutId = controller ? setTimeout(() => controller.abort(), timeoutMs) : null;
|
||||||
|
const started = performance.now();
|
||||||
|
return fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
...(controller ? { signal: controller.signal } : {}),
|
||||||
|
})
|
||||||
|
.then((response) => response.json().catch(() => ({ status: response.ok ? 'ok' : 'error' })))
|
||||||
|
.catch((err) => {
|
||||||
|
if (err && err.name === 'AbortError') {
|
||||||
|
console.warn(`[Stop] ${url} timed out after ${timeoutMs}ms`);
|
||||||
|
return { status: 'timeout', timed_out: true };
|
||||||
|
}
|
||||||
|
console.warn(`[Stop] ${url} failed: ${err?.message || err}`);
|
||||||
|
return { status: 'error', message: err?.message || String(err) };
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
if (timeoutId) clearTimeout(timeoutId);
|
||||||
|
const elapsedMs = Math.round(performance.now() - started);
|
||||||
|
console.debug(`[Stop] ${url} finished in ${elapsedMs}ms`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function awaitStopAction(name, action, timeoutMs = LOCAL_STOP_TIMEOUT_MS) {
|
||||||
|
const started = performance.now();
|
||||||
|
try {
|
||||||
|
const result = action();
|
||||||
|
const promise = (result && typeof result.then === 'function')
|
||||||
|
? result
|
||||||
|
: Promise.resolve(result);
|
||||||
|
await Promise.race([
|
||||||
|
promise,
|
||||||
|
new Promise((resolve) => setTimeout(resolve, timeoutMs)),
|
||||||
|
]);
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(`[ModeSwitch] stop ${name} failed: ${err?.message || err}`);
|
||||||
|
} finally {
|
||||||
|
const elapsedMs = Math.round(performance.now() - started);
|
||||||
|
console.debug(`[ModeSwitch] stop ${name} finished in ${elapsedMs}ms`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Mode switching
|
// Mode switching
|
||||||
function switchMode(mode, options = {}) {
|
async function switchMode(mode, options = {}) {
|
||||||
const { updateUrl = true } = options;
|
const { updateUrl = true } = options;
|
||||||
if (mode === 'listening') mode = 'waterfall';
|
if (mode === 'listening') mode = 'waterfall';
|
||||||
if (!validModes.has(mode)) mode = 'pager';
|
if (!validModes.has(mode)) mode = 'pager';
|
||||||
// Only stop local scans if in local mode (not agent mode)
|
// Only stop local scans if in local mode (not agent mode)
|
||||||
const isAgentMode = typeof currentAgent !== 'undefined' && currentAgent !== 'local';
|
const isAgentMode = typeof currentAgent !== 'undefined' && currentAgent !== 'local';
|
||||||
if (!isAgentMode) {
|
if (!isAgentMode) {
|
||||||
if (isRunning) stopDecoding();
|
if (isRunning) {
|
||||||
if (isSensorRunning) stopSensorDecoding();
|
await awaitStopAction('pager', () => stopDecoding(), LOCAL_STOP_TIMEOUT_MS);
|
||||||
if (isWifiRunning) stopWifiScan();
|
}
|
||||||
|
if (isSensorRunning) {
|
||||||
|
await awaitStopAction('sensor', () => stopSensorDecoding(), LOCAL_STOP_TIMEOUT_MS);
|
||||||
|
}
|
||||||
|
const wifiScanActive = (
|
||||||
|
typeof WiFiMode !== 'undefined'
|
||||||
|
&& typeof WiFiMode.isScanning === 'function'
|
||||||
|
&& WiFiMode.isScanning()
|
||||||
|
) || isWifiRunning;
|
||||||
|
if (wifiScanActive) {
|
||||||
|
await awaitStopAction('wifi', () => stopWifiScan(), LOCAL_STOP_TIMEOUT_MS);
|
||||||
|
}
|
||||||
const btScanActive = (typeof BluetoothMode !== 'undefined' &&
|
const btScanActive = (typeof BluetoothMode !== 'undefined' &&
|
||||||
typeof BluetoothMode.isScanning === 'function' &&
|
typeof BluetoothMode.isScanning === 'function' &&
|
||||||
BluetoothMode.isScanning()) || isBtRunning;
|
BluetoothMode.isScanning()) || isBtRunning;
|
||||||
const isBtModeTransition =
|
const isBtModeTransition =
|
||||||
(currentMode === 'bluetooth' && mode === 'bt_locate') ||
|
(currentMode === 'bluetooth' && mode === 'bt_locate') ||
|
||||||
(currentMode === 'bt_locate' && mode === 'bluetooth');
|
(currentMode === 'bt_locate' && mode === 'bluetooth');
|
||||||
if (btScanActive && !isBtModeTransition && typeof stopBtScan === 'function') stopBtScan();
|
if (btScanActive && !isBtModeTransition && typeof stopBtScan === 'function') {
|
||||||
if (isAprsRunning) stopAprs();
|
await awaitStopAction('bluetooth', () => stopBtScan(), LOCAL_STOP_TIMEOUT_MS);
|
||||||
if (isTscmRunning) stopTscmSweep();
|
}
|
||||||
|
if (isAprsRunning) {
|
||||||
|
await awaitStopAction('aprs', () => stopAprs(), LOCAL_STOP_TIMEOUT_MS);
|
||||||
|
}
|
||||||
|
if (isTscmRunning) {
|
||||||
|
await awaitStopAction('tscm', () => stopTscmSweep(), LOCAL_STOP_TIMEOUT_MS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up SubGHz SSE connection when leaving the mode
|
// Clean up SubGHz SSE connection when leaving the mode
|
||||||
@@ -4343,35 +4406,31 @@
|
|||||||
|
|
||||||
// Stop sensor decoding
|
// Stop sensor decoding
|
||||||
function stopSensorDecoding() {
|
function stopSensorDecoding() {
|
||||||
// Check if using remote agent
|
const isAgentMode = typeof currentAgent !== 'undefined' && currentAgent !== 'local';
|
||||||
if (typeof currentAgent !== 'undefined' && currentAgent !== 'local') {
|
const endpoint = isAgentMode
|
||||||
fetch(`/controller/agents/${currentAgent}/sensor/stop`, { method: 'POST' })
|
? `/controller/agents/${currentAgent}/sensor/stop`
|
||||||
.then(r => r.json())
|
: '/stop_sensor';
|
||||||
.then(data => {
|
const timeoutMs = isAgentMode ? REMOTE_STOP_TIMEOUT_MS : LOCAL_STOP_TIMEOUT_MS;
|
||||||
setSensorRunning(false);
|
|
||||||
if (eventSource) {
|
setSensorRunning(false);
|
||||||
eventSource.close();
|
if (eventSource) {
|
||||||
eventSource = null;
|
eventSource.close();
|
||||||
}
|
eventSource = null;
|
||||||
if (agentPollInterval) {
|
}
|
||||||
clearInterval(agentPollInterval);
|
if (agentPollInterval) {
|
||||||
agentPollInterval = null;
|
clearInterval(agentPollInterval);
|
||||||
}
|
agentPollInterval = null;
|
||||||
showInfo('Sensor stopped on remote agent');
|
}
|
||||||
});
|
if (!isAgentMode) {
|
||||||
return;
|
releaseDevice('sensor');
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch('/stop_sensor', { method: 'POST' })
|
return postStopRequest(endpoint, timeoutMs).then((data) => {
|
||||||
.then(r => r.json())
|
if (isAgentMode && data && data.status !== 'error' && data.status !== 'timeout') {
|
||||||
.then(data => {
|
showInfo('Sensor stopped on remote agent');
|
||||||
releaseDevice('sensor');
|
}
|
||||||
setSensorRunning(false);
|
return data;
|
||||||
if (eventSource) {
|
});
|
||||||
eventSource.close();
|
|
||||||
eventSource = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Polling interval for agent data
|
// Polling interval for agent data
|
||||||
@@ -4685,25 +4744,23 @@
|
|||||||
const endpoint = isAgentMode
|
const endpoint = isAgentMode
|
||||||
? `/controller/agents/${rtlamrCurrentAgent}/rtlamr/stop`
|
? `/controller/agents/${rtlamrCurrentAgent}/rtlamr/stop`
|
||||||
: '/stop_rtlamr';
|
: '/stop_rtlamr';
|
||||||
|
const timeoutMs = isAgentMode ? REMOTE_STOP_TIMEOUT_MS : LOCAL_STOP_TIMEOUT_MS;
|
||||||
|
|
||||||
fetch(endpoint, { method: 'POST' })
|
rtlamrCurrentAgent = null;
|
||||||
.then(r => r.json())
|
setRtlamrRunning(false);
|
||||||
.then(data => {
|
if (eventSource) {
|
||||||
if (!isAgentMode) {
|
eventSource.close();
|
||||||
releaseDevice('rtlamr');
|
eventSource = null;
|
||||||
}
|
}
|
||||||
rtlamrCurrentAgent = null;
|
if (rtlamrPollTimer) {
|
||||||
setRtlamrRunning(false);
|
clearInterval(rtlamrPollTimer);
|
||||||
if (eventSource) {
|
rtlamrPollTimer = null;
|
||||||
eventSource.close();
|
}
|
||||||
eventSource = null;
|
if (!isAgentMode) {
|
||||||
}
|
releaseDevice('rtlamr');
|
||||||
// Clear polling timer
|
}
|
||||||
if (rtlamrPollTimer) {
|
|
||||||
clearInterval(rtlamrPollTimer);
|
return postStopRequest(endpoint, timeoutMs);
|
||||||
rtlamrPollTimer = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setRtlamrRunning(running) {
|
function setRtlamrRunning(running) {
|
||||||
@@ -5727,24 +5784,22 @@
|
|||||||
const endpoint = isAgentMode
|
const endpoint = isAgentMode
|
||||||
? `/controller/agents/${currentAgent}/pager/stop`
|
? `/controller/agents/${currentAgent}/pager/stop`
|
||||||
: '/stop';
|
: '/stop';
|
||||||
|
const timeoutMs = isAgentMode ? REMOTE_STOP_TIMEOUT_MS : LOCAL_STOP_TIMEOUT_MS;
|
||||||
|
|
||||||
fetch(endpoint, { method: 'POST' })
|
if (!isAgentMode) {
|
||||||
.then(r => r.json())
|
releaseDevice('pager');
|
||||||
.then(data => {
|
}
|
||||||
if (!isAgentMode) {
|
setRunning(false);
|
||||||
releaseDevice('pager');
|
if (eventSource) {
|
||||||
}
|
eventSource.close();
|
||||||
setRunning(false);
|
eventSource = null;
|
||||||
if (eventSource) {
|
}
|
||||||
eventSource.close();
|
if (pagerPollTimer) {
|
||||||
eventSource = null;
|
clearInterval(pagerPollTimer);
|
||||||
}
|
pagerPollTimer = null;
|
||||||
// Clear polling timer if active
|
}
|
||||||
if (pagerPollTimer) {
|
|
||||||
clearInterval(pagerPollTimer);
|
return postStopRequest(endpoint, timeoutMs);
|
||||||
pagerPollTimer = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function killAll() {
|
function killAll() {
|
||||||
@@ -7642,15 +7697,19 @@
|
|||||||
|
|
||||||
// Stop WiFi scan
|
// Stop WiFi scan
|
||||||
function stopWifiScan() {
|
function stopWifiScan() {
|
||||||
fetch('/wifi/scan/stop', { method: 'POST' })
|
setWifiRunning(false);
|
||||||
.then(r => r.json())
|
if (wifiEventSource) {
|
||||||
.then(data => {
|
wifiEventSource.close();
|
||||||
setWifiRunning(false);
|
wifiEventSource = null;
|
||||||
if (wifiEventSource) {
|
}
|
||||||
wifiEventSource.close();
|
|
||||||
wifiEventSource = null;
|
if (typeof WiFiMode !== 'undefined' && typeof WiFiMode.stopScan === 'function') {
|
||||||
}
|
return Promise.resolve(WiFiMode.stopScan()).catch((err) => {
|
||||||
|
console.warn('[WiFi] stop via WiFiMode failed:', err);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return postStopRequest('/wifi/scan/stop', LOCAL_STOP_TIMEOUT_MS);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setWifiRunning(running) {
|
function setWifiRunning(running) {
|
||||||
@@ -8856,10 +8915,14 @@
|
|||||||
|
|
||||||
function stopBtScan() {
|
function stopBtScan() {
|
||||||
const bt = getBluetoothModeApi();
|
const bt = getBluetoothModeApi();
|
||||||
|
let stopPromise = Promise.resolve();
|
||||||
if (bt && typeof bt.stopScan === 'function') {
|
if (bt && typeof bt.stopScan === 'function') {
|
||||||
bt.stopScan();
|
stopPromise = Promise.resolve(bt.stopScan()).catch((err) => {
|
||||||
|
console.warn('[BT] stop failed:', err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
setTimeout(syncBtRunningState, 0);
|
setTimeout(syncBtRunningState, 0);
|
||||||
|
return stopPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setBtRunning(running) {
|
function setBtRunning(running) {
|
||||||
@@ -9175,44 +9238,36 @@
|
|||||||
const endpoint = isAgentMode
|
const endpoint = isAgentMode
|
||||||
? `/controller/agents/${aprsCurrentAgent}/aprs/stop`
|
? `/controller/agents/${aprsCurrentAgent}/aprs/stop`
|
||||||
: '/aprs/stop';
|
: '/aprs/stop';
|
||||||
|
const timeoutMs = isAgentMode ? REMOTE_STOP_TIMEOUT_MS : LOCAL_STOP_TIMEOUT_MS;
|
||||||
|
|
||||||
fetch(endpoint, { method: 'POST' })
|
isAprsRunning = false;
|
||||||
.then(r => r.json())
|
aprsCurrentAgent = null;
|
||||||
.then(data => {
|
document.getElementById('aprsStripStartBtn').style.display = 'inline-block';
|
||||||
isAprsRunning = false;
|
document.getElementById('aprsStripStopBtn').style.display = 'none';
|
||||||
aprsCurrentAgent = null;
|
document.getElementById('aprsMapStatus').textContent = 'STANDBY';
|
||||||
// Update function bar buttons
|
document.getElementById('aprsMapStatus').style.color = '';
|
||||||
document.getElementById('aprsStripStartBtn').style.display = 'inline-block';
|
updateAprsStatus('standby');
|
||||||
document.getElementById('aprsStripStopBtn').style.display = 'none';
|
document.getElementById('aprsStripFreq').textContent = '--';
|
||||||
// Update map status
|
document.getElementById('aprsStripSignal').textContent = '--';
|
||||||
document.getElementById('aprsMapStatus').textContent = 'STANDBY';
|
document.getElementById('aprsStripRegion').disabled = false;
|
||||||
document.getElementById('aprsMapStatus').style.color = '';
|
document.getElementById('aprsStripGain').disabled = false;
|
||||||
// Reset function bar status
|
const customFreqInput = document.getElementById('aprsStripCustomFreq');
|
||||||
updateAprsStatus('standby');
|
if (customFreqInput) customFreqInput.disabled = false;
|
||||||
document.getElementById('aprsStripFreq').textContent = '--';
|
const signalStat = document.getElementById('aprsStripSignalStat');
|
||||||
document.getElementById('aprsStripSignal').textContent = '--';
|
if (signalStat) {
|
||||||
// Re-enable controls
|
signalStat.classList.remove('good', 'warning', 'poor');
|
||||||
document.getElementById('aprsStripRegion').disabled = false;
|
}
|
||||||
document.getElementById('aprsStripGain').disabled = false;
|
stopAprsMeterCheck();
|
||||||
const customFreqInput = document.getElementById('aprsStripCustomFreq');
|
if (aprsEventSource) {
|
||||||
if (customFreqInput) customFreqInput.disabled = false;
|
aprsEventSource.close();
|
||||||
// Remove signal quality class
|
aprsEventSource = null;
|
||||||
const signalStat = document.getElementById('aprsStripSignalStat');
|
}
|
||||||
if (signalStat) {
|
if (aprsPollTimer) {
|
||||||
signalStat.classList.remove('good', 'warning', 'poor');
|
clearInterval(aprsPollTimer);
|
||||||
}
|
aprsPollTimer = null;
|
||||||
// Stop meter check interval
|
}
|
||||||
stopAprsMeterCheck();
|
|
||||||
if (aprsEventSource) {
|
return postStopRequest(endpoint, timeoutMs);
|
||||||
aprsEventSource.close();
|
|
||||||
aprsEventSource = null;
|
|
||||||
}
|
|
||||||
// Clear polling timer
|
|
||||||
if (aprsPollTimer) {
|
|
||||||
clearInterval(aprsPollTimer);
|
|
||||||
aprsPollTimer = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function startAprsStream(isAgentMode = false) {
|
function startAprsStream(isAgentMode = false) {
|
||||||
@@ -10902,16 +10957,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function stopTscmSweep() {
|
async function stopTscmSweep() {
|
||||||
try {
|
const isAgentMode = typeof currentAgent !== 'undefined' && currentAgent !== 'local';
|
||||||
// Route to agent or local based on selection
|
const endpoint = isAgentMode
|
||||||
const isAgentMode = typeof currentAgent !== 'undefined' && currentAgent !== 'local';
|
? `/controller/agents/${currentAgent}/tscm/stop`
|
||||||
const endpoint = isAgentMode
|
: '/tscm/sweep/stop';
|
||||||
? `/controller/agents/${currentAgent}/tscm/stop`
|
const timeoutMs = isAgentMode ? REMOTE_STOP_TIMEOUT_MS : LOCAL_STOP_TIMEOUT_MS;
|
||||||
: '/tscm/sweep/stop';
|
|
||||||
await fetch(endpoint, { method: 'POST' });
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Error stopping sweep:', e);
|
|
||||||
}
|
|
||||||
|
|
||||||
isTscmRunning = false;
|
isTscmRunning = false;
|
||||||
tscmSweepEndTime = new Date();
|
tscmSweepEndTime = new Date();
|
||||||
@@ -10931,6 +10981,8 @@
|
|||||||
// Show report button if we have any data
|
// Show report button if we have any data
|
||||||
const hasData = tscmWifiDevices.length > 0 || tscmBtDevices.length > 0 || tscmRfSignals.length > 0;
|
const hasData = tscmWifiDevices.length > 0 || tscmBtDevices.length > 0 || tscmRfSignals.length > 0;
|
||||||
document.getElementById('tscmReportBtn').style.display = hasData ? 'block' : 'none';
|
document.getElementById('tscmReportBtn').style.display = hasData ? 'block' : 'none';
|
||||||
|
|
||||||
|
return postStopRequest(endpoint, timeoutMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateTscmReport() {
|
function generateTscmReport() {
|
||||||
|
|||||||
+21
-20
@@ -97,7 +97,7 @@ class AgentClient:
|
|||||||
except requests.RequestException as e:
|
except requests.RequestException as e:
|
||||||
raise AgentHTTPError(f"Request failed: {e}")
|
raise AgentHTTPError(f"Request failed: {e}")
|
||||||
|
|
||||||
def _post(self, path: str, data: dict | None = None) -> dict:
|
def _post(self, path: str, data: dict | None = None, timeout: float | None = None) -> dict:
|
||||||
"""
|
"""
|
||||||
Perform POST request to agent.
|
Perform POST request to agent.
|
||||||
|
|
||||||
@@ -112,20 +112,21 @@ class AgentClient:
|
|||||||
AgentHTTPError: On HTTP errors
|
AgentHTTPError: On HTTP errors
|
||||||
AgentConnectionError: If agent is unreachable
|
AgentConnectionError: If agent is unreachable
|
||||||
"""
|
"""
|
||||||
url = f"{self.base_url}{path}"
|
url = f"{self.base_url}{path}"
|
||||||
try:
|
request_timeout = self.timeout if timeout is None else timeout
|
||||||
response = requests.post(
|
try:
|
||||||
url,
|
response = requests.post(
|
||||||
json=data or {},
|
url,
|
||||||
headers=self._headers(),
|
json=data or {},
|
||||||
timeout=self.timeout
|
headers=self._headers(),
|
||||||
)
|
timeout=request_timeout
|
||||||
response.raise_for_status()
|
)
|
||||||
return response.json() if response.content else {}
|
response.raise_for_status()
|
||||||
except requests.ConnectionError as e:
|
return response.json() if response.content else {}
|
||||||
raise AgentConnectionError(f"Cannot connect to agent at {self.base_url}: {e}")
|
except requests.ConnectionError as e:
|
||||||
except requests.Timeout:
|
raise AgentConnectionError(f"Cannot connect to agent at {self.base_url}: {e}")
|
||||||
raise AgentConnectionError(f"Request to agent timed out after {self.timeout}s")
|
except requests.Timeout:
|
||||||
|
raise AgentConnectionError(f"Request to agent timed out after {request_timeout}s")
|
||||||
except requests.HTTPError as e:
|
except requests.HTTPError as e:
|
||||||
# Try to extract error message from response body
|
# Try to extract error message from response body
|
||||||
error_msg = f"Agent returned error: {e.response.status_code}"
|
error_msg = f"Agent returned error: {e.response.status_code}"
|
||||||
@@ -141,9 +142,9 @@ class AgentClient:
|
|||||||
except requests.RequestException as e:
|
except requests.RequestException as e:
|
||||||
raise AgentHTTPError(f"Request failed: {e}")
|
raise AgentHTTPError(f"Request failed: {e}")
|
||||||
|
|
||||||
def post(self, path: str, data: dict | None = None) -> dict:
|
def post(self, path: str, data: dict | None = None, timeout: float | None = None) -> dict:
|
||||||
"""Public POST method for arbitrary endpoints."""
|
"""Public POST method for arbitrary endpoints."""
|
||||||
return self._post(path, data)
|
return self._post(path, data, timeout=timeout)
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
# Capability & Status
|
# Capability & Status
|
||||||
@@ -214,7 +215,7 @@ class AgentClient:
|
|||||||
"""
|
"""
|
||||||
return self._post(f'/{mode}/start', params or {})
|
return self._post(f'/{mode}/start', params or {})
|
||||||
|
|
||||||
def stop_mode(self, mode: str) -> dict:
|
def stop_mode(self, mode: str, timeout: float = 8.0) -> dict:
|
||||||
"""
|
"""
|
||||||
Stop a running mode on the agent.
|
Stop a running mode on the agent.
|
||||||
|
|
||||||
@@ -224,7 +225,7 @@ class AgentClient:
|
|||||||
Returns:
|
Returns:
|
||||||
Stop result with 'status' field
|
Stop result with 'status' field
|
||||||
"""
|
"""
|
||||||
return self._post(f'/{mode}/stop')
|
return self._post(f'/{mode}/stop', timeout=timeout)
|
||||||
|
|
||||||
def get_mode_status(self, mode: str) -> dict:
|
def get_mode_status(self, mode: str) -> dict:
|
||||||
"""
|
"""
|
||||||
|
|||||||
+106
-56
@@ -726,46 +726,76 @@ class UnifiedWiFiScanner:
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def stop_deep_scan(self) -> bool:
|
def stop_deep_scan(self) -> bool:
|
||||||
"""
|
"""
|
||||||
Stop the deep scan.
|
Stop the deep scan.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
True if scan was stopped.
|
True if scan was stopped.
|
||||||
"""
|
"""
|
||||||
with self._lock:
|
cleanup_process: Optional[subprocess.Popen] = None
|
||||||
if not self._status.is_scanning:
|
cleanup_thread: Optional[threading.Thread] = None
|
||||||
return True
|
cleanup_detector = None
|
||||||
|
|
||||||
# Stop deauth detector first
|
with self._lock:
|
||||||
self._stop_deauth_detector()
|
if not self._status.is_scanning:
|
||||||
|
return True
|
||||||
self._deep_scan_stop_event.set()
|
|
||||||
|
self._deep_scan_stop_event.set()
|
||||||
if self._deep_scan_process:
|
cleanup_process = self._deep_scan_process
|
||||||
try:
|
cleanup_thread = self._deep_scan_thread
|
||||||
self._deep_scan_process.terminate()
|
cleanup_detector = self._deauth_detector
|
||||||
self._deep_scan_process.wait(timeout=5)
|
self._deauth_detector = None
|
||||||
except Exception as e:
|
self._deep_scan_process = None
|
||||||
logger.warning(f"Error terminating airodump-ng: {e}")
|
self._deep_scan_thread = None
|
||||||
try:
|
|
||||||
self._deep_scan_process.kill()
|
self._status.is_scanning = False
|
||||||
except Exception:
|
self._status.error = None
|
||||||
pass
|
|
||||||
self._deep_scan_process = None
|
self._queue_event({
|
||||||
|
'type': 'scan_stopped',
|
||||||
if self._deep_scan_thread:
|
'mode': SCAN_MODE_DEEP,
|
||||||
self._deep_scan_thread.join(timeout=5)
|
})
|
||||||
self._deep_scan_thread = None
|
|
||||||
|
cleanup_start = time.perf_counter()
|
||||||
self._status.is_scanning = False
|
|
||||||
|
def _finalize_stop(
|
||||||
self._queue_event({
|
process: Optional[subprocess.Popen],
|
||||||
'type': 'scan_stopped',
|
scan_thread: Optional[threading.Thread],
|
||||||
'mode': SCAN_MODE_DEEP,
|
detector,
|
||||||
})
|
) -> None:
|
||||||
|
if detector:
|
||||||
return True
|
try:
|
||||||
|
detector.stop()
|
||||||
|
logger.info("Deauth detector stopped")
|
||||||
|
self._queue_event({'type': 'deauth_detector_stopped'})
|
||||||
|
except Exception as exc:
|
||||||
|
logger.error(f"Error stopping deauth detector: {exc}")
|
||||||
|
|
||||||
|
if process and process.poll() is None:
|
||||||
|
try:
|
||||||
|
process.terminate()
|
||||||
|
process.wait(timeout=1.5)
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
process.kill()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if scan_thread and scan_thread.is_alive():
|
||||||
|
scan_thread.join(timeout=1.5)
|
||||||
|
|
||||||
|
elapsed_ms = (time.perf_counter() - cleanup_start) * 1000.0
|
||||||
|
logger.info(f"Deep scan stop finalized in {elapsed_ms:.1f}ms")
|
||||||
|
|
||||||
|
threading.Thread(
|
||||||
|
target=_finalize_stop,
|
||||||
|
args=(cleanup_process, cleanup_thread, cleanup_detector),
|
||||||
|
daemon=True,
|
||||||
|
name='wifi-deep-stop',
|
||||||
|
).start()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def _run_deep_scan(
|
def _run_deep_scan(
|
||||||
self,
|
self,
|
||||||
@@ -799,14 +829,32 @@ class UnifiedWiFiScanner:
|
|||||||
|
|
||||||
logger.info(f"Starting airodump-ng: {' '.join(cmd)}")
|
logger.info(f"Starting airodump-ng: {' '.join(cmd)}")
|
||||||
|
|
||||||
try:
|
process: Optional[subprocess.Popen] = None
|
||||||
self._deep_scan_process = subprocess.Popen(
|
try:
|
||||||
cmd,
|
process = subprocess.Popen(
|
||||||
stdout=subprocess.DEVNULL,
|
cmd,
|
||||||
stderr=subprocess.DEVNULL,
|
stdout=subprocess.DEVNULL,
|
||||||
)
|
stderr=subprocess.DEVNULL,
|
||||||
|
)
|
||||||
csv_file = f"{output_prefix}-01.csv"
|
should_track_process = False
|
||||||
|
with self._lock:
|
||||||
|
# Only expose the process handle if this run has not been
|
||||||
|
# replaced by a newer deep scan session.
|
||||||
|
if self._status.is_scanning and not self._deep_scan_stop_event.is_set():
|
||||||
|
should_track_process = True
|
||||||
|
self._deep_scan_process = process
|
||||||
|
if not should_track_process:
|
||||||
|
try:
|
||||||
|
process.terminate()
|
||||||
|
process.wait(timeout=1.0)
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
process.kill()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
csv_file = f"{output_prefix}-01.csv"
|
||||||
|
|
||||||
# Poll CSV file for updates
|
# Poll CSV file for updates
|
||||||
while not self._deep_scan_stop_event.is_set():
|
while not self._deep_scan_stop_event.is_set():
|
||||||
@@ -830,14 +878,16 @@ class UnifiedWiFiScanner:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"Error parsing airodump CSV: {e}")
|
logger.debug(f"Error parsing airodump CSV: {e}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(f"Deep scan error: {e}")
|
logger.exception(f"Deep scan error: {e}")
|
||||||
self._queue_event({
|
self._queue_event({
|
||||||
'type': 'scan_error',
|
'type': 'scan_error',
|
||||||
'error': str(e),
|
'error': str(e),
|
||||||
})
|
})
|
||||||
finally:
|
finally:
|
||||||
self._deep_scan_process = None
|
with self._lock:
|
||||||
|
if process is not None and self._deep_scan_process is process:
|
||||||
|
self._deep_scan_process = None
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
# Observation Processing
|
# Observation Processing
|
||||||
|
|||||||
Reference in New Issue
Block a user