mirror of
https://github.com/smittix/intercept.git
synced 2026-07-11 11:08:12 -07:00
Improve Bluetooth scanner filtering, stats, and layout
This commit is contained in:
+246
-8
@@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 ============== */
|
||||||
|
|||||||
+178
-143
@@ -27,15 +27,17 @@ const BluetoothMode = (function() {
|
|||||||
trackers: []
|
trackers: []
|
||||||
};
|
};
|
||||||
|
|
||||||
// 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;
|
||||||
let radarPaused = false;
|
let radarPaused = false;
|
||||||
|
|
||||||
// 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;
|
||||||
@@ -119,80 +121,91 @@ const BluetoothMode = (function() {
|
|||||||
/**
|
/**
|
||||||
* Initialize device list filter buttons
|
* Initialize device list filter buttons
|
||||||
*/
|
*/
|
||||||
function initDeviceFilters() {
|
function initDeviceFilters() {
|
||||||
const filterContainer = document.getElementById('btDeviceFilters');
|
const filterContainer = document.getElementById('btDeviceFilters');
|
||||||
if (!filterContainer) return;
|
if (filterContainer) {
|
||||||
|
filterContainer.addEventListener('click', (e) => {
|
||||||
filterContainer.addEventListener('click', (e) => {
|
const btn = e.target.closest('.bt-filter-btn');
|
||||||
const btn = e.target.closest('.bt-filter-btn');
|
if (!btn) return;
|
||||||
if (!btn) return;
|
|
||||||
|
const filter = btn.dataset.filter;
|
||||||
const filter = btn.dataset.filter;
|
if (!filter) return;
|
||||||
if (!filter) return;
|
|
||||||
|
// Update active state
|
||||||
// Update active state
|
filterContainer.querySelectorAll('.bt-filter-btn').forEach(b => b.classList.remove('active'));
|
||||||
filterContainer.querySelectorAll('.bt-filter-btn').forEach(b => b.classList.remove('active'));
|
btn.classList.add('active');
|
||||||
btn.classList.add('active');
|
|
||||||
|
// Apply filter
|
||||||
// Apply filter
|
currentDeviceFilter = filter;
|
||||||
currentDeviceFilter = filter;
|
applyDeviceFilter();
|
||||||
applyDeviceFilter();
|
});
|
||||||
});
|
}
|
||||||
}
|
|
||||||
|
const searchInput = document.getElementById('btDeviceSearch');
|
||||||
|
if (searchInput) {
|
||||||
|
searchInput.addEventListener('input', () => {
|
||||||
|
currentSearchTerm = searchInput.value.trim().toLowerCase();
|
||||||
|
applyDeviceFilter();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply current filter to device list
|
* Apply current filter to device list
|
||||||
*/
|
*/
|
||||||
function applyDeviceFilter() {
|
function applyDeviceFilter() {
|
||||||
if (!deviceContainer) return;
|
if (!deviceContainer) return;
|
||||||
|
|
||||||
const cards = deviceContainer.querySelectorAll('[data-bt-device-id]');
|
const cards = deviceContainer.querySelectorAll('[data-bt-device-id]');
|
||||||
cards.forEach(card => {
|
let visibleCount = 0;
|
||||||
const isNew = card.dataset.isNew === 'true';
|
cards.forEach(card => {
|
||||||
const hasName = card.dataset.hasName === 'true';
|
const isNew = card.dataset.isNew === 'true';
|
||||||
const rssi = parseInt(card.dataset.rssi) || -100;
|
const hasName = card.dataset.hasName === 'true';
|
||||||
const isTracker = card.dataset.isTracker === 'true';
|
const rssi = parseInt(card.dataset.rssi) || -100;
|
||||||
|
const isTracker = card.dataset.isTracker === 'true';
|
||||||
let visible = true;
|
const searchHaystack = (card.dataset.search || '').toLowerCase();
|
||||||
switch (currentDeviceFilter) {
|
|
||||||
case 'new':
|
let matchesFilter = true;
|
||||||
visible = isNew;
|
switch (currentDeviceFilter) {
|
||||||
break;
|
case 'new':
|
||||||
case 'named':
|
matchesFilter = isNew;
|
||||||
visible = hasName;
|
break;
|
||||||
break;
|
case 'named':
|
||||||
case 'strong':
|
matchesFilter = hasName;
|
||||||
visible = rssi >= -70;
|
break;
|
||||||
break;
|
case 'strong':
|
||||||
case 'trackers':
|
matchesFilter = rssi >= -70;
|
||||||
visible = isTracker;
|
break;
|
||||||
break;
|
case 'trackers':
|
||||||
case 'all':
|
matchesFilter = isTracker;
|
||||||
default:
|
break;
|
||||||
visible = true;
|
case 'all':
|
||||||
}
|
default:
|
||||||
|
matchesFilter = true;
|
||||||
card.style.display = visible ? '' : 'none';
|
}
|
||||||
});
|
|
||||||
|
const matchesSearch = !currentSearchTerm || searchHaystack.includes(currentSearchTerm);
|
||||||
// Update visible count
|
const visible = matchesFilter && matchesSearch;
|
||||||
updateFilteredCount();
|
card.style.display = visible ? '' : 'none';
|
||||||
}
|
if (visible) visibleCount++;
|
||||||
|
});
|
||||||
|
|
||||||
|
visibleDeviceCount = visibleCount;
|
||||||
|
|
||||||
|
// Update visible count
|
||||||
|
updateFilteredCount();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the device count display based on visible devices
|
* Update the device count display based on visible devices
|
||||||
*/
|
*/
|
||||||
function updateFilteredCount() {
|
function updateFilteredCount() {
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the new proximity radar component
|
* Initialize the new proximity radar component
|
||||||
@@ -308,30 +321,20 @@ 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++;
|
});
|
||||||
});
|
|
||||||
|
updateProximityZoneCounts(zoneCounts);
|
||||||
// Update DOM elements
|
}
|
||||||
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
|
||||||
let selectedDeviceId = null;
|
let selectedDeviceId = null;
|
||||||
@@ -927,20 +930,22 @@ const BluetoothMode = (function() {
|
|||||||
if (statusText) statusText.textContent = scanning ? 'Scanning...' : 'Idle';
|
if (statusText) statusText.textContent = scanning ? 'Scanning...' : 'Idle';
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetStats() {
|
function resetStats() {
|
||||||
deviceStats = {
|
deviceStats = {
|
||||||
strong: 0,
|
strong: 0,
|
||||||
medium: 0,
|
medium: 0,
|
||||||
weak: 0,
|
weak: 0,
|
||||||
trackers: []
|
trackers: []
|
||||||
};
|
};
|
||||||
updateVisualizationPanels();
|
visibleDeviceCount = 0;
|
||||||
updateProximityZones();
|
updateVisualizationPanels();
|
||||||
|
updateProximityZones();
|
||||||
// Clear radar
|
updateFilteredCount();
|
||||||
if (radarInitialized && typeof ProximityRadar !== 'undefined') {
|
|
||||||
ProximityRadar.clear();
|
// Clear radar
|
||||||
}
|
if (radarInitialized && typeof ProximityRadar !== 'undefined') {
|
||||||
|
ProximityRadar.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function startEventStream() {
|
function startEventStream() {
|
||||||
@@ -1127,9 +1132,9 @@ const BluetoothMode = (function() {
|
|||||||
/**
|
/**
|
||||||
* Update visualization panels
|
* Update visualization panels
|
||||||
*/
|
*/
|
||||||
function updateVisualizationPanels() {
|
function updateVisualizationPanels() {
|
||||||
// Signal Distribution
|
// Signal Distribution
|
||||||
const total = devices.size || 1;
|
const total = devices.size || 1;
|
||||||
const strongBar = document.getElementById('btSignalStrong');
|
const strongBar = document.getElementById('btSignalStrong');
|
||||||
const mediumBar = document.getElementById('btSignalMedium');
|
const mediumBar = document.getElementById('btSignalMedium');
|
||||||
const weakBar = document.getElementById('btSignalWeak');
|
const weakBar = document.getElementById('btSignalWeak');
|
||||||
@@ -1140,12 +1145,32 @@ const BluetoothMode = (function() {
|
|||||||
if (strongBar) strongBar.style.width = (deviceStats.strong / total * 100) + '%';
|
if (strongBar) strongBar.style.width = (deviceStats.strong / total * 100) + '%';
|
||||||
if (mediumBar) mediumBar.style.width = (deviceStats.medium / total * 100) + '%';
|
if (mediumBar) mediumBar.style.width = (deviceStats.medium / total * 100) + '%';
|
||||||
if (weakBar) weakBar.style.width = (deviceStats.weak / total * 100) + '%';
|
if (weakBar) weakBar.style.width = (deviceStats.weak / total * 100) + '%';
|
||||||
if (strongCount) strongCount.textContent = deviceStats.strong;
|
if (strongCount) strongCount.textContent = deviceStats.strong;
|
||||||
if (mediumCount) mediumCount.textContent = deviceStats.medium;
|
if (mediumCount) mediumCount.textContent = deviceStats.medium;
|
||||||
if (weakCount) weakCount.textContent = deviceStats.weak;
|
if (weakCount) weakCount.textContent = deviceStats.weak;
|
||||||
|
|
||||||
// Tracker Detection - Enhanced display with confidence and evidence
|
// Device summary strip
|
||||||
const trackerList = document.getElementById('btTrackerList');
|
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
|
||||||
|
const trackerList = document.getElementById('btTrackerList');
|
||||||
if (trackerList) {
|
if (trackerList) {
|
||||||
if (devices.size === 0) {
|
if (devices.size === 0) {
|
||||||
trackerList.innerHTML = '<div style="color:#666;padding:10px;text-align:center;font-size:11px;">Start scanning to detect trackers</div>';
|
trackerList.innerHTML = '<div style="color:#666;padding:10px;text-align:center;font-size:11px;">Start scanning to detect trackers</div>';
|
||||||
@@ -1214,27 +1239,25 @@ const BluetoothMode = (function() {
|
|||||||
updateFilteredCount();
|
updateFilteredCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderDevice(device) {
|
function renderDevice(device) {
|
||||||
if (!deviceContainer) {
|
if (!deviceContainer) {
|
||||||
deviceContainer = document.getElementById('btDeviceListContent');
|
deviceContainer = document.getElementById('btDeviceListContent');
|
||||||
if (!deviceContainer) return;
|
if (!deviceContainer) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const escapedId = CSS.escape(device.device_id);
|
const escapedId = CSS.escape(device.device_id);
|
||||||
const existingCard = deviceContainer.querySelector('[data-bt-device-id="' + escapedId + '"]');
|
const existingCard = deviceContainer.querySelector('[data-bt-device-id="' + escapedId + '"]');
|
||||||
const cardHtml = createSimpleDeviceCard(device);
|
const cardHtml = createSimpleDeviceCard(device);
|
||||||
|
|
||||||
if (existingCard) {
|
if (existingCard) {
|
||||||
existingCard.outerHTML = cardHtml;
|
existingCard.outerHTML = cardHtml;
|
||||||
} else {
|
} else {
|
||||||
deviceContainer.insertAdjacentHTML('afterbegin', cardHtml);
|
deviceContainer.insertAdjacentHTML('afterbegin', cardHtml);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-apply filter after rendering
|
// Re-apply filter after rendering
|
||||||
if (currentDeviceFilter !== 'all') {
|
applyDeviceFilter();
|
||||||
applyDeviceFilter();
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createSimpleDeviceCard(device) {
|
function createSimpleDeviceCard(device) {
|
||||||
const protocol = device.protocol || 'ble';
|
const protocol = device.protocol || 'ble';
|
||||||
@@ -1257,9 +1280,17 @@ const BluetoothMode = (function() {
|
|||||||
const displayName = device.name || formatDeviceId(device.address);
|
const displayName = device.name || formatDeviceId(device.address);
|
||||||
const name = escapeHtml(displayName);
|
const name = escapeHtml(displayName);
|
||||||
const addr = escapeHtml(isUuidAddress(device) ? formatAddress(device) : (device.address || 'Unknown'));
|
const addr = escapeHtml(isUuidAddress(device) ? formatAddress(device) : (device.address || 'Unknown'));
|
||||||
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 +
|
||||||
@@ -1383,12 +1414,16 @@ const BluetoothMode = (function() {
|
|||||||
return '#ef4444';
|
return '#ef4444';
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeHtml(text) {
|
function escapeHtml(text) {
|
||||||
if (!text) return '';
|
if (!text) return '';
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
div.textContent = String(text);
|
div.textContent = String(text);
|
||||||
return div.innerHTML;
|
return div.innerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function escapeAttr(text) {
|
||||||
|
return escapeHtml(text).replace(/"/g, '"').replace(/'/g, ''');
|
||||||
|
}
|
||||||
|
|
||||||
async function setBaseline() {
|
async function setBaseline() {
|
||||||
try {
|
try {
|
||||||
|
|||||||
+37
-14
@@ -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;">
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user