mirror of
https://github.com/smittix/intercept.git
synced 2026-04-25 07:10:00 -07:00
Improve Bluetooth scanner filtering, stats, and layout
This commit is contained in:
@@ -27,15 +27,17 @@ const BluetoothMode = (function() {
|
||||
trackers: []
|
||||
};
|
||||
|
||||
// Zone counts for proximity display
|
||||
let zoneCounts = { veryClose: 0, close: 0, nearby: 0, far: 0 };
|
||||
// Zone counts for proximity display
|
||||
let zoneCounts = { immediate: 0, near: 0, far: 0 };
|
||||
|
||||
// New visualization components
|
||||
let radarInitialized = false;
|
||||
let radarPaused = false;
|
||||
|
||||
// Device list filter
|
||||
let currentDeviceFilter = 'all';
|
||||
// Device list filter
|
||||
let currentDeviceFilter = 'all';
|
||||
let currentSearchTerm = '';
|
||||
let visibleDeviceCount = 0;
|
||||
|
||||
// Agent support
|
||||
let showAllAgentsMode = false;
|
||||
@@ -119,80 +121,91 @@ const BluetoothMode = (function() {
|
||||
/**
|
||||
* Initialize device list filter buttons
|
||||
*/
|
||||
function initDeviceFilters() {
|
||||
const filterContainer = document.getElementById('btDeviceFilters');
|
||||
if (!filterContainer) return;
|
||||
|
||||
filterContainer.addEventListener('click', (e) => {
|
||||
const btn = e.target.closest('.bt-filter-btn');
|
||||
if (!btn) return;
|
||||
|
||||
const filter = btn.dataset.filter;
|
||||
if (!filter) return;
|
||||
|
||||
// Update active state
|
||||
filterContainer.querySelectorAll('.bt-filter-btn').forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
|
||||
// Apply filter
|
||||
currentDeviceFilter = filter;
|
||||
applyDeviceFilter();
|
||||
});
|
||||
}
|
||||
function initDeviceFilters() {
|
||||
const filterContainer = document.getElementById('btDeviceFilters');
|
||||
if (filterContainer) {
|
||||
filterContainer.addEventListener('click', (e) => {
|
||||
const btn = e.target.closest('.bt-filter-btn');
|
||||
if (!btn) return;
|
||||
|
||||
const filter = btn.dataset.filter;
|
||||
if (!filter) return;
|
||||
|
||||
// Update active state
|
||||
filterContainer.querySelectorAll('.bt-filter-btn').forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
|
||||
// Apply filter
|
||||
currentDeviceFilter = filter;
|
||||
applyDeviceFilter();
|
||||
});
|
||||
}
|
||||
|
||||
const searchInput = document.getElementById('btDeviceSearch');
|
||||
if (searchInput) {
|
||||
searchInput.addEventListener('input', () => {
|
||||
currentSearchTerm = searchInput.value.trim().toLowerCase();
|
||||
applyDeviceFilter();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply current filter to device list
|
||||
*/
|
||||
function applyDeviceFilter() {
|
||||
if (!deviceContainer) return;
|
||||
|
||||
const cards = deviceContainer.querySelectorAll('[data-bt-device-id]');
|
||||
cards.forEach(card => {
|
||||
const isNew = card.dataset.isNew === 'true';
|
||||
const hasName = card.dataset.hasName === 'true';
|
||||
const rssi = parseInt(card.dataset.rssi) || -100;
|
||||
const isTracker = card.dataset.isTracker === 'true';
|
||||
|
||||
let visible = true;
|
||||
switch (currentDeviceFilter) {
|
||||
case 'new':
|
||||
visible = isNew;
|
||||
break;
|
||||
case 'named':
|
||||
visible = hasName;
|
||||
break;
|
||||
case 'strong':
|
||||
visible = rssi >= -70;
|
||||
break;
|
||||
case 'trackers':
|
||||
visible = isTracker;
|
||||
break;
|
||||
case 'all':
|
||||
default:
|
||||
visible = true;
|
||||
}
|
||||
|
||||
card.style.display = visible ? '' : 'none';
|
||||
});
|
||||
|
||||
// Update visible count
|
||||
updateFilteredCount();
|
||||
}
|
||||
function applyDeviceFilter() {
|
||||
if (!deviceContainer) return;
|
||||
|
||||
const cards = deviceContainer.querySelectorAll('[data-bt-device-id]');
|
||||
let visibleCount = 0;
|
||||
cards.forEach(card => {
|
||||
const isNew = card.dataset.isNew === 'true';
|
||||
const hasName = card.dataset.hasName === 'true';
|
||||
const rssi = parseInt(card.dataset.rssi) || -100;
|
||||
const isTracker = card.dataset.isTracker === 'true';
|
||||
const searchHaystack = (card.dataset.search || '').toLowerCase();
|
||||
|
||||
let matchesFilter = true;
|
||||
switch (currentDeviceFilter) {
|
||||
case 'new':
|
||||
matchesFilter = isNew;
|
||||
break;
|
||||
case 'named':
|
||||
matchesFilter = hasName;
|
||||
break;
|
||||
case 'strong':
|
||||
matchesFilter = rssi >= -70;
|
||||
break;
|
||||
case 'trackers':
|
||||
matchesFilter = isTracker;
|
||||
break;
|
||||
case 'all':
|
||||
default:
|
||||
matchesFilter = true;
|
||||
}
|
||||
|
||||
const matchesSearch = !currentSearchTerm || searchHaystack.includes(currentSearchTerm);
|
||||
const visible = matchesFilter && matchesSearch;
|
||||
card.style.display = visible ? '' : 'none';
|
||||
if (visible) visibleCount++;
|
||||
});
|
||||
|
||||
visibleDeviceCount = visibleCount;
|
||||
|
||||
// Update visible count
|
||||
updateFilteredCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the device count display based on visible devices
|
||||
*/
|
||||
function updateFilteredCount() {
|
||||
const countEl = document.getElementById('btDeviceListCount');
|
||||
if (!countEl || !deviceContainer) return;
|
||||
|
||||
if (currentDeviceFilter === 'all') {
|
||||
countEl.textContent = devices.size;
|
||||
} else {
|
||||
const visible = deviceContainer.querySelectorAll('[data-bt-device-id]:not([style*="display: none"])').length;
|
||||
countEl.textContent = visible + '/' + devices.size;
|
||||
}
|
||||
}
|
||||
function updateFilteredCount() {
|
||||
const countEl = document.getElementById('btDeviceListCount');
|
||||
if (!countEl || !deviceContainer) return;
|
||||
|
||||
const hasFilter = currentDeviceFilter !== 'all' || currentSearchTerm.length > 0;
|
||||
countEl.textContent = hasFilter ? `${visibleDeviceCount}/${devices.size}` : devices.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the new proximity radar component
|
||||
@@ -308,30 +321,20 @@ const BluetoothMode = (function() {
|
||||
/**
|
||||
* Update proximity zone counts (simple HTML, no canvas)
|
||||
*/
|
||||
function updateProximityZones() {
|
||||
zoneCounts = { veryClose: 0, close: 0, nearby: 0, far: 0 };
|
||||
|
||||
devices.forEach(device => {
|
||||
const rssi = device.rssi_current;
|
||||
if (rssi == null) return;
|
||||
|
||||
if (rssi >= -40) zoneCounts.veryClose++;
|
||||
else if (rssi >= -55) zoneCounts.close++;
|
||||
else if (rssi >= -70) zoneCounts.nearby++;
|
||||
else zoneCounts.far++;
|
||||
});
|
||||
|
||||
// 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;
|
||||
}
|
||||
function updateProximityZones() {
|
||||
zoneCounts = { immediate: 0, near: 0, far: 0 };
|
||||
|
||||
devices.forEach(device => {
|
||||
const rssi = device.rssi_current;
|
||||
if (rssi == null) return;
|
||||
|
||||
if (rssi >= -50) zoneCounts.immediate++;
|
||||
else if (rssi >= -70) zoneCounts.near++;
|
||||
else zoneCounts.far++;
|
||||
});
|
||||
|
||||
updateProximityZoneCounts(zoneCounts);
|
||||
}
|
||||
|
||||
// Currently selected device
|
||||
let selectedDeviceId = null;
|
||||
@@ -927,20 +930,22 @@ const BluetoothMode = (function() {
|
||||
if (statusText) statusText.textContent = scanning ? 'Scanning...' : 'Idle';
|
||||
}
|
||||
|
||||
function resetStats() {
|
||||
deviceStats = {
|
||||
strong: 0,
|
||||
medium: 0,
|
||||
weak: 0,
|
||||
trackers: []
|
||||
};
|
||||
updateVisualizationPanels();
|
||||
updateProximityZones();
|
||||
|
||||
// Clear radar
|
||||
if (radarInitialized && typeof ProximityRadar !== 'undefined') {
|
||||
ProximityRadar.clear();
|
||||
}
|
||||
function resetStats() {
|
||||
deviceStats = {
|
||||
strong: 0,
|
||||
medium: 0,
|
||||
weak: 0,
|
||||
trackers: []
|
||||
};
|
||||
visibleDeviceCount = 0;
|
||||
updateVisualizationPanels();
|
||||
updateProximityZones();
|
||||
updateFilteredCount();
|
||||
|
||||
// Clear radar
|
||||
if (radarInitialized && typeof ProximityRadar !== 'undefined') {
|
||||
ProximityRadar.clear();
|
||||
}
|
||||
}
|
||||
|
||||
function startEventStream() {
|
||||
@@ -1127,9 +1132,9 @@ const BluetoothMode = (function() {
|
||||
/**
|
||||
* Update visualization panels
|
||||
*/
|
||||
function updateVisualizationPanels() {
|
||||
// Signal Distribution
|
||||
const total = devices.size || 1;
|
||||
function updateVisualizationPanels() {
|
||||
// Signal Distribution
|
||||
const total = devices.size || 1;
|
||||
const strongBar = document.getElementById('btSignalStrong');
|
||||
const mediumBar = document.getElementById('btSignalMedium');
|
||||
const weakBar = document.getElementById('btSignalWeak');
|
||||
@@ -1140,12 +1145,32 @@ const BluetoothMode = (function() {
|
||||
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 - Enhanced display with confidence and evidence
|
||||
const trackerList = document.getElementById('btTrackerList');
|
||||
if (strongCount) strongCount.textContent = deviceStats.strong;
|
||||
if (mediumCount) mediumCount.textContent = deviceStats.medium;
|
||||
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
|
||||
const trackerList = document.getElementById('btTrackerList');
|
||||
if (trackerList) {
|
||||
if (devices.size === 0) {
|
||||
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();
|
||||
}
|
||||
|
||||
function renderDevice(device) {
|
||||
if (!deviceContainer) {
|
||||
deviceContainer = document.getElementById('btDeviceListContent');
|
||||
if (!deviceContainer) return;
|
||||
}
|
||||
function renderDevice(device) {
|
||||
if (!deviceContainer) {
|
||||
deviceContainer = document.getElementById('btDeviceListContent');
|
||||
if (!deviceContainer) return;
|
||||
}
|
||||
|
||||
const escapedId = CSS.escape(device.device_id);
|
||||
const existingCard = deviceContainer.querySelector('[data-bt-device-id="' + escapedId + '"]');
|
||||
const cardHtml = createSimpleDeviceCard(device);
|
||||
|
||||
if (existingCard) {
|
||||
existingCard.outerHTML = cardHtml;
|
||||
} else {
|
||||
deviceContainer.insertAdjacentHTML('afterbegin', cardHtml);
|
||||
}
|
||||
|
||||
// Re-apply filter after rendering
|
||||
if (currentDeviceFilter !== 'all') {
|
||||
applyDeviceFilter();
|
||||
}
|
||||
}
|
||||
if (existingCard) {
|
||||
existingCard.outerHTML = cardHtml;
|
||||
} else {
|
||||
deviceContainer.insertAdjacentHTML('afterbegin', cardHtml);
|
||||
}
|
||||
|
||||
// Re-apply filter after rendering
|
||||
applyDeviceFilter();
|
||||
}
|
||||
|
||||
function createSimpleDeviceCard(device) {
|
||||
const protocol = device.protocol || 'ble';
|
||||
@@ -1257,9 +1280,17 @@ const BluetoothMode = (function() {
|
||||
const displayName = device.name || formatDeviceId(device.address);
|
||||
const name = escapeHtml(displayName);
|
||||
const addr = escapeHtml(isUuidAddress(device) ? formatAddress(device) : (device.address || 'Unknown'));
|
||||
const mfr = device.manufacturer_name ? escapeHtml(device.manufacturer_name) : '';
|
||||
const seenCount = device.seen_count || 0;
|
||||
const deviceIdEscaped = escapeHtml(device.device_id).replace(/'/g, "\\'");
|
||||
const mfr = device.manufacturer_name ? escapeHtml(device.manufacturer_name) : '';
|
||||
const seenCount = device.seen_count || 0;
|
||||
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
|
||||
const protoBadge = protocol === 'ble'
|
||||
@@ -1346,7 +1377,7 @@ const BluetoothMode = (function() {
|
||||
const borderColor = isTracker && trackerConfidence === 'high' ? '#ef4444' :
|
||||
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-left">' +
|
||||
protoBadge +
|
||||
@@ -1383,12 +1414,16 @@ const BluetoothMode = (function() {
|
||||
return '#ef4444';
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
if (!text) return '';
|
||||
const div = document.createElement('div');
|
||||
div.textContent = String(text);
|
||||
return div.innerHTML;
|
||||
}
|
||||
function escapeHtml(text) {
|
||||
if (!text) return '';
|
||||
const div = document.createElement('div');
|
||||
div.textContent = String(text);
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
function escapeAttr(text) {
|
||||
return escapeHtml(text).replace(/"/g, '"').replace(/'/g, ''');
|
||||
}
|
||||
|
||||
async function setBaseline() {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user