diff --git a/static/js/modes/bluetooth.js b/static/js/modes/bluetooth.js
index a15ccc7..9486d26 100644
--- a/static/js/modes/bluetooth.js
+++ b/static/js/modes/bluetooth.js
@@ -12,12 +12,27 @@ const BluetoothMode = (function() {
let devices = new Map();
let baselineSet = false;
let baselineCount = 0;
+ let selectedDeviceId = null;
// DOM elements (cached)
let startBtn, stopBtn, messageContainer, deviceContainer;
let adapterSelect, scanModeSelect, transportSelect, durationInput, minRssiInput;
let baselineStatusEl, capabilityStatusEl;
+ // Stats tracking
+ let deviceStats = {
+ phones: 0,
+ computers: 0,
+ audio: 0,
+ wearables: 0,
+ other: 0,
+ strong: 0,
+ medium: 0,
+ weak: 0,
+ trackers: [],
+ findmy: []
+ };
+
/**
* Initialize the Bluetooth mode
*/
@@ -37,18 +52,197 @@ const BluetoothMode = (function() {
baselineStatusEl = document.getElementById('btBaselineStatus');
capabilityStatusEl = document.getElementById('btCapabilityStatus');
- console.log('[BT] DOM elements:', {
- startBtn: !!startBtn,
- stopBtn: !!stopBtn,
- deviceContainer: !!deviceContainer,
- adapterSelect: !!adapterSelect
- });
+ // Create modal if it doesn't exist
+ createModal();
// Check capabilities on load
checkCapabilities();
// Check scan status (in case page was reloaded during scan)
checkScanStatus();
+
+ // Initialize radar canvas
+ initRadar();
+ }
+
+ /**
+ * Create the device details modal
+ */
+ function createModal() {
+ if (document.getElementById('btDeviceModal')) return;
+
+ const modal = document.createElement('div');
+ modal.id = 'btDeviceModal';
+ modal.style.cssText = 'display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.8);z-index:10000;align-items:center;justify-content:center;';
+ modal.innerHTML = `
+
+
+
Device Details
+
+
+
+
+ `;
+ modal.onclick = (e) => {
+ if (e.target === modal) closeModal();
+ };
+ document.body.appendChild(modal);
+ }
+
+ /**
+ * Show device details modal
+ */
+ function showModal(deviceId) {
+ const device = devices.get(deviceId);
+ if (!device) return;
+
+ selectedDeviceId = deviceId;
+ const modal = document.getElementById('btDeviceModal');
+ const title = document.getElementById('btModalTitle');
+ const body = document.getElementById('btModalBody');
+
+ title.textContent = device.name || formatDeviceId(device.address);
+
+ const rssi = device.rssi_current;
+ const rssiColor = getRssiColor(rssi);
+ const flags = device.heuristic_flags || [];
+
+ body.innerHTML = `
+
+
+ ${(device.protocol || 'ble').toUpperCase()}
+ ${flags.map(f => `${f.replace('_', ' ').toUpperCase()}`).join('')}
+ ${device.in_baseline ? '✓ In Baseline' : '● New Device'}
+
+
+
+
+
+
+
Signal Strength
+
${rssi !== null ? rssi : '--'}dBm
+
+
+
Range
+
${device.range_band || 'Unknown'}
+
+
+
+
+
Min
+
${device.rssi_min !== null ? device.rssi_min : '--'}
+
+
+
Max
+
${device.rssi_max !== null ? device.rssi_max : '--'}
+
+
+
Median
+
${device.rssi_median !== null ? device.rssi_median : '--'}
+
+
+
Confidence
+
${device.rssi_confidence ? Math.round(device.rssi_confidence * 100) + '%' : '--'}
+
+
+
+
+
+
+
+
Address
+
${device.address}
+
+
+
Address Type
+
${device.address_type || 'Unknown'}
+
+
+
Manufacturer
+
${device.manufacturer_name || 'Unknown'}
+
+
+
Manufacturer ID
+
${device.manufacturer_id !== null ? '0x' + device.manufacturer_id.toString(16).toUpperCase().padStart(4, '0') : '--'}
+
+
+
+
+
+
Observation Stats
+
+
+
First Seen
+
${device.first_seen ? new Date(device.first_seen).toLocaleTimeString() : '--'}
+
+
+
Last Seen
+
${device.last_seen ? new Date(device.last_seen).toLocaleTimeString() : '--'}
+
+
+
Seen Count
+
${device.seen_count || 0} times
+
+
+
+
+
+ ${device.service_uuids && device.service_uuids.length > 0 ? `
+
+
Service UUIDs
+
+ ${device.service_uuids.map(uuid => `${uuid}`).join('')}
+
+
+ ` : ''}
+
+
+
+
+
+
+ `;
+
+ modal.style.display = 'flex';
+ }
+
+ /**
+ * Close device details modal
+ */
+ function closeModal() {
+ const modal = document.getElementById('btDeviceModal');
+ if (modal) modal.style.display = 'none';
+ selectedDeviceId = null;
+ }
+
+ /**
+ * Copy address to clipboard
+ */
+ function copyAddress(address) {
+ navigator.clipboard.writeText(address).then(() => {
+ // Brief visual feedback
+ const btn = event.target;
+ const originalText = btn.textContent;
+ btn.textContent = 'Copied!';
+ btn.style.background = '#22c55e';
+ setTimeout(() => {
+ btn.textContent = originalText;
+ btn.style.background = '#252538';
+ }, 1500);
+ });
+ }
+
+ /**
+ * Format device ID for display (when no name available)
+ */
+ function formatDeviceId(address) {
+ if (!address) return 'Unknown Device';
+ // Return shortened format: first 2 and last 2 octets
+ const parts = address.split(':');
+ if (parts.length === 6) {
+ return parts[0] + ':' + parts[1] + ':...:' + parts[4] + ':' + parts[5];
+ }
+ return address;
}
/**
@@ -97,23 +291,14 @@ const BluetoothMode = (function() {
* Show capability warning
*/
function showCapabilityWarning(issues) {
- if (!capabilityStatusEl || !messageContainer) return;
+ if (!capabilityStatusEl) return;
capabilityStatusEl.style.display = 'block';
-
- if (typeof MessageCard !== 'undefined') {
- const card = MessageCard.createCapabilityWarning(issues);
- if (card) {
- capabilityStatusEl.innerHTML = '';
- capabilityStatusEl.appendChild(card);
- }
- } else {
- capabilityStatusEl.innerHTML = `
-
- ${issues.map(i => `
${i}
`).join('')}
-
- `;
- }
+ capabilityStatusEl.innerHTML = `
+
+ ${issues.map(i => `
⚠ ${i}
`).join('')}
+
+ `;
}
/**
@@ -179,7 +364,6 @@ const BluetoothMode = (function() {
if (data.status === 'started' || data.status === 'already_scanning') {
setScanning(true);
startEventStream();
- showScanningMessage(mode);
} else {
showErrorMessage(data.message || 'Failed to start scan');
}
@@ -198,7 +382,6 @@ const BluetoothMode = (function() {
await fetch('/api/bluetooth/scan/stop', { method: 'POST' });
setScanning(false);
stopEventStream();
- removeScanningMessage();
} catch (err) {
console.error('Failed to stop scan:', err);
}
@@ -213,10 +396,11 @@ const BluetoothMode = (function() {
if (startBtn) startBtn.style.display = scanning ? 'none' : 'block';
if (stopBtn) stopBtn.style.display = scanning ? 'block' : 'none';
- // Clear container when starting scan (removes legacy cards and placeholder)
+ // Clear container when starting scan
if (scanning && deviceContainer) {
deviceContainer.innerHTML = '';
- devices.clear(); // Also clear our device map
+ devices.clear();
+ resetStats();
}
// Update global status if available
@@ -226,6 +410,25 @@ const BluetoothMode = (function() {
if (statusText) statusText.textContent = scanning ? 'Scanning...' : 'Idle';
}
+ /**
+ * Reset stats
+ */
+ function resetStats() {
+ deviceStats = {
+ phones: 0,
+ computers: 0,
+ audio: 0,
+ wearables: 0,
+ other: 0,
+ strong: 0,
+ medium: 0,
+ weak: 0,
+ trackers: [],
+ findmy: []
+ };
+ updateVisualizationPanels();
+ }
+
/**
* Start SSE event stream
*/
@@ -233,10 +436,8 @@ const BluetoothMode = (function() {
if (eventSource) eventSource.close();
eventSource = new EventSource('/api/bluetooth/stream');
- console.log('[BT] SSE stream connected');
eventSource.addEventListener('device_update', (e) => {
- console.log('[BT] SSE device_update event:', e.data);
try {
const device = JSON.parse(e.data);
handleDeviceUpdate(device);
@@ -245,31 +446,14 @@ const BluetoothMode = (function() {
}
});
- // Also listen for generic messages as fallback
- eventSource.onmessage = (e) => {
- console.log('[BT] SSE generic message:', e.data);
- };
-
eventSource.addEventListener('scan_started', (e) => {
const data = JSON.parse(e.data);
setScanning(true);
- showScanningMessage(data.mode);
});
eventSource.addEventListener('scan_stopped', (e) => {
setScanning(false);
- removeScanningMessage();
const data = JSON.parse(e.data);
- showScanCompleteMessage(data.device_count, data.duration);
- });
-
- eventSource.addEventListener('error', (e) => {
- try {
- const data = JSON.parse(e.data);
- showErrorMessage(data.message);
- } catch {
- // Connection error
- }
});
eventSource.onerror = () => {
@@ -291,10 +475,237 @@ const BluetoothMode = (function() {
* Handle device update from SSE
*/
function handleDeviceUpdate(device) {
- console.log('[BT] Device update received:', device);
devices.set(device.device_id, device);
renderDevice(device);
updateDeviceCount();
+ updateStatsFromDevice(device);
+ updateVisualizationPanels();
+ updateRadar();
+ }
+
+ /**
+ * Update stats from device
+ */
+ function updateStatsFromDevice(device) {
+ // Categorize by manufacturer/type
+ const mfr = (device.manufacturer_name || '').toLowerCase();
+ const name = (device.name || '').toLowerCase();
+
+ // Reset counts and recalculate from all devices
+ deviceStats.phones = 0;
+ deviceStats.computers = 0;
+ deviceStats.audio = 0;
+ deviceStats.wearables = 0;
+ deviceStats.other = 0;
+ deviceStats.strong = 0;
+ deviceStats.medium = 0;
+ deviceStats.weak = 0;
+ deviceStats.trackers = [];
+ deviceStats.findmy = [];
+
+ devices.forEach(d => {
+ const m = (d.manufacturer_name || '').toLowerCase();
+ const n = (d.name || '').toLowerCase();
+ const rssi = d.rssi_current;
+
+ // Device type classification
+ if (n.includes('iphone') || n.includes('phone') || n.includes('pixel') || n.includes('galaxy') || n.includes('android')) {
+ deviceStats.phones++;
+ } else if (n.includes('macbook') || n.includes('laptop') || n.includes('pc') || n.includes('computer') || n.includes('imac')) {
+ deviceStats.computers++;
+ } else if (n.includes('airpod') || n.includes('headphone') || n.includes('speaker') || n.includes('buds') || n.includes('audio') || n.includes('beats')) {
+ deviceStats.audio++;
+ } else if (n.includes('watch') || n.includes('band') || n.includes('fitbit') || n.includes('garmin')) {
+ deviceStats.wearables++;
+ } else {
+ deviceStats.other++;
+ }
+
+ // Signal strength classification
+ if (rssi !== null && rssi !== undefined) {
+ if (rssi >= -50) deviceStats.strong++;
+ else if (rssi >= -70) deviceStats.medium++;
+ else deviceStats.weak++;
+ }
+
+ // Tracker detection (Apple, Tile, etc.)
+ if (m.includes('apple') && (d.heuristic_flags || []).includes('beacon_like')) {
+ if (!deviceStats.findmy.find(t => t.address === d.address)) {
+ deviceStats.findmy.push(d);
+ }
+ }
+ if (n.includes('tile') || n.includes('airtag') || n.includes('smarttag')) {
+ if (!deviceStats.trackers.find(t => t.address === d.address)) {
+ deviceStats.trackers.push(d);
+ }
+ }
+ });
+ }
+
+ /**
+ * Update visualization panels
+ */
+ function updateVisualizationPanels() {
+ // Device Types
+ const phoneCount = document.getElementById('btPhoneCount');
+ const computerCount = document.getElementById('btComputerCount');
+ const audioCount = document.getElementById('btAudioCount');
+ const wearableCount = document.getElementById('btWearableCount');
+ const otherCount = document.getElementById('btOtherCount');
+
+ if (phoneCount) phoneCount.textContent = deviceStats.phones;
+ if (computerCount) computerCount.textContent = deviceStats.computers;
+ if (audioCount) audioCount.textContent = deviceStats.audio;
+ if (wearableCount) wearableCount.textContent = deviceStats.wearables;
+ if (otherCount) otherCount.textContent = deviceStats.other;
+
+ // Signal Distribution
+ const total = devices.size || 1;
+ const strongBar = document.getElementById('btSignalStrong');
+ const mediumBar = document.getElementById('btSignalMedium');
+ const weakBar = document.getElementById('btSignalWeak');
+ const strongCount = document.getElementById('btSignalStrongCount');
+ const mediumCount = document.getElementById('btSignalMediumCount');
+ const weakCount = document.getElementById('btSignalWeakCount');
+
+ if (strongBar) strongBar.style.width = (deviceStats.strong / total * 100) + '%';
+ if (mediumBar) mediumBar.style.width = (deviceStats.medium / total * 100) + '%';
+ if (weakBar) weakBar.style.width = (deviceStats.weak / total * 100) + '%';
+ if (strongCount) strongCount.textContent = deviceStats.strong;
+ if (mediumCount) mediumCount.textContent = deviceStats.medium;
+ if (weakCount) weakCount.textContent = deviceStats.weak;
+
+ // Tracker Detection
+ const trackerList = document.getElementById('btTrackerList');
+ if (trackerList) {
+ if (deviceStats.trackers.length === 0) {
+ trackerList.innerHTML = 'No trackers detected
';
+ } else {
+ trackerList.innerHTML = deviceStats.trackers.map(t => `
+
+
+ ${t.name || formatDeviceId(t.address)}
+ ${t.rssi_current || '--'} dBm
+
+
${t.address}
+
+ `).join('');
+ }
+ }
+
+ // FindMy Detection
+ const findmyList = document.getElementById('btFindMyList');
+ if (findmyList) {
+ if (deviceStats.findmy.length === 0) {
+ findmyList.innerHTML = 'No FindMy devices detected
';
+ } else {
+ findmyList.innerHTML = deviceStats.findmy.map(t => `
+
+
+ ${t.name || 'Apple Device'}
+ ${t.rssi_current || '--'} dBm
+
+
${t.address}
+
+ `).join('');
+ }
+ }
+ }
+
+ /**
+ * Initialize radar canvas
+ */
+ function initRadar() {
+ const canvas = document.getElementById('btRadarCanvas');
+ if (!canvas) return;
+
+ const ctx = canvas.getContext('2d');
+ drawRadarBackground(ctx, canvas.width, canvas.height);
+ }
+
+ /**
+ * Draw radar background
+ */
+ function drawRadarBackground(ctx, width, height) {
+ const centerX = width / 2;
+ const centerY = height / 2;
+ const maxRadius = Math.min(width, height) / 2 - 10;
+
+ ctx.clearRect(0, 0, width, height);
+
+ // Draw concentric circles
+ ctx.strokeStyle = 'rgba(0, 212, 255, 0.2)';
+ ctx.lineWidth = 1;
+ for (let i = 1; i <= 4; i++) {
+ ctx.beginPath();
+ ctx.arc(centerX, centerY, maxRadius * i / 4, 0, Math.PI * 2);
+ ctx.stroke();
+ }
+
+ // Draw cross lines
+ ctx.beginPath();
+ ctx.moveTo(centerX, 10);
+ ctx.lineTo(centerX, height - 10);
+ ctx.moveTo(10, centerY);
+ ctx.lineTo(width - 10, centerY);
+ ctx.stroke();
+
+ // Center dot
+ ctx.fillStyle = '#00d4ff';
+ ctx.beginPath();
+ ctx.arc(centerX, centerY, 3, 0, Math.PI * 2);
+ ctx.fill();
+ }
+
+ /**
+ * Update radar with device positions
+ */
+ function updateRadar() {
+ const canvas = document.getElementById('btRadarCanvas');
+ if (!canvas) return;
+
+ const ctx = canvas.getContext('2d');
+ const width = canvas.width;
+ const height = canvas.height;
+ const centerX = width / 2;
+ const centerY = height / 2;
+ const maxRadius = Math.min(width, height) / 2 - 15;
+
+ // Redraw background
+ drawRadarBackground(ctx, width, height);
+
+ // Plot devices
+ let angle = 0;
+ const angleStep = (Math.PI * 2) / Math.max(devices.size, 1);
+
+ devices.forEach(device => {
+ const rssi = device.rssi_current;
+ if (rssi === null || rssi === undefined) return;
+
+ // Convert RSSI to distance (closer = smaller radius)
+ // RSSI typically ranges from -30 (very close) to -100 (far)
+ const normalizedRssi = Math.max(0, Math.min(1, (rssi + 100) / 70));
+ const radius = maxRadius * (1 - normalizedRssi);
+
+ const x = centerX + Math.cos(angle) * radius;
+ const y = centerY + Math.sin(angle) * radius;
+
+ // Color based on signal strength
+ const color = getRssiColor(rssi);
+
+ ctx.fillStyle = color;
+ ctx.beginPath();
+ ctx.arc(x, y, 4, 0, Math.PI * 2);
+ ctx.fill();
+
+ // Glow effect
+ ctx.fillStyle = color.replace(')', ', 0.3)').replace('rgb', 'rgba');
+ ctx.beginPath();
+ ctx.arc(x, y, 8, 0, Math.PI * 2);
+ ctx.fill();
+
+ angle += angleStep;
+ });
}
/**
@@ -311,35 +722,24 @@ const BluetoothMode = (function() {
* Render a device card
*/
function renderDevice(device) {
- console.log('[BT] Rendering device:', device.device_id, device);
if (!deviceContainer) {
deviceContainer = document.getElementById('btDeviceListContent');
- if (!deviceContainer) {
- console.error('[BT] No container - cannot render');
- return;
- }
+ if (!deviceContainer) return;
}
- // Use simple inline rendering with NO CSS classes to avoid any interference
const escapedId = CSS.escape(device.device_id);
const existingCard = deviceContainer.querySelector('[data-bt-device-id="' + escapedId + '"]');
const cardHtml = createSimpleDeviceCard(device);
- console.log('[BT] Card HTML length:', cardHtml.length, 'existing:', !!existingCard);
-
if (existingCard) {
existingCard.outerHTML = cardHtml;
} else {
deviceContainer.insertAdjacentHTML('afterbegin', cardHtml);
}
-
- // Log container state
- console.log('[BT] Container now has', deviceContainer.children.length, 'children');
}
/**
- * Simple device card - pure inline rendering with NO CSS classes
- * This avoids any CSS conflicts by using only inline styles
+ * Simple device card with click handler
*/
function createSimpleDeviceCard(device) {
const protocol = device.protocol || 'ble';
@@ -356,7 +756,9 @@ const BluetoothMode = (function() {
badgesHtml += 'PERSISTENT';
}
- const name = escapeHtml(device.name || device.device_id || 'Unknown');
+ // Use device name if available, otherwise format the address nicely
+ const displayName = device.name || formatDeviceId(device.address);
+ const name = escapeHtml(displayName);
const addr = escapeHtml(device.address || 'Unknown');
const addrType = escapeHtml(device.address_type || 'unknown');
const rssi = device.rssi_current;
@@ -367,19 +769,20 @@ const BluetoothMode = (function() {
const rangeBand = device.range_band || 'unknown';
const inBaseline = device.in_baseline || false;
- // Use a div with NO classes at all - pure inline styles to avoid any CSS interference
- const cardStyle = 'display:block;background:#1a1a2e;border:1px solid #444;border-radius:8px;padding:14px;margin-bottom:10px;box-sizing:border-box;overflow:visible;';
+ const cardStyle = 'display:block;background:#1a1a2e;border:1px solid #444;border-radius:8px;padding:14px;margin-bottom:10px;cursor:pointer;transition:border-color 0.2s;';
const headerStyle = 'display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;';
- const nameStyle = 'font-size:15px;font-weight:600;color:#e0e0e0;margin-bottom:4px;';
- const addrStyle = 'font-family:monospace;font-size:12px;color:#00d4ff;';
- const rssiRowStyle = 'display:flex;justify-content:space-between;align-items:center;background:#141428;padding:12px;border-radius:6px;margin:10px 0;';
- const rssiValueStyle = 'font-family:monospace;font-size:18px;font-weight:700;color:' + rssiColor + ';';
- const rangeBandStyle = 'font-size:11px;color:#888;text-transform:uppercase;letter-spacing:0.5px;';
- const mfrStyle = 'font-size:11px;color:#888;margin-bottom:8px;';
+ const nameStyle = 'font-size:14px;font-weight:600;color:#e0e0e0;margin-bottom:4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;';
+ const addrStyle = 'font-family:monospace;font-size:11px;color:#00d4ff;';
+ const rssiRowStyle = 'display:flex;justify-content:space-between;align-items:center;background:#141428;padding:10px;border-radius:6px;margin:10px 0;';
+ const rssiValueStyle = 'font-family:monospace;font-size:16px;font-weight:700;color:' + rssiColor + ';';
+ const rangeBandStyle = 'font-size:10px;color:#888;text-transform:uppercase;letter-spacing:0.5px;';
+ const mfrStyle = 'font-size:11px;color:#888;margin-bottom:6px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;';
const metaStyle = 'display:flex;justify-content:space-between;font-size:10px;color:#666;';
const statusPillStyle = 'background:' + (inBaseline ? 'rgba(34,197,94,0.15)' : 'rgba(59,130,246,0.15)') + ';color:' + (inBaseline ? '#22c55e' : '#3b82f6') + ';padding:3px 10px;border-radius:12px;font-size:10px;font-weight:500;';
- return '' +
+ const deviceIdEscaped = escapeHtml(device.device_id).replace(/'/g, "\\'");
+
+ return '
' +
'
' +
'
' + protoBadge + badgesHtml + '
' +
'
' + (inBaseline ? '✓ Known' : '● New') + '' +
@@ -392,9 +795,9 @@ const BluetoothMode = (function() {
'
' + rssiStr + '' +
'
' + rangeBand + '' +
'
' +
- (mfr ? '
Manufacturer: ' + mfr + '
' : '') +
+ (mfr ? '
' + mfr + '
' : '') +
'
' +
- 'Seen: ' + seenCount + ' times' +
+ 'Seen ' + seenCount + '×' +
'Just now' +
'
' +
'
';
@@ -422,30 +825,6 @@ const BluetoothMode = (function() {
return div.innerHTML;
}
- /**
- * Show device details
- */
- async function showDeviceDetails(deviceId) {
- try {
- const response = await fetch(`/api/bluetooth/devices/${encodeURIComponent(deviceId)}`);
- const device = await response.json();
-
- // Toggle advanced panel or show modal
- const card = deviceContainer?.querySelector(`[data-device-id="${deviceId}"]`);
- if (card) {
- const panel = card.querySelector('.signal-advanced-panel');
- if (panel) {
- panel.classList.toggle('show');
- if (panel.classList.contains('show')) {
- panel.innerHTML = `
${JSON.stringify(device, null, 2)}`;
- }
- }
- }
- } catch (err) {
- console.error('Failed to get device details:', err);
- }
- }
-
/**
* Set baseline
*/
@@ -458,13 +837,9 @@ const BluetoothMode = (function() {
baselineSet = true;
baselineCount = data.device_count;
updateBaselineStatus();
- showBaselineSetMessage(data.device_count);
- } else {
- showErrorMessage(data.message || 'Failed to set baseline');
}
} catch (err) {
console.error('Failed to set baseline:', err);
- showErrorMessage('Failed to set baseline');
}
}
@@ -493,10 +868,10 @@ const BluetoothMode = (function() {
if (!baselineStatusEl) return;
if (baselineSet) {
- baselineStatusEl.textContent = `Baseline set: ${baselineCount} device${baselineCount !== 1 ? 's' : ''}`;
+ baselineStatusEl.textContent = `Baseline: ${baselineCount} devices`;
baselineStatusEl.style.color = '#22c55e';
} else {
- baselineStatusEl.textContent = 'No baseline set';
+ baselineStatusEl.textContent = 'No baseline';
baselineStatusEl.style.color = '';
}
}
@@ -508,55 +883,12 @@ const BluetoothMode = (function() {
window.open(`/api/bluetooth/export?format=${format}`, '_blank');
}
- /**
- * Show scanning message
- */
- function showScanningMessage(mode) {
- if (!messageContainer || typeof MessageCard === 'undefined') return;
-
- removeScanningMessage();
- const card = MessageCard.createScanningCard({
- backend: mode,
- deviceCount: devices.size
- });
- messageContainer.appendChild(card);
- }
-
- /**
- * Remove scanning message
- */
- function removeScanningMessage() {
- MessageCard?.removeMessage?.('btScanningStatus');
- }
-
- /**
- * Show scan complete message
- */
- function showScanCompleteMessage(deviceCount, duration) {
- if (!messageContainer || typeof MessageCard === 'undefined') return;
-
- const card = MessageCard.createScanCompleteCard(deviceCount, duration || 0);
- messageContainer.appendChild(card);
- }
-
- /**
- * Show baseline set message
- */
- function showBaselineSetMessage(count) {
- if (!messageContainer || typeof MessageCard === 'undefined') return;
-
- const card = MessageCard.createBaselineCard(count, true);
- messageContainer.appendChild(card);
- }
-
/**
* Show error message
*/
function showErrorMessage(message) {
- if (!messageContainer || typeof MessageCard === 'undefined') return;
-
- const card = MessageCard.createErrorCard(message, () => startScan());
- messageContainer.appendChild(card);
+ console.error('[BT] Error:', message);
+ // Could show a toast notification here
}
// Public API
@@ -568,6 +900,9 @@ const BluetoothMode = (function() {
setBaseline,
clearBaseline,
exportData,
+ showModal,
+ closeModal,
+ copyAddress,
getDevices: () => Array.from(devices.values()),
isScanning: () => isScanning
};
@@ -584,7 +919,6 @@ function btExport(format) { BluetoothMode.exportData(format); }
// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
- // Only init if we're on a page with Bluetooth mode
if (document.getElementById('bluetoothMode')) {
BluetoothMode.init();
}