88#include < AsyncWebSocket.h>
99#include < RTClib.h>
1010#include < esp_timer.h>
11+ #include < deque>
1112
1213extern " C"
1314{
@@ -62,7 +63,7 @@ extern void randomizeMacAddress();
6263
6364// WebSocket for terminal
6465AsyncWebSocket ws (" /terminal" );
65- static std::vector <String> terminalBuffer;
66+ static std::deque <String> terminalBuffer;
6667static const size_t TERMINAL_BUFFER_SIZE = 500 ;
6768static bool terminalClientsConnected = false ;
6869
@@ -113,7 +114,7 @@ void broadcastToTerminal(const String &message) {
113114
114115 terminalBuffer.push_back (timestamped);
115116 if (terminalBuffer.size () > TERMINAL_BUFFER_SIZE) {
116- terminalBuffer.erase (terminalBuffer. begin () );
117+ terminalBuffer.pop_front ( );
117118 }
118119}
119120
@@ -960,7 +961,7 @@ static const char INDEX_HTML[] PROGMEM = R"HTML(
960961 let lastScanningState = false;
961962 let lastResultsText = '';
962963 let meshEnabled = true;
963- let lastTriangulationStartTime = 0; // Track when triangulation scan was started
964+ let lastScanStartTime = 0;
964965
965966 function switchTab(tabName) {
966967 document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
@@ -1710,9 +1711,7 @@ static const char INDEX_HTML[] PROGMEM = R"HTML(
17101711 }
17111712 }
17121713 } else {
1713- // Don't reset UI if we just started triangulation (give backend time to update status)
1714- const timeSinceTriangulationStart = Date.now() - lastTriangulationStartTime;
1715- const isWithinGracePeriod = timeSinceTriangulationStart < 5000; // 5 second grace period
1714+ const isWithinGracePeriod = (Date.now() - lastScanStartTime) < 3000;
17161715
17171716 if (!isWithinGracePeriod) {
17181717 document.getElementById('scanStatus').innerText = 'Idle';
@@ -3028,9 +3027,10 @@ static const char INDEX_HTML[] PROGMEM = R"HTML(
30283027 if (document.activeElement && (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA' || document.activeElement.tagName === 'SELECT' || document.activeElement.isContentEditable || window.getSelection().toString().length > 0)) return;
30293028 tickRunning = true;
30303029 try {
3031- const [diagResponse, droneResponse] = await Promise.all([
3030+ const [diagResponse, droneResponse, resultsResponse ] = await Promise.all([
30323031 fetch('/diag'),
3033- fetch('/drone/status').catch(() => null)
3032+ fetch('/drone/status').catch(() => null),
3033+ fetch('/results').catch(() => null)
30343034 ]);
30353035 const diagText = await diagResponse.text();
30363036 const isScanning = diagText.includes('Scanning: yes');
@@ -3095,67 +3095,65 @@ static const char INDEX_HTML[] PROGMEM = R"HTML(
30953095 }
30963096 const resultsElement = document.getElementById('r');
30973097 if (resultsElement && !resultsElement.contains(document.activeElement)) {
3098- if (isScanning) {
3099- const rr = await fetch('/results');
3100- const resultsText = await rr.text();
3098+ if ((isScanning || (lastScanningState && !isScanning)) && resultsResponse) {
3099+ const resultsText = await resultsResponse.text();
31013100 if (resultsText !== lastResultsText) {
31023101 lastResultsText = resultsText;
3103- requestAnimationFrame(() => {
3104- const expandedCards = new Set();
3105- const expandedDetails = new Map();
3106- const contents = resultsElement.querySelectorAll('[id$="Content"]');
3107- for (const content of contents) {
3108- if (content.style.display !== 'none') {
3109- expandedCards.add(content.id);
3110- }
3111- }
3112- const openDetails = resultsElement.querySelectorAll('details[open]');
3113- for (const details of openDetails) {
3114- const summary = details.querySelector('summary');
3115- if (summary && summary.textContent) {
3116- expandedDetails.set(summary.textContent.trim(), true);
3102+ if (isScanning) {
3103+ requestAnimationFrame(() => {
3104+ const expandedCards = new Set();
3105+ const expandedDetails = new Map();
3106+ const contents = resultsElement.querySelectorAll('[id$="Content"]');
3107+ for (const content of contents) {
3108+ if (content.style.display !== 'none') {
3109+ expandedCards.add(content.id);
3110+ }
31173111 }
3118- }
3119- resultsElement.innerHTML = parseAndStyleResults(resultsText);
3120- for (const contentId of expandedCards) {
3121- const content = document.getElementById(contentId);
3122- if (content) {
3123- const iconId = contentId.replace('Content', 'Icon');
3124- const icon = document.getElementById(iconId);
3125- content.style.display = 'block';
3126- if (icon) {
3127- icon.style.transform = 'rotate(0deg)';
3128- icon.textContent = '▼';
3112+ const openDetails = resultsElement.querySelectorAll('details[open]');
3113+ for (const details of openDetails) {
3114+ const summary = details.querySelector('summary');
3115+ if (summary && summary.textContent) {
3116+ expandedDetails.set(summary.textContent.trim(), true);
31293117 }
31303118 }
3131- }
3132- const allDetails = resultsElement.querySelectorAll('details');
3133- for ( const details of allDetails) {
3134- const summary = details.querySelector('summary');
3135- if (summary) {
3136- const summaryText = summary.textContent.trim( );
3137- if (expandedDetails.has(summaryText)) {
3138- details.open = true;
3139- const spans = summary.querySelectorAll('span') ;
3140- const arrow = spans[spans.length - 1] ;
3141- if (arrow) arrow.style.transform = 'rotate(90deg)';
3119+ resultsElement.innerHTML = parseAndStyleResults(resultsText);
3120+ for (const contentId of expandedCards) {
3121+ const content = document.getElementById(contentId);
3122+ if (content) {
3123+ const iconId = contentId.replace('Content', 'Icon');
3124+ const icon = document.getElementById(iconId );
3125+ content.style.display = 'block';
3126+ if (icon) {
3127+ icon.style.transform = 'rotate(0deg)' ;
3128+ icon.textContent = '▼' ;
3129+ }
31423130 }
31433131 }
3144- details.addEventListener('toggle', () => {
3145- const spans = details.querySelectorAll('summary span');
3146- const arrow = spans[spans.length - 1];
3147- if (arrow) {
3148- arrow.style.transform = details.open ? 'rotate(90deg)' : 'rotate(0deg)';
3132+ const allDetails = resultsElement.querySelectorAll('details');
3133+ for (const details of allDetails) {
3134+ const summary = details.querySelector('summary');
3135+ if (summary) {
3136+ const summaryText = summary.textContent.trim();
3137+ if (expandedDetails.has(summaryText)) {
3138+ details.open = true;
3139+ const spans = summary.querySelectorAll('span');
3140+ const arrow = spans[spans.length - 1];
3141+ if (arrow) arrow.style.transform = 'rotate(90deg)';
3142+ }
31493143 }
3150- });
3151- }
3152- });
3144+ details.addEventListener('toggle', () => {
3145+ const spans = details.querySelectorAll('summary span');
3146+ const arrow = spans[spans.length - 1];
3147+ if (arrow) {
3148+ arrow.style.transform = details.open ? 'rotate(90deg)' : 'rotate(0deg)';
3149+ }
3150+ });
3151+ }
3152+ });
3153+ } else {
3154+ resultsElement.innerHTML = parseAndStyleResults(resultsText);
3155+ }
31533156 }
3154- } else if (lastScanningState && !isScanning) {
3155- const rr = await fetch('/results');
3156- const resultsText = await rr.text();
3157- lastResultsText = resultsText;
3158- resultsElement.innerHTML = parseAndStyleResults(resultsText);
31593157 }
31603158 }
31613159 lastScanningState = isScanning;
@@ -3262,10 +3260,7 @@ static const char INDEX_HTML[] PROGMEM = R"HTML(
32623260 // Check if triangulation mode is selected
32633261 const isTriangulation = fd.has('triangulate') && fd.get('triangulate') === '1';
32643262
3265- // Track when triangulation was started
3266- if (isTriangulation) {
3267- lastTriangulationStartTime = now;
3268- }
3263+ lastScanStartTime = now;
32693264
32703265 // Immediately update UI to show scanning state for ALL scan types
32713266 const scanStatusEl = document.getElementById('scanStatus');
@@ -3294,7 +3289,13 @@ static const char INDEX_HTML[] PROGMEM = R"HTML(
32943289 };
32953290 }
32963291
3297- console.log('[SCAN] Form submitted at', new Date().toISOString());
3292+ const resultsElScan = document.getElementById('r');
3293+ if (resultsElScan && !resultsElScan.contains(document.activeElement)) {
3294+ lastResultsText = '';
3295+ const modeVal = parseInt(document.querySelector('#s select[name="mode"]')?.value ?? '2');
3296+ const modeLabel = ['WiFi', 'BLE', 'WiFi+BLE'][modeVal] ?? 'WiFi+BLE';
3297+ resultsElScan.innerHTML = parseAndStyleResults('Target scan starting...\nMode: ' + modeLabel + '\n');
3298+ }
32983299
32993300 fetch('/scan', {
33003301 method: 'POST',
@@ -3405,6 +3406,7 @@ static const char INDEX_HTML[] PROGMEM = R"HTML(
34053406
34063407 state.inProgress = true;
34073408 state.lastSubmit = now;
3409+ lastScanStartTime = now;
34083410
34093411 const scanStatusEl = document.getElementById('scanStatus');
34103412 if (scanStatusEl) {
@@ -3446,6 +3448,12 @@ static const char INDEX_HTML[] PROGMEM = R"HTML(
34463448 }, 500);
34473449 };
34483450
3451+ const resultsElSniffer = document.getElementById('r');
3452+ if (resultsElSniffer && !resultsElSniffer.contains(document.activeElement)) {
3453+ lastResultsText = '';
3454+ resultsElSniffer.innerHTML = parseAndStyleResults('Scan starting...\n');
3455+ }
3456+
34493457 if (detectionMethod === 'baseline') {
34503458 const rssiThreshold = document.getElementById('baselineRssiThreshold').value;
34513459 const duration = document.getElementById('baselineDuration').value;
@@ -3720,6 +3728,7 @@ void startWebServer()
37203728 req->send (200 , " text/plain" , forever ? (" Scan starting (forever) - " + modeStr) : (" Scan starting for " + String (secs) + " s - " + modeStr));
37213729
37223730 if (!workerTaskHandle) {
3731+ scanning = true ;
37233732 xTaskCreatePinnedToCore (listScanTask, " scan" , 8192 , (void *)(intptr_t )(forever ? 0 : secs), 1 , &workerTaskHandle, 1 );
37243733 }
37253734 });
@@ -4222,9 +4231,10 @@ server->on("/baseline/config", HTTP_GET, [](AsyncWebServerRequest *req)
42224231 req->send (200 , " text/plain" , forever ? " Deauth detection starting (forever)" : (" Deauth detection starting for " + String (secs) + " s" ));
42234232
42244233 if (!blueTeamTaskHandle) {
4234+ scanning = true ;
42254235 xTaskCreatePinnedToCore (blueTeamTask, " blueteam" , 12288 , (void *)(intptr_t )(forever ? 0 : secs), 1 , &blueTeamTaskHandle, 1 );
42264236 }
4227-
4237+
42284238 } else if (detection == " baseline" ) {
42294239 currentScanMode = SCAN_BOTH;
42304240 if (secs < 0 ) secs = 0 ;
@@ -4236,8 +4246,9 @@ server->on("/baseline/config", HTTP_GET, [](AsyncWebServerRequest *req)
42364246 (" Baseline detection starting for " + String (secs) + " s" ));
42374247
42384248 if (!workerTaskHandle) {
4239- xTaskCreatePinnedToCore (baselineDetectionTask, " baseline" , 12288 ,
4240- (void *)(intptr_t )(forever ? 0 : secs),
4249+ scanning = true ;
4250+ xTaskCreatePinnedToCore (baselineDetectionTask, " baseline" , 12288 ,
4251+ (void *)(intptr_t )(forever ? 0 : secs),
42414252 1 , &workerTaskHandle, 1 );
42424253 }
42434254
@@ -4264,6 +4275,7 @@ server->on("/baseline/config", HTTP_GET, [](AsyncWebServerRequest *req)
42644275 (" Randomization detection starting for " + String (secs) + " s - " + modeStr));
42654276
42664277 if (!workerTaskHandle) {
4278+ scanning = true ;
42674279 xTaskCreatePinnedToCore (randomizationDetectionTask, " randdetect" , 8192 ,
42684280 (void *)(intptr_t )(forever ? 0 : secs),
42694281 1 , &workerTaskHandle, 1 );
@@ -4292,8 +4304,9 @@ server->on("/baseline/config", HTTP_GET, [](AsyncWebServerRequest *req)
42924304 (" Device scan starting for " + String (secs) + " s - " + modeStr));
42934305
42944306 if (!workerTaskHandle) {
4295- xTaskCreatePinnedToCore (snifferScanTask, " sniffer" , 12288 ,
4296- (void *)(intptr_t )(forever ? 0 : secs),
4307+ scanning = true ;
4308+ xTaskCreatePinnedToCore (snifferScanTask, " sniffer" , 12288 ,
4309+ (void *)(intptr_t )(forever ? 0 : secs),
42974310 1 , &workerTaskHandle, 1 );
42984311 }
42994312
@@ -4308,6 +4321,7 @@ server->on("/baseline/config", HTTP_GET, [](AsyncWebServerRequest *req)
43084321 (" Drone detection starting for " + String (secs) + " s" ));
43094322
43104323 if (!workerTaskHandle) {
4324+ scanning = true ;
43114325 xTaskCreatePinnedToCore (droneDetectorTask, " drone" , 12288 ,
43124326 (void *)(intptr_t )(forever ? 0 : secs),
43134327 1 , &workerTaskHandle, 1 );
0 commit comments