mirror of
https://github.com/smittix/intercept.git
synced 2026-07-25 17:28:11 -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
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import platform
|
||||||
import queue
|
import queue
|
||||||
|
import subprocess
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
from flask import Blueprint, Response, jsonify, request
|
from flask import Blueprint, Response, jsonify, request
|
||||||
@@ -56,6 +58,81 @@ def _ensure_workers() -> None:
|
|||||||
_relay_thread.start()
|
_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")
|
@drone_bp.route("/status")
|
||||||
def status():
|
def status():
|
||||||
vectors = []
|
vectors = []
|
||||||
|
|||||||
@@ -10,11 +10,63 @@
|
|||||||
function init() {
|
function init() {
|
||||||
document.getElementById('droneStartBtn')?.addEventListener('click', _start);
|
document.getElementById('droneStartBtn')?.addEventListener('click', _start);
|
||||||
document.getElementById('droneStopBtn')?.addEventListener('click', _stop);
|
document.getElementById('droneStopBtn')?.addEventListener('click', _stop);
|
||||||
|
_refreshDevices();
|
||||||
_initMap();
|
_initMap();
|
||||||
_connectSSE();
|
_connectSSE();
|
||||||
_refreshStatus();
|
_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() {
|
function _initMap() {
|
||||||
if (_map) return;
|
if (_map) return;
|
||||||
const mapEl = document.getElementById('droneMainMap');
|
const mapEl = document.getElementById('droneMainMap');
|
||||||
@@ -163,8 +215,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function _start() {
|
function _start() {
|
||||||
const iface = document.getElementById('droneWifiIface')?.value.trim() || null;
|
const ifaceVal = document.getElementById('droneWifiIface')?.value || '';
|
||||||
const rtlIndex = parseInt(document.getElementById('droneRtlIndex')?.value, 10) || 0;
|
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;
|
const useHackrf = document.getElementById('droneUseHackrf')?.checked ?? true;
|
||||||
fetch('/drone/start', {
|
fetch('/drone/start', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|||||||
@@ -20,15 +20,19 @@
|
|||||||
<h3>WiFi Interface</h3>
|
<h3>WiFi Interface</h3>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="droneWifiIface">Interface (monitor mode)</label>
|
<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>
|
</div>
|
||||||
|
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<h3>SDR Settings</h3>
|
<h3>SDR Settings</h3>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="droneRtlIndex">RTL-SDR Device Index (433 MHz)</label>
|
<label for="droneRtlIndex">RTL-SDR Device (433 MHz)</label>
|
||||||
<input type="number" id="droneRtlIndex" value="0" min="0" max="7" placeholder="Device index">
|
<select id="droneRtlIndex">
|
||||||
|
<option value="">Loading devices…</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="inline-checkbox">
|
<label class="inline-checkbox">
|
||||||
|
|||||||
Reference in New Issue
Block a user