diff --git a/static/css/components/device-cards.css b/static/css/components/device-cards.css index 0489abd..26bdf07 100644 --- a/static/css/components/device-cards.css +++ b/static/css/components/device-cards.css @@ -555,27 +555,32 @@ } /* ============================================ - DEVICE CARD GRID LAYOUT + BLUETOOTH DEVICE LIST CONTAINER ============================================ */ #btDeviceListContent { - display: grid !important; - grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)) !important; - grid-auto-rows: auto !important; - gap: 12px !important; - padding: 12px !important; - align-items: start !important; + display: block !important; + padding: 10px !important; + overflow-y: auto !important; + overflow-x: hidden !important; } -#btDeviceListContent > * { - min-height: auto !important; +/* Pure inline-styled cards - ensure no interference */ +#btDeviceListContent > div[data-bt-device-id] { + display: block !important; + visibility: visible !important; + opacity: 1 !important; height: auto !important; + min-height: auto !important; + overflow: visible !important; } +/* Legacy card support */ #btDeviceListContent .device-card, #btDeviceListContent .signal-card { - margin: 0; + margin: 0 0 10px 0; height: auto !important; min-height: auto !important; + overflow: visible !important; } /* Ensure card body is visible */ diff --git a/static/js/modes/bluetooth.js b/static/js/modes/bluetooth.js index eee2e27..a15ccc7 100644 --- a/static/js/modes/bluetooth.js +++ b/static/js/modes/bluetooth.js @@ -311,76 +311,93 @@ const BluetoothMode = (function() { * Render a device card */ function renderDevice(device) { - console.log('[BT] Rendering device:', device.device_id, 'Container:', deviceContainer); + console.log('[BT] Rendering device:', device.device_id, device); if (!deviceContainer) { - console.warn('[BT] No device container found!'); - // Try to find it again deviceContainer = document.getElementById('btDeviceListContent'); if (!deviceContainer) { - console.error('[BT] Still no container - cannot render'); + console.error('[BT] No container - cannot render'); return; } } - const existingCard = deviceContainer.querySelector(`[data-device-id="${device.device_id}"]`); + // 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); - if (typeof DeviceCard !== 'undefined') { - // DeviceCard.createDeviceCard returns a DOM element - const cardElement = DeviceCard.createDeviceCard(device); + console.log('[BT] Card HTML length:', cardHtml.length, 'existing:', !!existingCard); - if (existingCard) { - existingCard.replaceWith(cardElement); - } else { - deviceContainer.prepend(cardElement); - } - - // Attach click handler - cardElement.addEventListener('click', () => showDeviceDetails(device.device_id)); + if (existingCard) { + existingCard.outerHTML = cardHtml; } else { - // Fallback simple rendering (returns HTML string) - const cardHtml = createSimpleDeviceCard(device); - - if (existingCard) { - existingCard.outerHTML = cardHtml; - } else { - deviceContainer.insertAdjacentHTML('afterbegin', cardHtml); - } + deviceContainer.insertAdjacentHTML('afterbegin', cardHtml); } + + // Log container state + console.log('[BT] Container now has', deviceContainer.children.length, 'children'); } /** - * Simple device card fallback + * Simple device card - pure inline rendering with NO CSS classes + * This avoids any CSS conflicts by using only inline styles */ function createSimpleDeviceCard(device) { - const protoBadge = device.protocol === 'ble' - ? 'BLE' - : 'CLASSIC'; + const protocol = device.protocol || 'ble'; + const protoBadge = protocol === 'ble' + ? 'BLE' + : 'CLASSIC'; - const badges = []; - if (device.is_new) badges.push('New'); - if (device.is_persistent) badges.push('Persistent'); - if (device.is_beacon_like) badges.push('Beacon-like'); + const flags = device.heuristic_flags || []; + let badgesHtml = ''; + if (flags.includes('random_address')) { + badgesHtml += 'RANDOM'; + } + if (flags.includes('persistent')) { + badgesHtml += 'PERSISTENT'; + } - const rssiColor = getRssiColor(device.rssi_current); + const name = escapeHtml(device.name || device.device_id || 'Unknown'); + const addr = escapeHtml(device.address || 'Unknown'); + const addrType = escapeHtml(device.address_type || 'unknown'); + const rssi = device.rssi_current; + const rssiStr = (rssi !== null && rssi !== undefined) ? rssi + ' dBm' : '--'; + const rssiColor = getRssiColor(rssi); + const mfr = device.manufacturer_name ? escapeHtml(device.manufacturer_name) : ''; + const seenCount = device.seen_count || 0; + const rangeBand = device.range_band || 'unknown'; + const inBaseline = device.in_baseline || false; - return ` -
-
-
- ${protoBadge} - ${badges.join('')} -
-
-
-
${escapeHtml(device.name || 'Unknown Device')}
-
${escapeHtml(device.address)} (${device.address_type || 'unknown'})
-
- ${device.rssi_current !== null ? device.rssi_current + ' dBm' : '--'} -
- ${device.manufacturer_name ? `
${escapeHtml(device.manufacturer_name)}
` : ''} -
-
- `; + // 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 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 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 '
' + + '
' + + '
' + protoBadge + badgesHtml + '
' + + '' + (inBaseline ? '✓ Known' : '● New') + '' + + '
' + + '
' + + '
' + name + '
' + + '
' + addr + ' (' + addrType + ')
' + + '
' + + '
' + + '' + rssiStr + '' + + '' + rangeBand + '' + + '
' + + (mfr ? '
Manufacturer: ' + mfr + '
' : '') + + '
' + + 'Seen: ' + seenCount + ' times' + + 'Just now' + + '
' + + '
'; } /**