Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ work/
.pytest_cache/
.venv/
.claude/settings.local.json
EOF
.playwright-browsers/
auto_scan_config.json
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,23 @@ cp /path/to/runtime.sqlite3 NmapUIMenuBar.app/Contents/Resources/data/runtime.sq

The current repository does not include the old `build.sh` installer flow referenced in earlier notes.

## Build Environment Variables

The macOS wrapper build accepts the following environment variables:

- `NMAPUI_SWIFT_TARGET` - Override the Swift compilation target (defaults are picked from `uname -m`: `arm64-apple-macosx13.0` or `x86_64-apple-macosx13.0`)
- `NMAPUI_APPLICATIONS_DIR` - Override the install destination; otherwise the bundle goes to `/Applications` when writable, or `~/Applications`
- `NMAPUI_MIGRATE_DB=1 ./build.sh` - Migrate an existing runtime database during install
- `NMAPUI_MIGRATE_DB_FROM=<path>` - Explicit source database for the migration

## Runtime Maintenance

Backfill scan artifacts and customer history into the SQLite runtime store:

```bash
python3 scripts/backfill_runtime_store.py
```

## Usage

### Start the app
Expand Down
86 changes: 68 additions & 18 deletions static/js/auto_scan_ui.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,88 @@
let autoScanHttpBusy = false;

function applyAutoScanConfigState(config = {}) {
if (!config || typeof config !== 'object') return;
// Flask payloads use start_time/end_time; the Node dev runtime uses startTime.
window.currentAutoScanConfig = config || {};
const autoScanToggle = document.getElementById('auto-scan-toggle');
const startTimeInput = document.getElementById('auto-start-time');
const endTimeInput = document.getElementById('auto-end-time');
if (autoScanToggle) autoScanToggle.checked = !!config.enabled;
if (startTimeInput) startTimeInput.value = config.startTime || config.start_time || '01:00';
if (endTimeInput) endTimeInput.value = config.endTime || config.end_time || '06:00';
}

// The packaged Flask runtime exposes /api/auto_scan/*; prefer it and fall back
// to socket events for the Node dev runtime. Returns true when handled via HTTP.
async function sendAutoScanUpdate(payload) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Queue auto-scan updates instead of reporting them handled

When an auto-scan update is still in flight and the user immediately changes the toggle again—for example, enabling it and then quickly disabling it—this early return reports success without sending the second update. The caller consequently skips its Socket.IO fallback, while the first request completes and leaves automatic scanning enabled despite the user's last action; serialize or queue the latest update rather than treating a dropped request as handled.

Useful? React with 👍 / 👎.

if (autoScanHttpBusy) {
// Never report an in-flight update as handled - the caller must still
// emit over socket so rapid toggle changes cannot be silently dropped.
return false;
}
autoScanHttpBusy = true;
try {
const response = await fetch('/api/auto_scan/update', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
if (!response.ok) return false;
// The update acknowledgement is only {success: true}; pull the
// effective config (incl. next_run/warning fields) separately.
await refreshAutoScanStatus();
return true;
} catch (error) {
return false;
} finally {
autoScanHttpBusy = false;
}
}

async function refreshAutoScanStatus() {
try {
const response = await fetch('/api/auto_scan/status');
if (!response.ok) return;
applyAutoScanConfigState(await response.json());
} catch (error) {
// Socket handlers remain the fallback on the Node dev runtime.
}
}

function initializeAutoScanUI(socket) {
const autoScanToggle = document.getElementById('auto-scan-toggle');
const autoScanModal = document.getElementById('auto-scan-modal');
const recurrenceInput = document.getElementById('auto-recurrence');
const startTimeInput = document.getElementById('auto-start-time');
if (!autoScanToggle || !autoScanModal) return;

window.currentAutoScanConfig = window.currentAutoScanConfig || {};

function applyAutoScanConfig(config = {}) {
window.currentAutoScanConfig = config || {};
autoScanToggle.checked = !!config.enabled;
if (recurrenceInput) recurrenceInput.value = config.recurrence || 'daily';
if (startTimeInput) startTimeInput.value = config.startTime || '01:00';
}

autoScanToggle.addEventListener('change', () => {
if (autoScanToggle.checked) {
autoScanModal.classList.remove('hidden');
} else {
socket.emit('disable_auto_scan');
sendAutoScanUpdate({ enabled: false }).then((handled) => {
if (!handled) socket.emit('disable_auto_scan');
});
}
});

socket.on('sync_state', (state = {}) => applyAutoScanConfig(state.autoScan));
socket.on('initial_data', (data = {}) => applyAutoScanConfig(data.autoScan));
socket.on('auto_scan_config', applyAutoScanConfig);
socket.on('sync_state', (state = {}) => applyAutoScanConfigState(state.autoScan));
socket.on('initial_data', (data = {}) => applyAutoScanConfigState(data.autoScan));
socket.on('auto_scan_config', applyAutoScanConfigState);
socket.on('auto_scan_status', applyAutoScanConfigState);
refreshAutoScanStatus();
}

function saveAutoScanTimes() {
const recurrence = document.getElementById('auto-recurrence').value;
const startTime = document.getElementById('auto-start-time').value;
const target = document.getElementById('scan-target').value;
async function saveAutoScanTimes() {
const startTime = document.getElementById('auto-start-time')?.value || '01:00';
const endTime = document.getElementById('auto-end-time')?.value || '06:00';

window.socket.emit('enable_auto_scan', { recurrence, startTime, target });
// Flask schedule is a daily start/end window scanning the current network
// target; recurrence/target selectors only exist on the Node dev runtime.
const handled = await sendAutoScanUpdate({ enabled: true, start_time: startTime, end_time: endTime });
if (!handled) {
window.socket.emit('enable_auto_scan', { startTime, target: document.getElementById('scan-target')?.value });
}
document.getElementById('auto-scan-modal').classList.add('hidden');
}

Expand Down
Loading
Loading