fix: repair recon scanning and add macOS deployment
Start scans via the Pager's native /api/pineap/recon/new so history appends instead of rotating. Enforce finite durations (1-86400s, default 30s), remove the unsupported Continuous mode, and refuse the unsafe manual stop that left recon.db locked. Rework scan list and detail reads into single-pass aggregate SQL, shorten lock retries, and serve cached results during short exclusive lock windows. Fall back to immutable read-only access when the firmware leaves a stale lock after native completion. Frontend auto-follows new scans, queues a single in-flight detail refresh, keeps previous tables visible while a scan starts, and shows completion toasts and daemon error details. Add scripts/deploy.sh for macOS/Linux (zip packaging, scp/ssh install, atomic payload replacement, service restart, portal refresh) with README instructions, plus regression coverage for native start, safe stop semantics, aggregate queries, and stale-lock fallback. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
parent
3e1805dab8
commit
2bf39ecb9d
@@ -1047,7 +1047,9 @@ views.recon = (root) => {
|
||||
const state = { scans: [], selected: null, detail: null,
|
||||
apPage: 0, apSearch: '', clientPage: 0, clientSearch: '',
|
||||
apPer: reconPer('ap', 10), clientPer: reconPer('client', 10),
|
||||
apSort: null, clientSort: null, focusAp: null, autoFollow: false, scanActive: false };
|
||||
apSort: null, clientSort: null, focusAp: null, autoFollow: false,
|
||||
scanActive: false, detailLoading: false, detailLoadingId: null,
|
||||
detailQueued: false, detailId: null };
|
||||
const cols = reconLoadCols();
|
||||
|
||||
// ---- title cards ----
|
||||
@@ -1133,7 +1135,7 @@ views.recon = (root) => {
|
||||
const scanLabel = h('label', { class: 'switch recon-scan-toggle' }, scanToggle, h('span', { class: 'track' }), ' Scan');
|
||||
scanBar.appendChild(scanLabel);
|
||||
const durSel = h('select', { class: 'sel', id: 'recon-duration' });
|
||||
[[30, '30 Seconds'], [60, '1 Minute'], [120, '2 Minutes'], [300, '5 Minutes'], [600, '10 Minutes'], [0, 'Continuous']]
|
||||
[[30, '30 Seconds'], [60, '1 Minute'], [120, '2 Minutes'], [300, '5 Minutes'], [600, '10 Minutes']]
|
||||
.forEach(([v, t]) => durSel.appendChild(h('option', { value: String(v), text: t })));
|
||||
durSel.value = localStorage.getItem('pw_scan_duration') || '30';
|
||||
durSel.addEventListener('change', () => localStorage.setItem('pw_scan_duration', durSel.value));
|
||||
@@ -1150,18 +1152,22 @@ views.recon = (root) => {
|
||||
.then(() => {
|
||||
if (on) {
|
||||
state.scanActive = true;
|
||||
state.selected = null;
|
||||
state.autoFollow = true;
|
||||
state.apPage = 0;
|
||||
state.clientPage = 0;
|
||||
App.toast('Scan started');
|
||||
} else {
|
||||
state.scanActive = false;
|
||||
state.autoFollow = false;
|
||||
App.toast('Scan stopped');
|
||||
}
|
||||
restartPoll();
|
||||
load();
|
||||
})
|
||||
.catch(() => { scanToggle.checked = !on; App.toast('Recon control failed', 'error'); })
|
||||
.catch((err) => {
|
||||
scanToggle.checked = !on;
|
||||
App.toast((err && err.message) || 'Recon control failed', 'error');
|
||||
})
|
||||
.finally(() => { pendingScan = false; scanToggle.disabled = false; });
|
||||
});
|
||||
|
||||
@@ -1452,19 +1458,39 @@ views.recon = (root) => {
|
||||
|
||||
function loadDetail() {
|
||||
if (state.selected == null) return;
|
||||
PagerAPI.get('/api/recon/scans/' + state.selected).then((r) => {
|
||||
const scanId = state.selected;
|
||||
if (state.detailLoading) {
|
||||
if (state.detailLoadingId !== scanId) state.detailQueued = true;
|
||||
return;
|
||||
}
|
||||
if (!state.scanActive && state.detailId === scanId) return;
|
||||
state.detailLoading = true;
|
||||
state.detailLoadingId = scanId;
|
||||
PagerAPI.get('/api/recon/scans/' + scanId).then((r) => {
|
||||
if (state.selected !== scanId) return;
|
||||
state.detail = r.data;
|
||||
state.detailId = scanId;
|
||||
drawCharts(r.data);
|
||||
renderTables();
|
||||
hsCount.textContent = (r.data.handshakes || []).length;
|
||||
}).catch(() => {});
|
||||
}).catch(() => {}).finally(() => {
|
||||
state.detailLoading = false;
|
||||
state.detailLoadingId = null;
|
||||
if (state.detailQueued) {
|
||||
state.detailQueued = false;
|
||||
loadDetail();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function load() {
|
||||
PagerAPI.get('/api/recon/scans').then((r) => {
|
||||
state.scans = r.data.scans || [];
|
||||
const keep = state.selected && state.scans.some((s) => s.id === state.selected)
|
||||
? state.selected : (state.scans[0] ? state.scans[0].id : null);
|
||||
const newest = state.scans[0] ? state.scans[0].id : null;
|
||||
const keep = state.autoFollow
|
||||
? newest
|
||||
: (state.selected && state.scans.some((s) => s.id === state.selected)
|
||||
? state.selected : newest);
|
||||
sel.innerHTML = '';
|
||||
state.scans.forEach((s) => {
|
||||
const opt = document.createElement('option');
|
||||
@@ -1482,17 +1508,23 @@ views.recon = (root) => {
|
||||
state.selected = keep;
|
||||
if (keep != null) loadDetail();
|
||||
}).catch(() => {});
|
||||
PagerAPI.get('/api/pineap/get_config').then((r) => {
|
||||
hsAuto.querySelector('input').checked = !!((r.data || {}).loghandshake);
|
||||
}).catch(() => {});
|
||||
PagerAPI.get('/api/recon/status').then((r) => {
|
||||
const scanning = !!r.data.scanning;
|
||||
const wasScanning = state.scanActive;
|
||||
const completed = wasScanning && !scanning;
|
||||
state.scanActive = scanning;
|
||||
if (!pendingScan) scanToggle.checked = scanning;
|
||||
restartPoll();
|
||||
if (wasScanning !== scanning) restartPoll();
|
||||
if (completed) {
|
||||
state.autoFollow = false;
|
||||
App.toast('Scan complete');
|
||||
}
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
PagerAPI.get('/api/pineap/get_config').then((r) => {
|
||||
hsAuto.querySelector('input').checked = !!((r.data || {}).loghandshake);
|
||||
}).catch(() => {});
|
||||
load();
|
||||
let pollIv = null;
|
||||
const restartPoll = () => {
|
||||
|
||||
Reference in New Issue
Block a user