mirror of
https://github.com/smittix/intercept.git
synced 2026-06-08 06:01:56 -07:00
Improve WiFi device identification, remove signal history, fix Listen button
- WiFi interfaces now show driver, chipset, and MAC address for easier identification - Remove signal history feature from WiFi and Bluetooth sections (HTML, JS, CSS, API) - Fix Listen button in Listening Post signal hits to properly tune to frequency - Make stopAudio() async and improve tuneToFrequency() with proper awaits - Fix Device Intelligence panel auto-expand and manufacturer display Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,8 +9,6 @@ from utils.database import (
|
||||
set_setting,
|
||||
delete_setting,
|
||||
get_all_settings,
|
||||
get_signal_history,
|
||||
add_signal_reading,
|
||||
get_correlations,
|
||||
)
|
||||
from utils.logging import get_logger
|
||||
@@ -145,66 +143,6 @@ def delete_single_setting(key: str) -> Response:
|
||||
}), 500
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Signal History Endpoints
|
||||
# =============================================================================
|
||||
|
||||
@settings_bp.route('/signal-history/<mode>/<device_id>', methods=['GET'])
|
||||
def get_device_signal_history(mode: str, device_id: str) -> Response:
|
||||
"""Get signal strength history for a device."""
|
||||
limit = request.args.get('limit', 100, type=int)
|
||||
since_minutes = request.args.get('since', 60, type=int)
|
||||
|
||||
# Validate mode
|
||||
valid_modes = ['wifi', 'bluetooth', 'adsb', 'pager', 'sensor']
|
||||
if mode not in valid_modes:
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': f'Invalid mode. Valid modes: {valid_modes}'
|
||||
}), 400
|
||||
|
||||
try:
|
||||
history = get_signal_history(mode, device_id, limit, since_minutes)
|
||||
return jsonify({
|
||||
'status': 'success',
|
||||
'mode': mode,
|
||||
'device_id': device_id,
|
||||
'history': history
|
||||
})
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting signal history: {e}")
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': str(e)
|
||||
}), 500
|
||||
|
||||
|
||||
@settings_bp.route('/signal-history', methods=['POST'])
|
||||
def add_signal_history() -> Response:
|
||||
"""Add a signal strength reading (for internal use)."""
|
||||
data = request.json or {}
|
||||
|
||||
mode = data.get('mode')
|
||||
device_id = data.get('device_id')
|
||||
signal_strength = data.get('signal_strength')
|
||||
|
||||
if not all([mode, device_id, signal_strength is not None]):
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': 'mode, device_id, and signal_strength are required'
|
||||
}), 400
|
||||
|
||||
try:
|
||||
add_signal_reading(mode, device_id, signal_strength, data.get('metadata'))
|
||||
return jsonify({'status': 'success'})
|
||||
except Exception as e:
|
||||
logger.error(f"Error adding signal reading: {e}")
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': str(e)
|
||||
}), 500
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Device Correlation Endpoints
|
||||
# =============================================================================
|
||||
|
||||
+71
-6
@@ -105,12 +105,18 @@ def detect_wifi_interfaces():
|
||||
current_iface = line.split()[1]
|
||||
elif current_iface and 'type' in line:
|
||||
iface_type = line.split()[-1]
|
||||
interfaces.append({
|
||||
iface_info = {
|
||||
'name': current_iface,
|
||||
'type': iface_type,
|
||||
'monitor_capable': True,
|
||||
'status': 'up'
|
||||
})
|
||||
'status': 'up',
|
||||
'driver': '',
|
||||
'chipset': '',
|
||||
'mac': ''
|
||||
}
|
||||
# Get additional interface details
|
||||
iface_info.update(_get_interface_details(current_iface))
|
||||
interfaces.append(iface_info)
|
||||
current_iface = None
|
||||
except FileNotFoundError:
|
||||
# Fall back to iwconfig if iw is not available
|
||||
@@ -119,12 +125,17 @@ def detect_wifi_interfaces():
|
||||
for line in result.stdout.split('\n'):
|
||||
if 'IEEE 802.11' in line:
|
||||
iface = line.split()[0]
|
||||
interfaces.append({
|
||||
iface_info = {
|
||||
'name': iface,
|
||||
'type': 'managed',
|
||||
'monitor_capable': True,
|
||||
'status': 'up'
|
||||
})
|
||||
'status': 'up',
|
||||
'driver': '',
|
||||
'chipset': '',
|
||||
'mac': ''
|
||||
}
|
||||
iface_info.update(_get_interface_details(iface))
|
||||
interfaces.append(iface_info)
|
||||
except FileNotFoundError:
|
||||
logger.debug("Neither iw nor iwconfig found")
|
||||
except subprocess.SubprocessError as e:
|
||||
@@ -137,6 +148,60 @@ def detect_wifi_interfaces():
|
||||
return interfaces
|
||||
|
||||
|
||||
def _get_interface_details(iface_name):
|
||||
"""Get additional details about a WiFi interface (driver, chipset, MAC)."""
|
||||
details = {'driver': '', 'chipset': '', 'mac': ''}
|
||||
|
||||
# Get MAC address
|
||||
try:
|
||||
mac_path = f'/sys/class/net/{iface_name}/address'
|
||||
with open(mac_path, 'r') as f:
|
||||
details['mac'] = f.read().strip().upper()
|
||||
except (FileNotFoundError, IOError):
|
||||
pass
|
||||
|
||||
# Get driver name
|
||||
try:
|
||||
driver_link = f'/sys/class/net/{iface_name}/device/driver'
|
||||
import os
|
||||
if os.path.islink(driver_link):
|
||||
driver_path = os.readlink(driver_link)
|
||||
details['driver'] = os.path.basename(driver_path)
|
||||
except (FileNotFoundError, IOError, OSError):
|
||||
pass
|
||||
|
||||
# Get chipset info from USB or PCI
|
||||
try:
|
||||
# Check if USB device
|
||||
device_path = f'/sys/class/net/{iface_name}/device'
|
||||
import os
|
||||
if os.path.exists(device_path):
|
||||
# Try to get USB product name
|
||||
for usb_path in [f'{device_path}/product', f'{device_path}/../product']:
|
||||
try:
|
||||
with open(usb_path, 'r') as f:
|
||||
details['chipset'] = f.read().strip()
|
||||
break
|
||||
except (FileNotFoundError, IOError):
|
||||
pass
|
||||
|
||||
# If no USB product, try to get from uevent
|
||||
if not details['chipset']:
|
||||
try:
|
||||
uevent_path = f'{device_path}/uevent'
|
||||
with open(uevent_path, 'r') as f:
|
||||
for line in f:
|
||||
if line.startswith('PCI_ID=') or line.startswith('PRODUCT='):
|
||||
details['chipset'] = line.split('=')[1].strip()
|
||||
break
|
||||
except (FileNotFoundError, IOError):
|
||||
pass
|
||||
except (FileNotFoundError, IOError, OSError):
|
||||
pass
|
||||
|
||||
return details
|
||||
|
||||
|
||||
def parse_airodump_csv(csv_path):
|
||||
"""Parse airodump-ng CSV output file."""
|
||||
networks = {}
|
||||
|
||||
@@ -1309,38 +1309,6 @@ header p {
|
||||
color: var(--accent-red);
|
||||
}
|
||||
|
||||
/* Signal Strength Graph */
|
||||
.signal-graph-panel {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.signal-graph-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.signal-graph-header h4 {
|
||||
color: var(--accent-cyan);
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.signal-graph-device {
|
||||
font-size: 10px;
|
||||
color: var(--text-secondary);
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
}
|
||||
|
||||
#signalGraph,
|
||||
#btSignalGraph,
|
||||
#adsbStatsChart {
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
|
||||
+66
-286
@@ -1271,14 +1271,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Signal Strength History Graph -->
|
||||
<div class="wifi-visual-panel signal-graph-panel" id="signalGraphPanel" style="grid-column: span 2;">
|
||||
<div class="signal-graph-header">
|
||||
<h4>📈 Signal History</h4>
|
||||
<span class="signal-graph-device" id="signalGraphDevice">Click a device to track</span>
|
||||
</div>
|
||||
<canvas id="signalGraph"></canvas>
|
||||
</div>
|
||||
<!-- Network Relationship Graph -->
|
||||
<div class="wifi-visual-panel network-graph-container" style="grid-column: span 2;">
|
||||
<h4>🕸️ Network Topology</h4>
|
||||
@@ -1355,13 +1347,6 @@
|
||||
<div style="color: var(--text-dim); padding: 10px; text-align: center;">Monitoring for AirTags, Tiles, and other trackers...</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wifi-visual-panel signal-graph-panel" style="grid-column: span 4;">
|
||||
<div class="signal-graph-header">
|
||||
<h4>📈 BT Signal History</h4>
|
||||
<span class="signal-graph-device" id="btSignalGraphDevice">Select a device from the list</span>
|
||||
</div>
|
||||
<canvas id="btSignalGraph"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Aircraft Visualizations - Leaflet Map -->
|
||||
@@ -2340,6 +2325,10 @@
|
||||
} else {
|
||||
if (reconBtn) reconBtn.style.display = 'inline-block';
|
||||
if (intelBtn) intelBtn.style.display = 'inline-block';
|
||||
// Restore panel visibility based on reconEnabled state
|
||||
if (reconEnabled) {
|
||||
document.getElementById('reconPanel').style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
// Show RTL-SDR device section for modes that use it
|
||||
@@ -3599,7 +3588,7 @@
|
||||
<div class="device-info">
|
||||
<div class="device-name-row">
|
||||
<span class="timeline-dot ${timelineDot}"></span>
|
||||
<span class="badge ${badgeClass}">${profile.protocol.substring(0, 8)}</span>
|
||||
<span class="badge ${badgeClass}">${profile.protocol.substring(0, 10)}</span>
|
||||
${deviceId.substring(0, 30)}
|
||||
</div>
|
||||
<div class="device-id">
|
||||
@@ -4109,221 +4098,6 @@
|
||||
|
||||
// ============== NEW FEATURES ==============
|
||||
|
||||
// Signal History Graph with Chart.js
|
||||
let signalHistory = {}; // {mac: [{time, signal}]}
|
||||
let trackedDevice = null;
|
||||
let trackedDeviceMode = 'wifi'; // 'wifi' or 'bluetooth'
|
||||
const maxSignalPoints = 60;
|
||||
let signalChart = null;
|
||||
|
||||
function trackDeviceSignal(mac, signal, mode = 'wifi') {
|
||||
if (!signalHistory[mac]) {
|
||||
signalHistory[mac] = [];
|
||||
}
|
||||
const timestamp = Date.now();
|
||||
signalHistory[mac].push({
|
||||
time: timestamp,
|
||||
signal: parseInt(signal) || -100
|
||||
});
|
||||
// Keep only last N points
|
||||
if (signalHistory[mac].length > maxSignalPoints) {
|
||||
signalHistory[mac].shift();
|
||||
}
|
||||
// Update graph if this is the tracked device
|
||||
if (trackedDevice === mac) {
|
||||
updateSignalChart();
|
||||
}
|
||||
// Persist to server (non-blocking)
|
||||
fetch('/settings/signal-history', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ mode, device_id: mac, signal_strength: signal })
|
||||
}).catch(() => {}); // Ignore errors
|
||||
}
|
||||
|
||||
function setTrackedDevice(mac, name, mode = 'wifi') {
|
||||
trackedDevice = mac;
|
||||
trackedDeviceMode = mode;
|
||||
document.getElementById('signalGraphDevice').textContent = name || mac;
|
||||
initSignalChart();
|
||||
updateSignalChart();
|
||||
}
|
||||
|
||||
function initSignalChart() {
|
||||
const canvas = document.getElementById('signalGraph');
|
||||
if (!canvas) return;
|
||||
|
||||
// Destroy existing chart if any
|
||||
if (signalChart) {
|
||||
signalChart.destroy();
|
||||
}
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
signalChart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [{
|
||||
label: 'Signal (dBm)',
|
||||
data: [],
|
||||
borderColor: '#00d4ff',
|
||||
backgroundColor: 'rgba(0, 212, 255, 0.1)',
|
||||
borderWidth: 2,
|
||||
fill: true,
|
||||
tension: 0.3,
|
||||
pointRadius: 0,
|
||||
pointHoverRadius: 4
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
animation: { duration: 0 },
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
||||
titleColor: '#00d4ff',
|
||||
bodyColor: '#fff'
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
display: false
|
||||
},
|
||||
y: {
|
||||
min: -100,
|
||||
max: -20,
|
||||
reverse: true,
|
||||
grid: { color: 'rgba(255, 255, 255, 0.1)' },
|
||||
ticks: {
|
||||
color: '#666',
|
||||
font: { size: 10, family: 'monospace' },
|
||||
callback: v => v + ' dBm'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateSignalChart() {
|
||||
if (!signalChart || !trackedDevice) return;
|
||||
|
||||
const data = signalHistory[trackedDevice] || [];
|
||||
if (data.length === 0) return;
|
||||
|
||||
signalChart.data.labels = data.map((_, i) => i);
|
||||
signalChart.data.datasets[0].data = data.map(p => p.signal);
|
||||
signalChart.update('none');
|
||||
|
||||
// Update current value display
|
||||
const lastSignal = data[data.length - 1].signal;
|
||||
const deviceLabel = document.getElementById('signalGraphDevice');
|
||||
if (deviceLabel && !deviceLabel.textContent.includes('dBm')) {
|
||||
deviceLabel.textContent += ` (${lastSignal} dBm)`;
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy function for backward compatibility
|
||||
function drawSignalGraph() {
|
||||
if (!signalChart) {
|
||||
initSignalChart();
|
||||
}
|
||||
updateSignalChart();
|
||||
}
|
||||
|
||||
// Bluetooth Signal History Chart
|
||||
let btSignalChart = null;
|
||||
let btTrackedDevice = null;
|
||||
let btSignalHistory = {};
|
||||
|
||||
function trackBtDeviceSignal(mac, rssi) {
|
||||
if (!btSignalHistory[mac]) {
|
||||
btSignalHistory[mac] = [];
|
||||
}
|
||||
btSignalHistory[mac].push({
|
||||
time: Date.now(),
|
||||
signal: parseInt(rssi) || -100
|
||||
});
|
||||
if (btSignalHistory[mac].length > maxSignalPoints) {
|
||||
btSignalHistory[mac].shift();
|
||||
}
|
||||
if (btTrackedDevice === mac) {
|
||||
updateBtSignalChart();
|
||||
}
|
||||
// Persist to server
|
||||
fetch('/settings/signal-history', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ mode: 'bluetooth', device_id: mac, signal_strength: rssi })
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
function setBtTrackedDevice(mac, name) {
|
||||
btTrackedDevice = mac;
|
||||
document.getElementById('btSignalGraphDevice').textContent = name || mac;
|
||||
initBtSignalChart();
|
||||
updateBtSignalChart();
|
||||
}
|
||||
|
||||
function initBtSignalChart() {
|
||||
const canvas = document.getElementById('btSignalGraph');
|
||||
if (!canvas) return;
|
||||
|
||||
if (btSignalChart) {
|
||||
btSignalChart.destroy();
|
||||
}
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
btSignalChart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [{
|
||||
label: 'RSSI (dBm)',
|
||||
data: [],
|
||||
borderColor: '#ff6600',
|
||||
backgroundColor: 'rgba(255, 102, 0, 0.1)',
|
||||
borderWidth: 2,
|
||||
fill: true,
|
||||
tension: 0.3,
|
||||
pointRadius: 0
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
animation: { duration: 0 },
|
||||
plugins: { legend: { display: false } },
|
||||
scales: {
|
||||
x: { display: false },
|
||||
y: {
|
||||
min: -100,
|
||||
max: -20,
|
||||
reverse: true,
|
||||
grid: { color: 'rgba(255, 255, 255, 0.1)' },
|
||||
ticks: {
|
||||
color: '#666',
|
||||
font: { size: 10, family: 'monospace' },
|
||||
callback: v => v + ' dBm'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateBtSignalChart() {
|
||||
if (!btSignalChart || !btTrackedDevice) return;
|
||||
const data = btSignalHistory[btTrackedDevice] || [];
|
||||
if (data.length === 0) return;
|
||||
|
||||
btSignalChart.data.labels = data.map((_, i) => i);
|
||||
btSignalChart.data.datasets[0].data = data.map(p => p.signal);
|
||||
btSignalChart.update('none');
|
||||
}
|
||||
|
||||
// Network Topology Graph
|
||||
function drawNetworkGraph() {
|
||||
const canvas = document.getElementById('networkGraph');
|
||||
@@ -4604,7 +4378,6 @@
|
||||
// Update visualizations periodically
|
||||
setInterval(() => {
|
||||
if (currentMode === 'wifi') {
|
||||
drawSignalGraph();
|
||||
drawNetworkGraph();
|
||||
updateChannelRecommendation();
|
||||
correlateDevices();
|
||||
@@ -4620,9 +4393,18 @@
|
||||
if (data.interfaces.length === 0) {
|
||||
select.innerHTML = '<option value="">No WiFi interfaces found</option>';
|
||||
} else {
|
||||
select.innerHTML = data.interfaces.map(i =>
|
||||
`<option value="${i.name}">${i.name} (${i.type})${i.monitor_capable ? ' [Monitor OK]' : ''}</option>`
|
||||
).join('');
|
||||
select.innerHTML = data.interfaces.map(i => {
|
||||
// Build descriptive label with available info
|
||||
let label = i.name;
|
||||
let details = [];
|
||||
if (i.chipset) details.push(i.chipset);
|
||||
else if (i.driver) details.push(i.driver);
|
||||
if (i.mac) details.push(i.mac.substring(0, 8) + '...');
|
||||
if (details.length > 0) label += ' - ' + details.join(' | ');
|
||||
label += ` (${i.type})`;
|
||||
if (i.monitor_capable) label += ' [Monitor OK]';
|
||||
return `<option value="${i.name}">${label}</option>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// Update tool status
|
||||
@@ -4839,9 +4621,6 @@
|
||||
const isNew = !wifiNetworks[net.bssid];
|
||||
wifiNetworks[net.bssid] = net;
|
||||
|
||||
// Track signal history for graphs
|
||||
trackDeviceSignal(net.bssid, net.power);
|
||||
|
||||
// Check if this reveals a hidden SSID
|
||||
if (net.essid && net.essid !== 'Hidden' && net.essid !== '[Hidden]') {
|
||||
revealHiddenSsid(net.bssid, net.essid);
|
||||
@@ -4890,9 +4669,6 @@
|
||||
const isNew = !wifiClients[client.mac];
|
||||
wifiClients[client.mac] = client;
|
||||
|
||||
// Track signal history for graphs
|
||||
trackDeviceSignal(client.mac, client.power);
|
||||
|
||||
if (isNew) {
|
||||
clientCount++;
|
||||
document.getElementById('clientCount').textContent = clientCount;
|
||||
@@ -5059,7 +4835,6 @@
|
||||
<button class="preset-btn" onclick="targetNetwork('${escapeAttr(net.bssid)}', '${escapeAttr(net.channel)}')" style="font-size: 10px; padding: 4px 8px;">Target</button>
|
||||
<button class="preset-btn" onclick="captureHandshake('${escapeAttr(net.bssid)}', '${escapeAttr(net.channel)}')" style="font-size: 10px; padding: 4px 8px; border-color: var(--accent-orange); color: var(--accent-orange);">4-Way</button>
|
||||
<button class="preset-btn pmkid-btn" onclick="capturePmkid('${escapeAttr(net.bssid)}', '${escapeAttr(net.channel)}')" style="font-size: 10px; padding: 4px 8px;">PMKID</button>
|
||||
<button class="preset-btn" onclick="setTrackedDevice('${escapeAttr(net.bssid)}', '${escapeAttr(net.essid || net.bssid)}')" style="font-size: 10px; padding: 4px 8px; border-color: var(--accent-cyan); color: var(--accent-cyan);" title="Track signal strength">📈</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -5963,10 +5738,6 @@
|
||||
}
|
||||
btDevices[device.mac] = device;
|
||||
|
||||
// Track signal history for graphs
|
||||
if (device.rssi) {
|
||||
trackBtDeviceSignal(device.mac, device.rssi);
|
||||
}
|
||||
|
||||
if (isNew) {
|
||||
btDeviceCount++;
|
||||
@@ -6062,12 +5833,11 @@
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// Select a BT device for signal tracking
|
||||
// Select a BT device for details
|
||||
function selectBtDevice(mac) {
|
||||
selectedBtDevice = mac;
|
||||
const device = btDevices[mac];
|
||||
if (device) {
|
||||
document.getElementById('btSignalGraphDevice').textContent = device.name || mac;
|
||||
document.getElementById('btTargetMac').value = mac;
|
||||
updateBtSelectedDevice(device);
|
||||
}
|
||||
@@ -6226,7 +5996,7 @@
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">Manufacturer</div>
|
||||
<div class="data-value">${escapeHtml(device.manufacturer)}</div>
|
||||
<div class="data-value">${escapeHtml(device.manufacturer || 'Unknown')}</div>
|
||||
</div>
|
||||
${device.findmy ? `
|
||||
<div class="data-item">
|
||||
@@ -8733,31 +8503,41 @@
|
||||
}
|
||||
|
||||
async function tuneToFrequency(freq, mod) {
|
||||
// Stop scanner if running
|
||||
if (isScannerRunning) {
|
||||
stopScanner();
|
||||
try {
|
||||
// Stop scanner if running
|
||||
if (isScannerRunning) {
|
||||
await fetch('/listening/scanner/stop', { method: 'POST' });
|
||||
isScannerRunning = false;
|
||||
document.getElementById('scannerStartBtn').textContent = '▶ Start Scanner';
|
||||
document.getElementById('scannerStartBtn').classList.remove('active');
|
||||
document.getElementById('scannerStatus').textContent = 'STOPPED';
|
||||
document.getElementById('scannerStatus').style.color = 'var(--text-muted)';
|
||||
}
|
||||
|
||||
// Stop any current audio and wait for it to complete
|
||||
if (isAudioPlaying) {
|
||||
await stopAudio();
|
||||
// Extra delay to ensure backend processes are fully stopped
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
}
|
||||
|
||||
// Set frequency in manual audio form
|
||||
document.getElementById('audioFrequency').value = freq.toFixed(3);
|
||||
document.getElementById('audioPreset').value = 'custom';
|
||||
if (mod) {
|
||||
document.getElementById('audioModulation').value = mod;
|
||||
}
|
||||
|
||||
// Small delay before starting to ensure backend is ready
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
|
||||
// Start playing
|
||||
startAudio();
|
||||
showNotification('Tuned', `Now listening to ${freq.toFixed(3)} MHz`);
|
||||
} catch (err) {
|
||||
console.error('Error tuning to frequency:', err);
|
||||
showNotification('Tune Error', 'Failed to tune to frequency: ' + err.message);
|
||||
}
|
||||
|
||||
// Stop any current audio and wait for it to complete
|
||||
if (isAudioPlaying) {
|
||||
stopAudio();
|
||||
// Wait for audio to fully stop
|
||||
await new Promise(resolve => setTimeout(resolve, 300));
|
||||
}
|
||||
|
||||
// Set frequency in manual audio form
|
||||
document.getElementById('audioFrequency').value = freq.toFixed(3);
|
||||
document.getElementById('audioPreset').value = 'custom';
|
||||
if (mod) {
|
||||
document.getElementById('audioModulation').value = mod;
|
||||
}
|
||||
|
||||
// Small delay before starting to ensure backend is ready
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Start playing
|
||||
startAudio();
|
||||
showNotification('Tuned', `Now listening to ${freq.toFixed(3)} MHz`);
|
||||
}
|
||||
|
||||
function skipSignal() {
|
||||
@@ -9154,7 +8934,7 @@
|
||||
});
|
||||
}
|
||||
|
||||
function stopAudio() {
|
||||
async function stopAudio() {
|
||||
// Stop visualizer
|
||||
stopAudioVisualizer();
|
||||
|
||||
@@ -9163,18 +8943,18 @@
|
||||
audioPlayer.pause();
|
||||
audioPlayer.src = '';
|
||||
|
||||
fetch('/listening/audio/stop', { method: 'POST' })
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
releaseDevice('audio');
|
||||
isAudioPlaying = false;
|
||||
document.getElementById('audioStartBtn').textContent = '▶ Play Audio';
|
||||
document.getElementById('audioStartBtn').classList.remove('active');
|
||||
document.getElementById('audioStatus').textContent = 'STOPPED';
|
||||
document.getElementById('audioStatus').style.color = 'var(--text-muted)';
|
||||
document.getElementById('audioDeviceStatus').textContent = '--';
|
||||
})
|
||||
.catch(() => {});
|
||||
try {
|
||||
await fetch('/listening/audio/stop', { method: 'POST' });
|
||||
releaseDevice('audio');
|
||||
isAudioPlaying = false;
|
||||
document.getElementById('audioStartBtn').textContent = '▶ Play Audio';
|
||||
document.getElementById('audioStartBtn').classList.remove('active');
|
||||
document.getElementById('audioStatus').textContent = 'STOPPED';
|
||||
document.getElementById('audioStatus').style.color = 'var(--text-muted)';
|
||||
document.getElementById('audioDeviceStatus').textContent = '--';
|
||||
} catch (e) {
|
||||
console.error('Error stopping audio:', e);
|
||||
}
|
||||
}
|
||||
|
||||
function updateAudioVolume() {
|
||||
|
||||
Reference in New Issue
Block a user