mirror of
https://github.com/smittix/intercept.git
synced 2026-06-08 14:11:54 -07:00
feat(drone): replace freeform inputs with populated device selects
Add /drone/devices endpoint that enumerates available WiFi interfaces (via iw/iwconfig) and RTL-SDR devices (via SDRFactory.detect_devices), matching the pattern used by TSCM. Sidebar WiFi interface and RTL-SDR inputs are now <select> elements populated on init() from /drone/devices, consistent with how other modes expose hardware selection. HackRF checkbox remains as a toggle since it's a binary capability rather than an enumerated device list. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import platform
|
||||
import queue
|
||||
import subprocess
|
||||
import threading
|
||||
|
||||
from flask import Blueprint, Response, jsonify, request
|
||||
@@ -56,6 +58,81 @@ def _ensure_workers() -> None:
|
||||
_relay_thread.start()
|
||||
|
||||
|
||||
@drone_bp.route("/devices")
|
||||
def devices():
|
||||
"""Return available WiFi interfaces and SDR devices for drone detection."""
|
||||
result: dict = {"wifi_interfaces": [], "sdr_devices": []}
|
||||
|
||||
# WiFi interfaces via iw/iwconfig
|
||||
if platform.system() == "Darwin":
|
||||
try:
|
||||
out = subprocess.run(
|
||||
["networksetup", "-listallhardwareports"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=5,
|
||||
).stdout
|
||||
lines = out.split("\n")
|
||||
for i, line in enumerate(lines):
|
||||
if "Wi-Fi" in line or "AirPort" in line:
|
||||
port = line.replace("Hardware Port:", "").strip()
|
||||
for j in range(i + 1, min(i + 3, len(lines))):
|
||||
if "Device:" in lines[j]:
|
||||
dev = lines[j].split("Device:")[1].strip()
|
||||
result["wifi_interfaces"].append(
|
||||
{"name": dev, "display_name": f"{port} ({dev})", "type": "internal"}
|
||||
)
|
||||
break
|
||||
except (FileNotFoundError, subprocess.TimeoutExpired, subprocess.SubprocessError):
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
out = subprocess.run(["iw", "dev"], capture_output=True, text=True, timeout=5).stdout
|
||||
current: str | None = None
|
||||
for line in out.split("\n"):
|
||||
line = line.strip()
|
||||
if line.startswith("Interface"):
|
||||
current = line.split()[1]
|
||||
elif current and "type" in line:
|
||||
iface_type = line.split()[-1]
|
||||
result["wifi_interfaces"].append(
|
||||
{
|
||||
"name": current,
|
||||
"display_name": f"{current} ({iface_type})",
|
||||
"type": iface_type,
|
||||
}
|
||||
)
|
||||
current = None
|
||||
except (FileNotFoundError, subprocess.TimeoutExpired, subprocess.SubprocessError):
|
||||
try:
|
||||
out = subprocess.run(["iwconfig"], capture_output=True, text=True, timeout=5).stdout
|
||||
for line in out.split("\n"):
|
||||
if "IEEE 802.11" in line:
|
||||
iface = line.split()[0]
|
||||
result["wifi_interfaces"].append(
|
||||
{"name": iface, "display_name": f"{iface} (managed)", "type": "managed"}
|
||||
)
|
||||
except (FileNotFoundError, subprocess.TimeoutExpired, subprocess.SubprocessError):
|
||||
pass
|
||||
|
||||
# SDR devices
|
||||
try:
|
||||
from utils.sdr import SDRFactory
|
||||
|
||||
for sdr in SDRFactory.detect_devices():
|
||||
sdr_type = sdr.sdr_type.value if hasattr(sdr.sdr_type, "value") else str(sdr.sdr_type)
|
||||
display = sdr.name
|
||||
if sdr.serial and sdr.serial not in ("N/A", "Unknown"):
|
||||
display = f"{sdr.name} (SN: {sdr.serial[-8:]})"
|
||||
result["sdr_devices"].append(
|
||||
{"index": sdr.index, "name": sdr.name, "display_name": display, "type": sdr_type}
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return jsonify({"status": "ok", "devices": result})
|
||||
|
||||
|
||||
@drone_bp.route("/status")
|
||||
def status():
|
||||
vectors = []
|
||||
|
||||
@@ -10,11 +10,63 @@
|
||||
function init() {
|
||||
document.getElementById('droneStartBtn')?.addEventListener('click', _start);
|
||||
document.getElementById('droneStopBtn')?.addEventListener('click', _stop);
|
||||
_refreshDevices();
|
||||
_initMap();
|
||||
_connectSSE();
|
||||
_refreshStatus();
|
||||
}
|
||||
|
||||
function _refreshDevices() {
|
||||
fetch('/drone/devices')
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (data) {
|
||||
const devs = data.devices || {};
|
||||
_populateSelect(
|
||||
'droneWifiIface',
|
||||
devs.wifi_interfaces || [],
|
||||
function (i) { return i.name; },
|
||||
function (i) { return i.display_name || i.name; },
|
||||
'No WiFi interfaces found'
|
||||
);
|
||||
_populateSelect(
|
||||
'droneRtlIndex',
|
||||
devs.sdr_devices || [],
|
||||
function (d) { return d.index; },
|
||||
function (d) { return d.display_name || d.name; },
|
||||
'No SDR devices found'
|
||||
);
|
||||
})
|
||||
.catch(function () {
|
||||
_setSelectError('droneWifiIface', 'Failed to load interfaces');
|
||||
_setSelectError('droneRtlIndex', 'Failed to load devices');
|
||||
});
|
||||
}
|
||||
|
||||
function _populateSelect(id, items, valFn, labelFn, emptyMsg) {
|
||||
const sel = document.getElementById(id);
|
||||
if (!sel) return;
|
||||
sel.innerHTML = '';
|
||||
if (!items.length) {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = '';
|
||||
opt.textContent = emptyMsg;
|
||||
sel.appendChild(opt);
|
||||
return;
|
||||
}
|
||||
items.forEach(function (item) {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = valFn(item);
|
||||
opt.textContent = labelFn(item);
|
||||
sel.appendChild(opt);
|
||||
});
|
||||
}
|
||||
|
||||
function _setSelectError(id, msg) {
|
||||
const sel = document.getElementById(id);
|
||||
if (!sel) return;
|
||||
sel.innerHTML = '<option value="">' + msg + '</option>';
|
||||
}
|
||||
|
||||
function _initMap() {
|
||||
if (_map) return;
|
||||
const mapEl = document.getElementById('droneMainMap');
|
||||
@@ -163,8 +215,10 @@
|
||||
}
|
||||
|
||||
function _start() {
|
||||
const iface = document.getElementById('droneWifiIface')?.value.trim() || null;
|
||||
const rtlIndex = parseInt(document.getElementById('droneRtlIndex')?.value, 10) || 0;
|
||||
const ifaceVal = document.getElementById('droneWifiIface')?.value || '';
|
||||
const iface = ifaceVal || null;
|
||||
const rtlVal = document.getElementById('droneRtlIndex')?.value;
|
||||
const rtlIndex = rtlVal !== '' && rtlVal != null ? parseInt(rtlVal, 10) : 0;
|
||||
const useHackrf = document.getElementById('droneUseHackrf')?.checked ?? true;
|
||||
fetch('/drone/start', {
|
||||
method: 'POST',
|
||||
|
||||
@@ -20,15 +20,19 @@
|
||||
<h3>WiFi Interface</h3>
|
||||
<div class="form-group">
|
||||
<label for="droneWifiIface">Interface (monitor mode)</label>
|
||||
<input type="text" id="droneWifiIface" placeholder="e.g. wlan0mon">
|
||||
<select id="droneWifiIface">
|
||||
<option value="">Loading interfaces…</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>SDR Settings</h3>
|
||||
<div class="form-group">
|
||||
<label for="droneRtlIndex">RTL-SDR Device Index (433 MHz)</label>
|
||||
<input type="number" id="droneRtlIndex" value="0" min="0" max="7" placeholder="Device index">
|
||||
<label for="droneRtlIndex">RTL-SDR Device (433 MHz)</label>
|
||||
<select id="droneRtlIndex">
|
||||
<option value="">Loading devices…</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="inline-checkbox">
|
||||
|
||||
Reference in New Issue
Block a user