mirror of
https://github.com/smittix/intercept.git
synced 2026-07-01 06:19:00 -07:00
Fix PR #124 remaining issues: XSS, state management, DB regression
- kill_all() now resets gsm_spy_scanner_running and related state so the scanner thread stops after killall - scanner_thread sets flag to False instead of None on exit - Restore alert_rules, alert_events, recording_sessions tables and wifi_clients column removed by PR in database.py - Escape all server-sourced values in analysis modals with escapeHtml() - Reset gsm_towers_found/gsm_devices_tracked on stop to prevent counter drift across sessions - Replace raw terminate/kill with safe_terminate() in scanner_thread Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -676,6 +676,7 @@ def kill_all() -> Response:
|
|||||||
global current_process, sensor_process, wifi_process, adsb_process, ais_process, acars_process
|
global current_process, sensor_process, wifi_process, adsb_process, ais_process, acars_process
|
||||||
global aprs_process, aprs_rtl_process, dsc_process, dsc_rtl_process, bt_process
|
global aprs_process, aprs_rtl_process, dsc_process, dsc_rtl_process, bt_process
|
||||||
global gsm_spy_livemon_process, gsm_spy_monitor_process
|
global gsm_spy_livemon_process, gsm_spy_monitor_process
|
||||||
|
global gsm_spy_scanner_running, gsm_spy_active_device, gsm_spy_selected_arfcn, gsm_spy_region
|
||||||
|
|
||||||
# Import adsb and ais modules to reset their state
|
# Import adsb and ais modules to reset their state
|
||||||
from routes import adsb as adsb_module
|
from routes import adsb as adsb_module
|
||||||
@@ -754,6 +755,11 @@ def kill_all() -> Response:
|
|||||||
|
|
||||||
# Reset GSM Spy state
|
# Reset GSM Spy state
|
||||||
with gsm_spy_lock:
|
with gsm_spy_lock:
|
||||||
|
gsm_spy_scanner_running = False
|
||||||
|
gsm_spy_active_device = None
|
||||||
|
gsm_spy_selected_arfcn = None
|
||||||
|
gsm_spy_region = 'Americas'
|
||||||
|
|
||||||
if gsm_spy_livemon_process:
|
if gsm_spy_livemon_process:
|
||||||
try:
|
try:
|
||||||
if safe_terminate(gsm_spy_livemon_process):
|
if safe_terminate(gsm_spy_livemon_process):
|
||||||
|
|||||||
+7
-30
@@ -463,7 +463,7 @@ def start_monitor():
|
|||||||
@gsm_spy_bp.route('/stop', methods=['POST'])
|
@gsm_spy_bp.route('/stop', methods=['POST'])
|
||||||
def stop_scanner():
|
def stop_scanner():
|
||||||
"""Stop GSM scanner and monitor."""
|
"""Stop GSM scanner and monitor."""
|
||||||
global gsm_connected
|
global gsm_connected, gsm_towers_found, gsm_devices_tracked
|
||||||
|
|
||||||
with app_module.gsm_spy_lock:
|
with app_module.gsm_spy_lock:
|
||||||
killed = []
|
killed = []
|
||||||
@@ -492,6 +492,8 @@ def stop_scanner():
|
|||||||
app_module.gsm_spy_active_device = None
|
app_module.gsm_spy_active_device = None
|
||||||
app_module.gsm_spy_selected_arfcn = None
|
app_module.gsm_spy_selected_arfcn = None
|
||||||
gsm_connected = False
|
gsm_connected = False
|
||||||
|
gsm_towers_found = 0
|
||||||
|
gsm_devices_tracked = 0
|
||||||
|
|
||||||
return jsonify({'status': 'stopped', 'killed': killed})
|
return jsonify({'status': 'stopped', 'killed': killed})
|
||||||
|
|
||||||
@@ -1317,13 +1319,7 @@ def scanner_thread(cmd, device_index):
|
|||||||
# Clean up process with timeout
|
# Clean up process with timeout
|
||||||
if process.poll() is None:
|
if process.poll() is None:
|
||||||
logger.info("Terminating scanner process")
|
logger.info("Terminating scanner process")
|
||||||
process.terminate()
|
safe_terminate(process, timeout=5)
|
||||||
try:
|
|
||||||
process.wait(timeout=5)
|
|
||||||
except subprocess.TimeoutExpired:
|
|
||||||
logger.warning("Process didn't terminate, killing")
|
|
||||||
process.kill()
|
|
||||||
process.wait()
|
|
||||||
else:
|
else:
|
||||||
process.wait() # Reap zombie
|
process.wait() # Reap zombie
|
||||||
|
|
||||||
@@ -1332,14 +1328,7 @@ def scanner_thread(cmd, device_index):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Scanner scan error: {e}", exc_info=True)
|
logger.error(f"Scanner scan error: {e}", exc_info=True)
|
||||||
if process and process.poll() is None:
|
if process and process.poll() is None:
|
||||||
try:
|
safe_terminate(process)
|
||||||
process.terminate()
|
|
||||||
process.wait(timeout=2)
|
|
||||||
except Exception:
|
|
||||||
try:
|
|
||||||
process.kill()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Check if should continue
|
# Check if should continue
|
||||||
if not app_module.gsm_spy_scanner_running:
|
if not app_module.gsm_spy_scanner_running:
|
||||||
@@ -1358,25 +1347,13 @@ def scanner_thread(cmd, device_index):
|
|||||||
finally:
|
finally:
|
||||||
# Always cleanup
|
# Always cleanup
|
||||||
if process and process.poll() is None:
|
if process and process.poll() is None:
|
||||||
try:
|
safe_terminate(process, timeout=5)
|
||||||
process.terminate()
|
|
||||||
process.wait(timeout=5)
|
|
||||||
except Exception:
|
|
||||||
try:
|
|
||||||
process.kill()
|
|
||||||
process.wait()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Unregister process from cleanup list
|
|
||||||
if process:
|
|
||||||
unregister_process(process)
|
|
||||||
|
|
||||||
logger.info("Scanner thread terminated")
|
logger.info("Scanner thread terminated")
|
||||||
|
|
||||||
# Reset global state
|
# Reset global state
|
||||||
with app_module.gsm_spy_lock:
|
with app_module.gsm_spy_lock:
|
||||||
app_module.gsm_spy_scanner_running = None
|
app_module.gsm_spy_scanner_running = False
|
||||||
if app_module.gsm_spy_active_device is not None:
|
if app_module.gsm_spy_active_device is not None:
|
||||||
from app import release_sdr_device
|
from app import release_sdr_device
|
||||||
release_sdr_device(app_module.gsm_spy_active_device)
|
release_sdr_device(app_module.gsm_spy_active_device)
|
||||||
|
|||||||
@@ -2160,14 +2160,14 @@
|
|||||||
const velocity_kmh = (item.estimated_velocity * 3.6).toFixed(2);
|
const velocity_kmh = (item.estimated_velocity * 3.6).toFixed(2);
|
||||||
html += `
|
html += `
|
||||||
<div class="analysis-device-item">
|
<div class="analysis-device-item">
|
||||||
<div style="font-weight: 600; color: var(--accent-cyan);">${item.device_id}</div>
|
<div style="font-weight: 600; color: var(--accent-cyan);">${escapeHtml(item.device_id)}</div>
|
||||||
<div class="analysis-stat">
|
<div class="analysis-stat">
|
||||||
<span class="analysis-stat-label">Velocity:</span>
|
<span class="analysis-stat-label">Velocity:</span>
|
||||||
<span class="analysis-stat-value">${velocity_kmh} km/h</span>
|
<span class="analysis-stat-value">${escapeHtml(velocity_kmh)} km/h</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="analysis-stat">
|
<div class="analysis-stat">
|
||||||
<span class="analysis-stat-label">TA Change:</span>
|
<span class="analysis-stat-label">TA Change:</span>
|
||||||
<span class="analysis-stat-value">${item.prev_ta} → ${item.curr_ta}</span>
|
<span class="analysis-stat-value">${escapeHtml(String(item.prev_ta))} → ${escapeHtml(String(item.curr_ta))}</span>
|
||||||
</div>
|
</div>
|
||||||
<div style="font-size: 9px; color: var(--text-dim); margin-top: 4px;">${new Date(item.timestamp).toLocaleString()}</div>
|
<div style="font-size: 9px; color: var(--text-dim); margin-top: 4px;">${new Date(item.timestamp).toLocaleString()}</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -2201,18 +2201,18 @@
|
|||||||
item.density_level === 'medium' ? 'var(--accent-yellow)' : 'var(--accent-green)';
|
item.density_level === 'medium' ? 'var(--accent-yellow)' : 'var(--accent-green)';
|
||||||
html += `
|
html += `
|
||||||
<div class="analysis-device-item" style="border-left-color: ${densityColor};">
|
<div class="analysis-device-item" style="border-left-color: ${densityColor};">
|
||||||
<div style="font-weight: 600; color: var(--accent-cyan);">Cell ${item.cid}</div>
|
<div style="font-weight: 600; color: var(--accent-cyan);">Cell ${escapeHtml(String(item.cid))}</div>
|
||||||
<div class="analysis-stat">
|
<div class="analysis-stat">
|
||||||
<span class="analysis-stat-label">Unique Devices:</span>
|
<span class="analysis-stat-label">Unique Devices:</span>
|
||||||
<span class="analysis-stat-value">${item.unique_devices}</span>
|
<span class="analysis-stat-value">${escapeHtml(String(item.unique_devices))}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="analysis-stat">
|
<div class="analysis-stat">
|
||||||
<span class="analysis-stat-label">Total Pings:</span>
|
<span class="analysis-stat-label">Total Pings:</span>
|
||||||
<span class="analysis-stat-value">${item.total_pings}</span>
|
<span class="analysis-stat-value">${escapeHtml(String(item.total_pings))}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="analysis-stat">
|
<div class="analysis-stat">
|
||||||
<span class="analysis-stat-label">Density:</span>
|
<span class="analysis-stat-label">Density:</span>
|
||||||
<span class="analysis-stat-value" style="color: ${densityColor}; text-transform: uppercase;">${item.density_level}</span>
|
<span class="analysis-stat-value" style="color: ${densityColor}; text-transform: uppercase;">${escapeHtml(item.density_level)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -2241,25 +2241,25 @@
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
contentDiv.innerHTML = `<div class="analysis-warning">${data.error}</div>`;
|
contentDiv.innerHTML = `<div class="analysis-warning">${escapeHtml(data.error)}</div>`;
|
||||||
} else if (data.regular_locations && data.regular_locations.length > 0) {
|
} else if (data.regular_locations && data.regular_locations.length > 0) {
|
||||||
let html = `
|
let html = `
|
||||||
<div style="font-size: 10px; color: var(--text-secondary); margin-bottom: 10px;">
|
<div style="font-size: 10px; color: var(--text-secondary); margin-bottom: 10px;">
|
||||||
${data.total_observations} total observations
|
${escapeHtml(String(data.total_observations))} total observations
|
||||||
</div>
|
</div>
|
||||||
<div style="font-weight: 600; margin-bottom: 8px;">Regular Locations:</div>
|
<div style="font-weight: 600; margin-bottom: 8px;">Regular Locations:</div>
|
||||||
`;
|
`;
|
||||||
data.regular_locations.forEach(loc => {
|
data.regular_locations.forEach(loc => {
|
||||||
html += `
|
html += `
|
||||||
<div class="analysis-device-item">
|
<div class="analysis-device-item">
|
||||||
<div style="font-weight: 600; color: var(--accent-cyan);">Cell ${loc.cid}</div>
|
<div style="font-weight: 600; color: var(--accent-cyan);">Cell ${escapeHtml(String(loc.cid))}</div>
|
||||||
<div class="analysis-stat">
|
<div class="analysis-stat">
|
||||||
<span class="analysis-stat-label">Typical Time:</span>
|
<span class="analysis-stat-label">Typical Time:</span>
|
||||||
<span class="analysis-stat-value">${loc.typical_time}</span>
|
<span class="analysis-stat-value">${escapeHtml(loc.typical_time)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="analysis-stat">
|
<div class="analysis-stat">
|
||||||
<span class="analysis-stat-label">Frequency:</span>
|
<span class="analysis-stat-label">Frequency:</span>
|
||||||
<span class="analysis-stat-value">${loc.frequency} times</span>
|
<span class="analysis-stat-value">${escapeHtml(String(loc.frequency))} times</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -2290,17 +2290,17 @@
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
contentDiv.innerHTML = `<div class="analysis-warning">${data.error}</div>`;
|
contentDiv.innerHTML = `<div class="analysis-warning">${escapeHtml(data.error)}</div>`;
|
||||||
} else {
|
} else {
|
||||||
const statusColor = data.status === 'suspicious' ? 'var(--accent-red)' : 'var(--accent-green)';
|
const statusColor = data.status === 'suspicious' ? 'var(--accent-red)' : 'var(--accent-green)';
|
||||||
let html = `
|
let html = `
|
||||||
<div class="analysis-stat">
|
<div class="analysis-stat">
|
||||||
<span class="analysis-stat-label">Status:</span>
|
<span class="analysis-stat-label">Status:</span>
|
||||||
<span class="analysis-stat-value" style="color: ${statusColor}; text-transform: uppercase;">${data.status}</span>
|
<span class="analysis-stat-value" style="color: ${statusColor}; text-transform: uppercase;">${escapeHtml(data.status)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="analysis-stat">
|
<div class="analysis-stat">
|
||||||
<span class="analysis-stat-label">Neighbor Count:</span>
|
<span class="analysis-stat-label">Neighbor Count:</span>
|
||||||
<span class="analysis-stat-value">${data.neighbor_count}</span>
|
<span class="analysis-stat-value">${escapeHtml(String(data.neighbor_count))}</span>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -2309,8 +2309,8 @@
|
|||||||
data.issues.forEach(issue => {
|
data.issues.forEach(issue => {
|
||||||
html += `
|
html += `
|
||||||
<div class="analysis-warning">
|
<div class="analysis-warning">
|
||||||
<div style="font-weight: 600;">${issue.type}</div>
|
<div style="font-weight: 600;">${escapeHtml(issue.type)}</div>
|
||||||
<div>${issue.message}</div>
|
<div>${escapeHtml(issue.message)}</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
});
|
});
|
||||||
@@ -2342,15 +2342,15 @@
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
contentDiv.innerHTML = `<div class="analysis-warning">${data.error}</div>`;
|
contentDiv.innerHTML = `<div class="analysis-warning">${escapeHtml(data.error)}</div>`;
|
||||||
} else {
|
} else {
|
||||||
let html = `
|
let html = `
|
||||||
<div style="font-size: 10px; color: var(--text-secondary); margin-bottom: 10px;">
|
<div style="font-size: 10px; color: var(--text-secondary); margin-bottom: 10px;">
|
||||||
Last ${data.time_window_minutes} minutes
|
Last ${escapeHtml(String(data.time_window_minutes))} minutes
|
||||||
</div>
|
</div>
|
||||||
<div class="analysis-stat">
|
<div class="analysis-stat">
|
||||||
<span class="analysis-stat-label">Active Devices:</span>
|
<span class="analysis-stat-label">Active Devices:</span>
|
||||||
<span class="analysis-stat-value">${data.active_devices}</span>
|
<span class="analysis-stat-value">${escapeHtml(String(data.active_devices))}</span>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -2361,16 +2361,16 @@
|
|||||||
corr.activity_level === 'medium' ? 'var(--accent-yellow)' : 'var(--accent-green)';
|
corr.activity_level === 'medium' ? 'var(--accent-yellow)' : 'var(--accent-green)';
|
||||||
html += `
|
html += `
|
||||||
<div class="analysis-device-item">
|
<div class="analysis-device-item">
|
||||||
<div style="font-weight: 600; color: var(--accent-cyan);">${corr.device_id}</div>
|
<div style="font-weight: 600; color: var(--accent-cyan);">${escapeHtml(corr.device_id)}</div>
|
||||||
<div class="analysis-stat">
|
<div class="analysis-stat">
|
||||||
<span class="analysis-stat-label">Burst Count:</span>
|
<span class="analysis-stat-label">Burst Count:</span>
|
||||||
<span class="analysis-stat-value">${corr.burst_count}</span>
|
<span class="analysis-stat-value">${escapeHtml(String(corr.burst_count))}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="analysis-stat">
|
<div class="analysis-stat">
|
||||||
<span class="analysis-stat-label">Activity:</span>
|
<span class="analysis-stat-label">Activity:</span>
|
||||||
<span class="analysis-stat-value" style="color: ${activityColor}; text-transform: uppercase;">${corr.activity_level}</span>
|
<span class="analysis-stat-value" style="color: ${activityColor}; text-transform: uppercase;">${escapeHtml(corr.activity_level)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div style="font-size: 9px; color: var(--text-dim); margin-top: 4px;">TA: ${corr.ta_value}</div>
|
<div style="font-size: 9px; color: var(--text-dim); margin-top: 4px;">TA: ${escapeHtml(String(corr.ta_value))}</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
});
|
});
|
||||||
|
|||||||
+59
-2
@@ -102,6 +102,52 @@ def init_db() -> None:
|
|||||||
)
|
)
|
||||||
''')
|
''')
|
||||||
|
|
||||||
|
# Alert rules
|
||||||
|
conn.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS alert_rules (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
mode TEXT,
|
||||||
|
event_type TEXT,
|
||||||
|
match TEXT,
|
||||||
|
severity TEXT DEFAULT 'medium',
|
||||||
|
enabled BOOLEAN DEFAULT 1,
|
||||||
|
notify TEXT,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
|
||||||
|
# Alert events
|
||||||
|
conn.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS alert_events (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
rule_id INTEGER,
|
||||||
|
mode TEXT,
|
||||||
|
event_type TEXT,
|
||||||
|
severity TEXT DEFAULT 'medium',
|
||||||
|
title TEXT,
|
||||||
|
message TEXT,
|
||||||
|
payload TEXT,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (rule_id) REFERENCES alert_rules(id) ON DELETE SET NULL
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
|
||||||
|
# Session recordings
|
||||||
|
conn.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS recording_sessions (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
mode TEXT NOT NULL,
|
||||||
|
label TEXT,
|
||||||
|
started_at TIMESTAMP NOT NULL,
|
||||||
|
stopped_at TIMESTAMP,
|
||||||
|
file_path TEXT NOT NULL,
|
||||||
|
event_count INTEGER DEFAULT 0,
|
||||||
|
size_bytes INTEGER DEFAULT 0,
|
||||||
|
metadata TEXT
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
|
||||||
# Users table for authentication
|
# Users table for authentication
|
||||||
conn.execute('''
|
conn.execute('''
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
@@ -139,6 +185,7 @@ def init_db() -> None:
|
|||||||
description TEXT,
|
description TEXT,
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
wifi_networks TEXT,
|
wifi_networks TEXT,
|
||||||
|
wifi_clients TEXT,
|
||||||
bt_devices TEXT,
|
bt_devices TEXT,
|
||||||
rf_frequencies TEXT,
|
rf_frequencies TEXT,
|
||||||
gps_coords TEXT,
|
gps_coords TEXT,
|
||||||
@@ -146,6 +193,14 @@ def init_db() -> None:
|
|||||||
)
|
)
|
||||||
''')
|
''')
|
||||||
|
|
||||||
|
# Ensure new columns exist for older databases
|
||||||
|
try:
|
||||||
|
columns = {row['name'] for row in conn.execute("PRAGMA table_info(tscm_baselines)")}
|
||||||
|
if 'wifi_clients' not in columns:
|
||||||
|
conn.execute('ALTER TABLE tscm_baselines ADD COLUMN wifi_clients TEXT')
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"Schema update skipped for tscm_baselines: {e}")
|
||||||
|
|
||||||
# TSCM Sweeps - Individual sweep sessions
|
# TSCM Sweeps - Individual sweep sessions
|
||||||
conn.execute('''
|
conn.execute('''
|
||||||
CREATE TABLE IF NOT EXISTS tscm_sweeps (
|
CREATE TABLE IF NOT EXISTS tscm_sweeps (
|
||||||
@@ -818,6 +873,7 @@ def create_tscm_baseline(
|
|||||||
location: str | None = None,
|
location: str | None = None,
|
||||||
description: str | None = None,
|
description: str | None = None,
|
||||||
wifi_networks: list | None = None,
|
wifi_networks: list | None = None,
|
||||||
|
wifi_clients: list | None = None,
|
||||||
bt_devices: list | None = None,
|
bt_devices: list | None = None,
|
||||||
rf_frequencies: list | None = None,
|
rf_frequencies: list | None = None,
|
||||||
gps_coords: dict | None = None
|
gps_coords: dict | None = None
|
||||||
@@ -831,13 +887,14 @@ def create_tscm_baseline(
|
|||||||
with get_db() as conn:
|
with get_db() as conn:
|
||||||
cursor = conn.execute('''
|
cursor = conn.execute('''
|
||||||
INSERT INTO tscm_baselines
|
INSERT INTO tscm_baselines
|
||||||
(name, location, description, wifi_networks, bt_devices, rf_frequencies, gps_coords)
|
(name, location, description, wifi_networks, wifi_clients, bt_devices, rf_frequencies, gps_coords)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
''', (
|
''', (
|
||||||
name,
|
name,
|
||||||
location,
|
location,
|
||||||
description,
|
description,
|
||||||
json.dumps(wifi_networks) if wifi_networks else None,
|
json.dumps(wifi_networks) if wifi_networks else None,
|
||||||
|
json.dumps(wifi_clients) if wifi_clients else None,
|
||||||
json.dumps(bt_devices) if bt_devices else None,
|
json.dumps(bt_devices) if bt_devices else None,
|
||||||
json.dumps(rf_frequencies) if rf_frequencies else None,
|
json.dumps(rf_frequencies) if rf_frequencies else None,
|
||||||
json.dumps(gps_coords) if gps_coords else None
|
json.dumps(gps_coords) if gps_coords else None
|
||||||
|
|||||||
Reference in New Issue
Block a user