Improve Bluetooth scanner filtering, stats, and layout

This commit is contained in:
Smittix
2026-02-19 14:04:12 +00:00
parent 02a94281c3
commit bbc25ddaa0
5 changed files with 492 additions and 167 deletions
+246 -8
View File
@@ -4060,8 +4060,8 @@ header h1 .tagline {
/* Bluetooth Layout Container */ /* Bluetooth Layout Container */
.bt-layout-container { .bt-layout-container {
display: flex; display: flex;
gap: 15px; gap: 12px;
padding: 15px; padding: 12px;
background: var(--bg-secondary); background: var(--bg-secondary);
margin: 0 15px 10px 15px; margin: 0 15px 10px 15px;
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
@@ -4082,14 +4082,14 @@ header h1 .tagline {
display: flex; display: flex;
gap: 12px; gap: 12px;
flex: 1; flex: 1;
min-height: 360px; /* ensure radar area (SVG 280px + controls + summary) is never crushed */ min-height: 380px;
} }
.bt-side-panels { .bt-side-panels {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 12px; gap: 12px;
width: 220px; width: 240px;
flex-shrink: 0; flex-shrink: 0;
} }
@@ -4107,6 +4107,90 @@ header h1 .tagline {
flex-direction: column; flex-direction: column;
} }
#btRadarControls {
gap: 6px;
}
.bt-radar-filter-btn,
#btRadarPauseBtn {
min-width: 84px;
padding: 4px 10px;
font-size: 10px;
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
border-radius: 4px;
color: var(--text-dim);
cursor: pointer;
transition: all 0.2s ease;
}
.bt-radar-filter-btn:hover,
#btRadarPauseBtn:hover {
color: var(--text-primary);
border-color: var(--accent-cyan);
}
.bt-radar-filter-btn.active,
#btRadarPauseBtn.active {
color: #0f172a;
background: var(--accent-cyan);
border-color: var(--accent-cyan);
}
.bt-zone-summary {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 8px;
margin-top: 12px;
font-size: 11px;
}
.bt-zone-card {
text-align: center;
border-radius: 6px;
padding: 8px 6px;
background: var(--bg-secondary);
border: 1px solid var(--border-color);
}
.bt-zone-card.immediate {
border-color: rgba(34, 197, 94, 0.35);
}
.bt-zone-card.near {
border-color: rgba(234, 179, 8, 0.35);
}
.bt-zone-card.far {
border-color: rgba(239, 68, 68, 0.35);
}
.bt-zone-value {
display: block;
font-size: 19px;
font-weight: 700;
}
.bt-zone-card.immediate .bt-zone-value {
color: #22c55e;
}
.bt-zone-card.near .bt-zone-value {
color: #eab308;
}
.bt-zone-card.far .bt-zone-value {
color: #ef4444;
}
.bt-zone-label {
color: var(--text-dim);
font-size: 10px;
margin-top: 3px;
text-transform: uppercase;
letter-spacing: 0.4px;
}
.bt-radar-panel #btProximityRadar { .bt-radar-panel #btProximityRadar {
flex: 1; flex: 1;
min-height: 0; min-height: 0;
@@ -4248,6 +4332,70 @@ header h1 .tagline {
color: #9ca3af; color: #9ca3af;
} }
.bt-detail-badge.tracker-high {
background: rgba(239, 68, 68, 0.2);
color: #ef4444;
}
.bt-detail-badge.tracker-medium {
background: rgba(249, 115, 22, 0.2);
color: #f97316;
}
.bt-detail-badge.tracker-low {
background: rgba(234, 179, 8, 0.2);
color: #eab308;
}
.bt-detail-tracker-analysis {
background: rgba(239, 68, 68, 0.08);
border: 1px solid rgba(239, 68, 68, 0.25);
border-radius: 6px;
padding: 8px 10px;
margin-bottom: 8px;
}
.bt-analysis-header {
color: #fca5a5;
font-size: 10px;
font-weight: 700;
letter-spacing: 0.35px;
text-transform: uppercase;
margin-bottom: 6px;
}
.bt-analysis-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
font-size: 10px;
margin-top: 4px;
}
.bt-analysis-label {
color: var(--text-dim);
font-size: 9px;
}
.bt-analysis-section {
margin-top: 6px;
}
.bt-evidence-list {
margin: 4px 0 0 0;
padding-left: 14px;
color: var(--text-primary);
font-size: 10px;
}
.bt-analysis-warning {
margin-top: 8px;
color: #fca5a5;
font-size: 9px;
line-height: 1.35;
}
.bt-detail-grid { .bt-detail-grid {
display: grid; display: grid;
grid-template-columns: repeat(4, 1fr); grid-template-columns: repeat(4, 1fr);
@@ -4283,6 +4431,8 @@ header h1 .tagline {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
gap: 6px;
flex-wrap: wrap;
} }
.bt-detail-services { .bt-detail-services {
@@ -4438,8 +4588,8 @@ header h1 .tagline {
border-left-color: var(--accent-purple) !important; border-left-color: var(--accent-purple) !important;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-width: 280px; min-width: 330px;
max-width: 320px; max-width: 420px;
max-height: 100%; max-height: 100%;
background: var(--bg-primary); background: var(--bg-primary);
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
@@ -4451,6 +4601,8 @@ header h1 .tagline {
flex: 1; flex: 1;
overflow-y: auto; overflow-y: auto;
min-height: 0; min-height: 0;
padding: 8px 10px 12px;
background: var(--bg-primary);
} }
.bt-device-list .wifi-device-list-header { .bt-device-list .wifi-device-list-header {
@@ -4460,6 +4612,10 @@ header h1 .tagline {
padding: 10px 12px; padding: 10px 12px;
border-bottom: 1px solid var(--border-color); border-bottom: 1px solid var(--border-color);
flex-shrink: 0; flex-shrink: 0;
position: sticky;
top: 0;
z-index: 4;
background: var(--bg-primary);
} }
.bt-device-list .wifi-device-list-header h5 { .bt-device-list .wifi-device-list-header h5 {
@@ -4469,6 +4625,63 @@ header h1 .tagline {
font-weight: 600; font-weight: 600;
} }
.bt-list-summary {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 6px;
padding: 8px 12px;
border-bottom: 1px solid var(--border-color);
background: var(--bg-primary);
}
.bt-summary-item {
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 5px 6px;
min-width: 0;
}
.bt-summary-label {
display: block;
font-size: 8px;
color: var(--text-dim);
text-transform: uppercase;
letter-spacing: 0.35px;
}
.bt-summary-value {
display: block;
font-size: 11px;
font-weight: 700;
color: var(--text-primary);
margin-top: 2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.bt-device-toolbar {
padding: 8px 12px;
border-bottom: 1px solid var(--border-color);
background: var(--bg-primary);
}
.bt-device-search {
width: 100%;
border: 1px solid var(--border-color);
border-radius: 4px;
background: var(--bg-tertiary);
color: var(--text-primary);
font-size: 11px;
padding: 7px 8px;
}
.bt-device-search:focus {
outline: none;
border-color: var(--accent-cyan);
}
/* Bluetooth Device Filters */ /* Bluetooth Device Filters */
.bt-device-filters { .bt-device-filters {
display: flex; display: flex;
@@ -4477,6 +4690,10 @@ header h1 .tagline {
border-bottom: 1px solid var(--border-color); border-bottom: 1px solid var(--border-color);
flex-wrap: wrap; flex-wrap: wrap;
flex-shrink: 0; flex-shrink: 0;
background: var(--bg-primary);
position: sticky;
top: 44px;
z-index: 3;
} }
.bt-filter-btn { .bt-filter-btn {
@@ -4501,6 +4718,14 @@ header h1 .tagline {
color: white; color: white;
} }
.bt-tracker-item {
transition: background 0.15s ease;
}
.bt-tracker-item:hover {
background: rgba(239, 68, 68, 0.08);
}
/* Bluetooth Signal Distribution */ /* Bluetooth Signal Distribution */
.bt-signal-dist { .bt-signal-dist {
display: flex; display: flex;
@@ -4570,6 +4795,10 @@ header h1 .tagline {
transition: all 0.15s ease; transition: all 0.15s ease;
} }
.bt-device-row:last-child {
margin-bottom: 0;
}
.bt-device-row:hover { .bt-device-row:hover {
background: rgba(0, 212, 255, 0.05); background: rgba(0, 212, 255, 0.05);
border-color: var(--accent-cyan); border-color: var(--accent-cyan);
@@ -4922,14 +5151,23 @@ header h1 .tagline {
min-height: 0; min-height: 0;
} }
.bt-layout-container .wifi-visuals { .bt-layout-container .bt-visuals-column {
max-height: 50vh; max-height: 50vh;
} }
.bt-main-area {
min-height: 0;
}
.bt-device-list { .bt-device-list {
width: 100%; width: 100%;
min-width: auto; min-width: auto;
max-height: 300px; max-width: none;
max-height: 320px;
}
.bt-list-summary {
grid-template-columns: repeat(2, minmax(0, 1fr));
} }
} }
+30 -2
View File
@@ -428,7 +428,7 @@
/* Visual panels should be scrollable, not clipped */ /* Visual panels should be scrollable, not clipped */
.wifi-visuals, .wifi-visuals,
.bt-visuals { .bt-visuals-column {
max-height: none !important; max-height: none !important;
overflow: visible !important; overflow: visible !important;
margin-bottom: 15px; margin-bottom: 15px;
@@ -444,7 +444,7 @@
/* Visual panels should stack in single column on mobile when visible */ /* Visual panels should stack in single column on mobile when visible */
.wifi-visuals, .wifi-visuals,
.bt-visuals { .bt-visuals-column {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 10px; gap: 10px;
@@ -465,6 +465,34 @@
.wifi-visual-panel { .wifi-visual-panel {
grid-column: auto !important; grid-column: auto !important;
} }
.bt-main-area {
flex-direction: column !important;
min-height: auto !important;
}
.bt-side-panels {
width: 100% !important;
flex-direction: column !important;
}
.bt-detail-grid {
grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
}
.bt-row-secondary {
padding-left: 0 !important;
white-space: normal !important;
}
.bt-row-actions {
padding-left: 0 !important;
justify-content: flex-start !important;
}
.bt-list-summary {
grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
}
} }
/* ============== MOBILE MAP FIXES ============== */ /* ============== MOBILE MAP FIXES ============== */
+79 -44
View File
@@ -28,7 +28,7 @@ const BluetoothMode = (function() {
}; };
// Zone counts for proximity display // Zone counts for proximity display
let zoneCounts = { veryClose: 0, close: 0, nearby: 0, far: 0 }; let zoneCounts = { immediate: 0, near: 0, far: 0 };
// New visualization components // New visualization components
let radarInitialized = false; let radarInitialized = false;
@@ -36,6 +36,8 @@ const BluetoothMode = (function() {
// Device list filter // Device list filter
let currentDeviceFilter = 'all'; let currentDeviceFilter = 'all';
let currentSearchTerm = '';
let visibleDeviceCount = 0;
// Agent support // Agent support
let showAllAgentsMode = false; let showAllAgentsMode = false;
@@ -121,23 +123,31 @@ const BluetoothMode = (function() {
*/ */
function initDeviceFilters() { function initDeviceFilters() {
const filterContainer = document.getElementById('btDeviceFilters'); const filterContainer = document.getElementById('btDeviceFilters');
if (!filterContainer) return; if (filterContainer) {
filterContainer.addEventListener('click', (e) => {
const btn = e.target.closest('.bt-filter-btn');
if (!btn) return;
filterContainer.addEventListener('click', (e) => { const filter = btn.dataset.filter;
const btn = e.target.closest('.bt-filter-btn'); if (!filter) return;
if (!btn) return;
const filter = btn.dataset.filter; // Update active state
if (!filter) return; filterContainer.querySelectorAll('.bt-filter-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
// Update active state // Apply filter
filterContainer.querySelectorAll('.bt-filter-btn').forEach(b => b.classList.remove('active')); currentDeviceFilter = filter;
btn.classList.add('active'); applyDeviceFilter();
});
}
// Apply filter const searchInput = document.getElementById('btDeviceSearch');
currentDeviceFilter = filter; if (searchInput) {
applyDeviceFilter(); searchInput.addEventListener('input', () => {
}); currentSearchTerm = searchInput.value.trim().toLowerCase();
applyDeviceFilter();
});
}
} }
/** /**
@@ -147,34 +157,41 @@ const BluetoothMode = (function() {
if (!deviceContainer) return; if (!deviceContainer) return;
const cards = deviceContainer.querySelectorAll('[data-bt-device-id]'); const cards = deviceContainer.querySelectorAll('[data-bt-device-id]');
let visibleCount = 0;
cards.forEach(card => { cards.forEach(card => {
const isNew = card.dataset.isNew === 'true'; const isNew = card.dataset.isNew === 'true';
const hasName = card.dataset.hasName === 'true'; const hasName = card.dataset.hasName === 'true';
const rssi = parseInt(card.dataset.rssi) || -100; const rssi = parseInt(card.dataset.rssi) || -100;
const isTracker = card.dataset.isTracker === 'true'; const isTracker = card.dataset.isTracker === 'true';
const searchHaystack = (card.dataset.search || '').toLowerCase();
let visible = true; let matchesFilter = true;
switch (currentDeviceFilter) { switch (currentDeviceFilter) {
case 'new': case 'new':
visible = isNew; matchesFilter = isNew;
break; break;
case 'named': case 'named':
visible = hasName; matchesFilter = hasName;
break; break;
case 'strong': case 'strong':
visible = rssi >= -70; matchesFilter = rssi >= -70;
break; break;
case 'trackers': case 'trackers':
visible = isTracker; matchesFilter = isTracker;
break; break;
case 'all': case 'all':
default: default:
visible = true; matchesFilter = true;
} }
const matchesSearch = !currentSearchTerm || searchHaystack.includes(currentSearchTerm);
const visible = matchesFilter && matchesSearch;
card.style.display = visible ? '' : 'none'; card.style.display = visible ? '' : 'none';
if (visible) visibleCount++;
}); });
visibleDeviceCount = visibleCount;
// Update visible count // Update visible count
updateFilteredCount(); updateFilteredCount();
} }
@@ -186,12 +203,8 @@ const BluetoothMode = (function() {
const countEl = document.getElementById('btDeviceListCount'); const countEl = document.getElementById('btDeviceListCount');
if (!countEl || !deviceContainer) return; if (!countEl || !deviceContainer) return;
if (currentDeviceFilter === 'all') { const hasFilter = currentDeviceFilter !== 'all' || currentSearchTerm.length > 0;
countEl.textContent = devices.size; countEl.textContent = hasFilter ? `${visibleDeviceCount}/${devices.size}` : devices.size;
} else {
const visible = deviceContainer.querySelectorAll('[data-bt-device-id]:not([style*="display: none"])').length;
countEl.textContent = visible + '/' + devices.size;
}
} }
/** /**
@@ -309,28 +322,18 @@ const BluetoothMode = (function() {
* Update proximity zone counts (simple HTML, no canvas) * Update proximity zone counts (simple HTML, no canvas)
*/ */
function updateProximityZones() { function updateProximityZones() {
zoneCounts = { veryClose: 0, close: 0, nearby: 0, far: 0 }; zoneCounts = { immediate: 0, near: 0, far: 0 };
devices.forEach(device => { devices.forEach(device => {
const rssi = device.rssi_current; const rssi = device.rssi_current;
if (rssi == null) return; if (rssi == null) return;
if (rssi >= -40) zoneCounts.veryClose++; if (rssi >= -50) zoneCounts.immediate++;
else if (rssi >= -55) zoneCounts.close++; else if (rssi >= -70) zoneCounts.near++;
else if (rssi >= -70) zoneCounts.nearby++;
else zoneCounts.far++; else zoneCounts.far++;
}); });
// Update DOM elements updateProximityZoneCounts(zoneCounts);
const veryCloseEl = document.getElementById('btZoneVeryClose');
const closeEl = document.getElementById('btZoneClose');
const nearbyEl = document.getElementById('btZoneNearby');
const farEl = document.getElementById('btZoneFar');
if (veryCloseEl) veryCloseEl.textContent = zoneCounts.veryClose;
if (closeEl) closeEl.textContent = zoneCounts.close;
if (nearbyEl) nearbyEl.textContent = zoneCounts.nearby;
if (farEl) farEl.textContent = zoneCounts.far;
} }
// Currently selected device // Currently selected device
@@ -934,8 +937,10 @@ const BluetoothMode = (function() {
weak: 0, weak: 0,
trackers: [] trackers: []
}; };
visibleDeviceCount = 0;
updateVisualizationPanels(); updateVisualizationPanels();
updateProximityZones(); updateProximityZones();
updateFilteredCount();
// Clear radar // Clear radar
if (radarInitialized && typeof ProximityRadar !== 'undefined') { if (radarInitialized && typeof ProximityRadar !== 'undefined') {
@@ -1144,6 +1149,26 @@ const BluetoothMode = (function() {
if (mediumCount) mediumCount.textContent = deviceStats.medium; if (mediumCount) mediumCount.textContent = deviceStats.medium;
if (weakCount) weakCount.textContent = deviceStats.weak; if (weakCount) weakCount.textContent = deviceStats.weak;
// Device summary strip
const totalEl = document.getElementById('btSummaryTotal');
const newEl = document.getElementById('btSummaryNew');
const trackersEl = document.getElementById('btSummaryTrackers');
const strongestEl = document.getElementById('btSummaryStrongest');
if (totalEl || newEl || trackersEl || strongestEl) {
let newCount = 0;
let strongest = null;
devices.forEach(d => {
if (!d.in_baseline) newCount++;
if (d.rssi_current != null) {
strongest = strongest == null ? d.rssi_current : Math.max(strongest, d.rssi_current);
}
});
if (totalEl) totalEl.textContent = devices.size;
if (newEl) newEl.textContent = newCount;
if (trackersEl) trackersEl.textContent = deviceStats.trackers.length;
if (strongestEl) strongestEl.textContent = strongest == null ? '--' : `${strongest} dBm`;
}
// Tracker Detection - Enhanced display with confidence and evidence // Tracker Detection - Enhanced display with confidence and evidence
const trackerList = document.getElementById('btTrackerList'); const trackerList = document.getElementById('btTrackerList');
if (trackerList) { if (trackerList) {
@@ -1231,9 +1256,7 @@ const BluetoothMode = (function() {
} }
// Re-apply filter after rendering // Re-apply filter after rendering
if (currentDeviceFilter !== 'all') { applyDeviceFilter();
applyDeviceFilter();
}
} }
function createSimpleDeviceCard(device) { function createSimpleDeviceCard(device) {
@@ -1260,6 +1283,14 @@ const BluetoothMode = (function() {
const mfr = device.manufacturer_name ? escapeHtml(device.manufacturer_name) : ''; const mfr = device.manufacturer_name ? escapeHtml(device.manufacturer_name) : '';
const seenCount = device.seen_count || 0; const seenCount = device.seen_count || 0;
const deviceIdEscaped = escapeHtml(device.device_id).replace(/'/g, "\\'"); const deviceIdEscaped = escapeHtml(device.device_id).replace(/'/g, "\\'");
const searchIndex = [
displayName,
device.address,
device.manufacturer_name,
device.tracker_name,
device.tracker_type,
agentName
].filter(Boolean).join(' ').toLowerCase();
// Protocol badge - compact // Protocol badge - compact
const protoBadge = protocol === 'ble' const protoBadge = protocol === 'ble'
@@ -1346,7 +1377,7 @@ const BluetoothMode = (function() {
const borderColor = isTracker && trackerConfidence === 'high' ? '#ef4444' : const borderColor = isTracker && trackerConfidence === 'high' ? '#ef4444' :
isTracker ? '#f97316' : rssiColor; isTracker ? '#f97316' : rssiColor;
return '<div class="bt-device-row' + (isTracker ? ' is-tracker' : '') + '" data-bt-device-id="' + escapeHtml(device.device_id) + '" data-is-new="' + isNew + '" data-has-name="' + hasName + '" data-rssi="' + (rssi || -100) + '" data-is-tracker="' + isTracker + '" onclick="BluetoothMode.selectDevice(\'' + deviceIdEscaped + '\')" style="border-left-color:' + borderColor + ';">' + return '<div class="bt-device-row' + (isTracker ? ' is-tracker' : '') + '" data-bt-device-id="' + escapeHtml(device.device_id) + '" data-is-new="' + isNew + '" data-has-name="' + hasName + '" data-rssi="' + (rssi || -100) + '" data-is-tracker="' + isTracker + '" data-search="' + escapeAttr(searchIndex) + '" onclick="BluetoothMode.selectDevice(\'' + deviceIdEscaped + '\')" style="border-left-color:' + borderColor + ';">' +
'<div class="bt-row-main">' + '<div class="bt-row-main">' +
'<div class="bt-row-left">' + '<div class="bt-row-left">' +
protoBadge + protoBadge +
@@ -1390,6 +1421,10 @@ const BluetoothMode = (function() {
return div.innerHTML; return div.innerHTML;
} }
function escapeAttr(text) {
return escapeHtml(text).replace(/"/g, '&quot;').replace(/'/g, '&#39;');
}
async function setBaseline() { async function setBaseline() {
try { try {
const response = await fetch('/api/bluetooth/baseline/set', { method: 'POST' }); const response = await fetch('/api/bluetooth/baseline/set', { method: 'POST' });
+37 -14
View File
@@ -830,6 +830,7 @@
</div> </div>
</div> </div>
<div class="bt-detail-badges" id="btDetailBadges"></div> <div class="bt-detail-badges" id="btDetailBadges"></div>
<div class="bt-detail-tracker-analysis" id="btDetailTrackerAnalysis" style="display: none;"></div>
<div class="bt-detail-grid"> <div class="bt-detail-grid">
<div class="bt-detail-stat"> <div class="bt-detail-stat">
<span class="bt-detail-stat-label">Manufacturer</span> <span class="bt-detail-stat-label">Manufacturer</span>
@@ -939,23 +940,23 @@
<h5>Proximity Radar</h5> <h5>Proximity Radar</h5>
<div id="btProximityRadar" style="display: flex; justify-content: center; padding: 8px 0;"></div> <div id="btProximityRadar" style="display: flex; justify-content: center; padding: 8px 0;"></div>
<div id="btRadarControls" style="display: flex; gap: 6px; justify-content: center; margin-top: 8px; flex-wrap: wrap;"> <div id="btRadarControls" style="display: flex; gap: 6px; justify-content: center; margin-top: 8px; flex-wrap: wrap;">
<button data-filter="newOnly" class="bt-radar-filter-btn" style="padding: 4px 10px; font-size: 10px; background: var(--bg-tertiary); border: 1px solid var(--border-color); border-radius: 4px; color: #888; cursor: pointer;">New Only</button> <button data-filter="newOnly" class="bt-radar-filter-btn">New Only</button>
<button data-filter="strongest" class="bt-radar-filter-btn" style="padding: 4px 10px; font-size: 10px; background: var(--bg-tertiary); border: 1px solid var(--border-color); border-radius: 4px; color: #888; cursor: pointer;">Strongest</button> <button data-filter="strongest" class="bt-radar-filter-btn">Strongest</button>
<button data-filter="unapproved" class="bt-radar-filter-btn" style="padding: 4px 10px; font-size: 10px; background: var(--bg-tertiary); border: 1px solid var(--border-color); border-radius: 4px; color: #888; cursor: pointer;">Unapproved</button> <button data-filter="unapproved" class="bt-radar-filter-btn">Unapproved</button>
<button id="btRadarPauseBtn" style="padding: 4px 10px; font-size: 10px; background: var(--bg-tertiary); border: 1px solid var(--border-color); border-radius: 4px; color: #888; cursor: pointer;">Pause</button> <button id="btRadarPauseBtn">Pause</button>
</div> </div>
<div id="btZoneSummary" style="display: flex; justify-content: center; gap: 24px; margin-top: 12px; font-size: 11px;"> <div id="btZoneSummary" class="bt-zone-summary">
<div style="text-align: center;"> <div class="bt-zone-card immediate">
<span id="btZoneImmediate" style="font-size: 20px; font-weight: 600; color: #22c55e;">0</span> <span id="btZoneImmediate" class="bt-zone-value">0</span>
<div style="color: #666;">Immediate</div> <div class="bt-zone-label">Immediate</div>
</div> </div>
<div style="text-align: center;"> <div class="bt-zone-card near">
<span id="btZoneNear" style="font-size: 20px; font-weight: 600; color: #eab308;">0</span> <span id="btZoneNear" class="bt-zone-value">0</span>
<div style="color: #666;">Near</div> <div class="bt-zone-label">Near</div>
</div> </div>
<div style="text-align: center;"> <div class="bt-zone-card far">
<span id="btZoneFar" style="font-size: 20px; font-weight: 600; color: #ef4444;">0</span> <span id="btZoneFar" class="bt-zone-value">0</span>
<div style="color: #666;">Far</div> <div class="bt-zone-label">Far</div>
</div> </div>
</div> </div>
</div> </div>
@@ -967,11 +968,33 @@
<h5>Bluetooth Devices</h5> <h5>Bluetooth Devices</h5>
<span class="device-count">(<span id="btDeviceListCount">0</span>)</span> <span class="device-count">(<span id="btDeviceListCount">0</span>)</span>
</div> </div>
<div class="bt-list-summary" id="btListSummary">
<div class="bt-summary-item">
<span class="bt-summary-label">Total</span>
<span class="bt-summary-value" id="btSummaryTotal">0</span>
</div>
<div class="bt-summary-item">
<span class="bt-summary-label">New</span>
<span class="bt-summary-value" id="btSummaryNew">0</span>
</div>
<div class="bt-summary-item">
<span class="bt-summary-label">Trackers</span>
<span class="bt-summary-value" id="btSummaryTrackers">0</span>
</div>
<div class="bt-summary-item">
<span class="bt-summary-label">Strongest</span>
<span class="bt-summary-value" id="btSummaryStrongest">--</span>
</div>
</div>
<div class="bt-device-toolbar">
<input type="search" id="btDeviceSearch" class="bt-device-search" placeholder="Filter by name, MAC, manufacturer...">
</div>
<div class="bt-device-filters" id="btDeviceFilters"> <div class="bt-device-filters" id="btDeviceFilters">
<button class="bt-filter-btn active" data-filter="all">All</button> <button class="bt-filter-btn active" data-filter="all">All</button>
<button class="bt-filter-btn" data-filter="new">New</button> <button class="bt-filter-btn" data-filter="new">New</button>
<button class="bt-filter-btn" data-filter="named">Named</button> <button class="bt-filter-btn" data-filter="named">Named</button>
<button class="bt-filter-btn" data-filter="strong">Strong</button> <button class="bt-filter-btn" data-filter="strong">Strong</button>
<button class="bt-filter-btn" data-filter="trackers">Trackers</button>
</div> </div>
<div class="wifi-device-list-content" id="btDeviceListContent"> <div class="wifi-device-list-content" id="btDeviceListContent">
<div style="color: var(--text-dim); text-align: center; padding: 30px;"> <div style="color: var(--text-dim); text-align: center; padding: 30px;">
+1
View File
@@ -52,6 +52,7 @@
<!-- Message Container for status cards --> <!-- Message Container for status cards -->
<div id="btMessageContainer"></div> <div id="btMessageContainer"></div>
<div id="btBaselineStatus" style="margin-top: 6px; font-size: 10px; color: var(--text-dim);">No baseline</div>
<button class="run-btn" id="startBtBtn" onclick="btStartScan()"> <button class="run-btn" id="startBtBtn" onclick="btStartScan()">
Start Scanning Start Scanning