mirror of
https://github.com/smittix/intercept.git
synced 2026-06-08 06:01:56 -07:00
feat(bluetooth): WiFi-style 2-line device rows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+85
-128
@@ -4799,8 +4799,8 @@ header h1 .tagline {
|
||||
|
||||
/* Selected device highlight */
|
||||
.bt-device-row.selected {
|
||||
background: rgba(0, 212, 255, 0.1);
|
||||
border-color: var(--accent-cyan);
|
||||
background: rgba(74, 163, 255, 0.07);
|
||||
border-left-color: var(--accent-cyan) !important;
|
||||
}
|
||||
|
||||
.bt-device-list {
|
||||
@@ -5212,57 +5212,107 @@ header h1 .tagline {
|
||||
background: linear-gradient(90deg, #ef4444, #dc2626);
|
||||
}
|
||||
|
||||
/* Bluetooth Device Row - Compact Design */
|
||||
/* Bluetooth Device Row - WiFi-style 2-line layout */
|
||||
.bt-device-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-left: 4px solid #666;
|
||||
border-radius: 6px;
|
||||
padding: 10px 12px;
|
||||
margin-bottom: 6px;
|
||||
border-left: 3px solid transparent;
|
||||
padding: 9px 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.03);
|
||||
transition: background 0.12s;
|
||||
}
|
||||
|
||||
.bt-device-row:last-child {
|
||||
margin-bottom: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.bt-device-row:hover {
|
||||
background: rgba(0, 212, 255, 0.05);
|
||||
border-color: var(--accent-cyan);
|
||||
}
|
||||
.bt-device-row:hover { background: var(--bg-tertiary); }
|
||||
|
||||
.bt-device-row:focus-visible {
|
||||
outline: 1px solid var(--accent-cyan);
|
||||
outline-offset: 1px;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
|
||||
.bt-row-main {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.bt-row-left {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px 8px;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.bt-row-right {
|
||||
/* Bluetooth device row — 2-line WiFi-style layout */
|
||||
.bt-row-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
justify-content: space-between;
|
||||
gap: 6px;
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
|
||||
.bt-row-top-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bt-row-top-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.bt-row-name {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.bt-row-name.bt-unnamed {
|
||||
color: var(--text-dim);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.bt-row-bottom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.bt-signal-bar-wrap { flex: 1; }
|
||||
|
||||
.bt-signal-track {
|
||||
height: 4px;
|
||||
background: var(--border-color);
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bt-signal-fill {
|
||||
height: 100%;
|
||||
border-radius: 2px;
|
||||
transition: width 0.4s ease;
|
||||
}
|
||||
|
||||
.bt-signal-fill.strong { background: linear-gradient(90deg, var(--accent-green), #88d49b); }
|
||||
.bt-signal-fill.medium { background: linear-gradient(90deg, var(--accent-green), var(--accent-orange)); }
|
||||
.bt-signal-fill.weak { background: linear-gradient(90deg, var(--accent-orange), var(--accent-red)); }
|
||||
|
||||
.bt-row-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex-shrink: 0;
|
||||
font-size: 10px;
|
||||
color: var(--text-dim);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.bt-row-rssi { font-family: var(--font-mono); font-size: 10px; }
|
||||
.bt-row-rssi.strong { color: var(--accent-green); }
|
||||
.bt-row-rssi.medium { color: var(--accent-amber, #eab308); }
|
||||
.bt-row-rssi.weak { color: var(--accent-red); }
|
||||
|
||||
.bt-proto-badge {
|
||||
display: inline-block;
|
||||
padding: 2px 6px;
|
||||
@@ -5308,43 +5358,6 @@ header h1 .tagline {
|
||||
border: 1px solid rgba(168, 85, 247, 0.3);
|
||||
}
|
||||
|
||||
.bt-device-name {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-word;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.bt-rssi-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.bt-rssi-bar-bg {
|
||||
width: 50px;
|
||||
height: 8px;
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bt-rssi-bar {
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.bt-rssi-value {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
min-width: 28px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.bt-status-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
@@ -5358,63 +5371,7 @@ header h1 .tagline {
|
||||
}
|
||||
|
||||
.bt-status-dot.known {
|
||||
background: #22c55e;
|
||||
}
|
||||
|
||||
.bt-row-secondary {
|
||||
font-size: 10px;
|
||||
color: var(--text-dim);
|
||||
margin-top: 4px;
|
||||
padding-left: 42px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.bt-row-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 4px 4px 0 42px;
|
||||
}
|
||||
|
||||
/* Locate action on Bluetooth device rows (must be in index.css so it styles in scanner mode) */
|
||||
.bt-row-actions .bt-locate-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
min-height: 28px;
|
||||
padding: 5px 10px;
|
||||
font-size: 10px;
|
||||
line-height: 1;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.07em;
|
||||
color: var(--accent-green, #38c180);
|
||||
background: linear-gradient(180deg, rgba(56, 193, 128, 0.2), rgba(56, 193, 128, 0.12));
|
||||
border: 1px solid rgba(56, 193, 128, 0.42);
|
||||
border-radius: 999px;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: background 0.18s ease, border-color 0.18s ease, transform 0.18s ease, box-shadow 0.18s ease;
|
||||
}
|
||||
|
||||
.bt-row-actions .bt-locate-btn:hover {
|
||||
background: linear-gradient(180deg, rgba(56, 193, 128, 0.28), rgba(56, 193, 128, 0.18));
|
||||
border-color: rgba(56, 193, 128, 0.72);
|
||||
box-shadow: 0 0 0 1px rgba(56, 193, 128, 0.2), 0 6px 16px rgba(20, 80, 54, 0.35);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.bt-row-actions .bt-locate-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.bt-row-actions .bt-locate-btn svg {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
stroke: currentColor;
|
||||
flex-shrink: 0;
|
||||
background: #484f58;
|
||||
}
|
||||
|
||||
.bt-device-filter-state {
|
||||
|
||||
@@ -37,6 +37,7 @@ const BluetoothMode = (function() {
|
||||
// Device list filter
|
||||
let currentDeviceFilter = 'all';
|
||||
let sortBy = 'rssi';
|
||||
let sortListenersBound = false;
|
||||
let currentSearchTerm = '';
|
||||
let visibleDeviceCount = 0;
|
||||
let pendingDeviceFlush = false;
|
||||
@@ -161,6 +162,8 @@ const BluetoothMode = (function() {
|
||||
}
|
||||
|
||||
function initSortControls() {
|
||||
if (sortListenersBound) return;
|
||||
sortListenersBound = true;
|
||||
const sortGroup = document.getElementById('btSortGroup');
|
||||
if (!sortGroup) return;
|
||||
sortGroup.addEventListener('click', (e) => {
|
||||
@@ -1390,6 +1393,7 @@ const BluetoothMode = (function() {
|
||||
*/
|
||||
function renderAllDevices() {
|
||||
if (!deviceContainer) return;
|
||||
if (devices.size === 0) return;
|
||||
deviceContainer.innerHTML = '';
|
||||
|
||||
const sorted = [...devices.values()].sort((a, b) => {
|
||||
@@ -1408,7 +1412,6 @@ const BluetoothMode = (function() {
|
||||
function createSimpleDeviceCard(device) {
|
||||
const protocol = device.protocol || 'ble';
|
||||
const rssi = device.rssi_current;
|
||||
const rssiColor = getRssiColor(rssi);
|
||||
const inBaseline = device.in_baseline || false;
|
||||
const isNew = !inBaseline;
|
||||
const hasName = !!device.name;
|
||||
@@ -1419,58 +1422,69 @@ const BluetoothMode = (function() {
|
||||
const agentName = device._agent || 'Local';
|
||||
const seenBefore = device.seen_before === true;
|
||||
|
||||
// Calculate RSSI bar width (0-100%)
|
||||
// RSSI typically ranges from -100 (weak) to -30 (very strong)
|
||||
// Signal bar
|
||||
const rssiPercent = rssi != null ? Math.max(0, Math.min(100, ((rssi + 100) / 70) * 100)) : 0;
|
||||
const fillClass = rssi == null ? 'weak'
|
||||
: rssi >= -60 ? 'strong'
|
||||
: rssi >= -75 ? 'medium' : 'weak';
|
||||
|
||||
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 searchIndex = [
|
||||
displayName,
|
||||
device.address,
|
||||
device.manufacturer_name,
|
||||
device.tracker_name,
|
||||
device.tracker_type,
|
||||
agentName
|
||||
displayName, device.address, device.manufacturer_name,
|
||||
device.tracker_name, device.tracker_type, agentName
|
||||
].filter(Boolean).join(' ').toLowerCase();
|
||||
|
||||
// Protocol badge - compact
|
||||
// Protocol badge
|
||||
const protoBadge = protocol === 'ble'
|
||||
? '<span class="bt-proto-badge ble">BLE</span>'
|
||||
: '<span class="bt-proto-badge classic">CLASSIC</span>';
|
||||
|
||||
// Tracker badge - show if device is detected as tracker
|
||||
// Tracker badge
|
||||
let trackerBadge = '';
|
||||
if (isTracker) {
|
||||
const confColor = trackerConfidence === 'high' ? '#ef4444' :
|
||||
trackerConfidence === 'medium' ? '#f97316' : '#eab308';
|
||||
const confBg = trackerConfidence === 'high' ? 'rgba(239,68,68,0.15)' :
|
||||
trackerConfidence === 'medium' ? 'rgba(249,115,22,0.15)' : 'rgba(234,179,8,0.15)';
|
||||
const typeLabel = trackerType === 'airtag' ? 'AirTag' :
|
||||
trackerType === 'tile' ? 'Tile' :
|
||||
trackerType === 'samsung_smarttag' ? 'SmartTag' :
|
||||
trackerType === 'findmy_accessory' ? 'FindMy' :
|
||||
trackerType === 'chipolo' ? 'Chipolo' : 'TRACKER';
|
||||
trackerBadge = '<span class="bt-tracker-badge" style="background:' + confBg + ';color:' + confColor + ';font-size:9px;padding:1px 4px;border-radius:3px;margin-left:4px;font-weight:600;">' + typeLabel + '</span>';
|
||||
const confColor = trackerConfidence === 'high' ? '#ef4444'
|
||||
: trackerConfidence === 'medium' ? '#f97316' : '#eab308';
|
||||
const confBg = trackerConfidence === 'high' ? 'rgba(239,68,68,0.15)'
|
||||
: trackerConfidence === 'medium' ? 'rgba(249,115,22,0.15)' : 'rgba(234,179,8,0.15)';
|
||||
const typeLabel = trackerType === 'airtag' ? 'AirTag'
|
||||
: trackerType === 'tile' ? 'Tile'
|
||||
: trackerType === 'samsung_smarttag' ? 'SmartTag'
|
||||
: trackerType === 'findmy_accessory' ? 'FindMy'
|
||||
: trackerType === 'chipolo' ? 'Chipolo' : 'TRACKER';
|
||||
trackerBadge = '<span class="bt-tracker-badge" style="background:' + confBg + ';color:' + confColor
|
||||
+ ';font-size:9px;padding:1px 5px;border-radius:3px;font-weight:600;">' + typeLabel + '</span>';
|
||||
}
|
||||
|
||||
// IRK badge - show if paired IRK is available
|
||||
let irkBadge = '';
|
||||
if (device.has_irk) {
|
||||
irkBadge = '<span class="bt-irk-badge">IRK</span>';
|
||||
}
|
||||
// IRK badge
|
||||
const irkBadge = device.has_irk ? '<span class="bt-irk-badge">IRK</span>' : '';
|
||||
|
||||
// Risk badge - show if risk score is significant
|
||||
// Risk badge
|
||||
let riskBadge = '';
|
||||
if (riskScore >= 0.3) {
|
||||
const riskColor = riskScore >= 0.5 ? '#ef4444' : '#f97316';
|
||||
riskBadge = '<span class="bt-risk-badge" style="color:' + riskColor + ';font-size:8px;margin-left:4px;font-weight:600;">' + Math.round(riskScore * 100) + '% RISK</span>';
|
||||
riskBadge = '<span class="bt-risk-badge" style="color:' + riskColor
|
||||
+ ';font-size:8px;font-weight:600;">' + Math.round(riskScore * 100) + '% RISK</span>';
|
||||
}
|
||||
|
||||
// Status indicator
|
||||
// MAC cluster badge
|
||||
const clusterBadge = device.mac_cluster_count > 1
|
||||
? '<span class="bt-mac-cluster-badge">' + device.mac_cluster_count + ' MACs</span>'
|
||||
: '';
|
||||
|
||||
// Flag badges (top-right, before status dot)
|
||||
const hFlags = device.heuristic_flags || [];
|
||||
let flagBadges = '';
|
||||
if (device.is_persistent || hFlags.includes('persistent'))
|
||||
flagBadges += '<span class="bt-flag-badge persistent">PERSIST</span>';
|
||||
if (device.is_beacon_like || hFlags.includes('beacon_like'))
|
||||
flagBadges += '<span class="bt-flag-badge beacon-like">BEACON</span>';
|
||||
if (device.is_strong_stable || hFlags.includes('strong_stable'))
|
||||
flagBadges += '<span class="bt-flag-badge strong-stable">STABLE</span>';
|
||||
|
||||
// Status dot
|
||||
let statusDot;
|
||||
if (isTracker && trackerConfidence === 'high') {
|
||||
statusDot = '<span class="bt-status-dot tracker" style="background:#ef4444;"></span>';
|
||||
@@ -1480,74 +1494,55 @@ const BluetoothMode = (function() {
|
||||
statusDot = '<span class="bt-status-dot known"></span>';
|
||||
}
|
||||
|
||||
// Distance display
|
||||
// Bottom meta
|
||||
const metaLabel = mfr || addr; // already HTML-escaped
|
||||
const distM = device.estimated_distance_m;
|
||||
let distStr = '';
|
||||
if (distM != null) {
|
||||
distStr = '~' + distM.toFixed(1) + 'm';
|
||||
}
|
||||
const distStr = distM != null ? '~' + distM.toFixed(1) + 'm' : '';
|
||||
let metaHtml = '<span>' + metaLabel + '</span>';
|
||||
if (distStr) metaHtml += '<span>' + distStr + '</span>';
|
||||
metaHtml += '<span class="bt-row-rssi ' + fillClass + '">' + (rssi != null ? rssi : '—') + '</span>';
|
||||
if (seenBefore) metaHtml += '<span class="bt-history-badge">SEEN</span>';
|
||||
if (agentName !== 'Local')
|
||||
metaHtml += '<span class="agent-badge agent-remote" style="font-size:8px;padding:1px 4px;">'
|
||||
+ escapeHtml(agentName) + '</span>';
|
||||
|
||||
// Behavioral flag badges
|
||||
const hFlags = device.heuristic_flags || [];
|
||||
let flagBadges = '';
|
||||
if (device.is_persistent || hFlags.includes('persistent')) {
|
||||
flagBadges += '<span class="bt-flag-badge persistent">PERSIST</span>';
|
||||
}
|
||||
if (device.is_beacon_like || hFlags.includes('beacon_like')) {
|
||||
flagBadges += '<span class="bt-flag-badge beacon-like">BEACON</span>';
|
||||
}
|
||||
if (device.is_strong_stable || hFlags.includes('strong_stable')) {
|
||||
flagBadges += '<span class="bt-flag-badge strong-stable">STABLE</span>';
|
||||
}
|
||||
// Left border colour
|
||||
const borderColor = isTracker && trackerConfidence === 'high' ? '#ef4444'
|
||||
: isTracker ? '#f97316'
|
||||
: rssi != null && rssi >= -60 ? 'var(--accent-green)'
|
||||
: rssi != null && rssi >= -75 ? 'var(--accent-amber, #eab308)'
|
||||
: 'var(--accent-red)';
|
||||
|
||||
// MAC cluster badge
|
||||
let clusterBadge = '';
|
||||
if (device.mac_cluster_count > 1) {
|
||||
clusterBadge = '<span class="bt-mac-cluster-badge">' + device.mac_cluster_count + ' MACs</span>';
|
||||
}
|
||||
|
||||
// Build secondary info line
|
||||
let secondaryParts = [addr];
|
||||
if (mfr) secondaryParts.push(mfr);
|
||||
if (distStr) secondaryParts.push(distStr);
|
||||
secondaryParts.push('Seen ' + seenCount + '×');
|
||||
if (seenBefore) secondaryParts.push('<span class="bt-history-badge">SEEN BEFORE</span>');
|
||||
// Add agent name if not Local
|
||||
if (agentName !== 'Local') {
|
||||
secondaryParts.push('<span class="agent-badge agent-remote" style="font-size:8px;padding:1px 4px;">' + escapeHtml(agentName) + '</span>');
|
||||
}
|
||||
const secondaryInfo = secondaryParts.join(' · ');
|
||||
|
||||
// Row border color - highlight trackers in red/orange
|
||||
const borderColor = isTracker && trackerConfidence === 'high' ? '#ef4444' :
|
||||
isTracker ? '#f97316' : rssiColor;
|
||||
|
||||
return '<div class="bt-device-row' + (isTracker ? ' is-tracker' : '') + '" data-bt-device-id="' + escapeAttr(device.device_id) + '" data-is-new="' + isNew + '" data-has-name="' + hasName + '" data-rssi="' + (rssi || -100) + '" data-is-tracker="' + isTracker + '" data-search="' + escapeAttr(searchIndex) + '" role="button" tabindex="0" data-keyboard-activate="true" style="border-left-color:' + borderColor + ';">' +
|
||||
'<div class="bt-row-main">' +
|
||||
'<div class="bt-row-left">' +
|
||||
protoBadge +
|
||||
'<span class="bt-device-name">' + name + '</span>' +
|
||||
trackerBadge +
|
||||
irkBadge +
|
||||
riskBadge +
|
||||
flagBadges +
|
||||
clusterBadge +
|
||||
'</div>' +
|
||||
'<div class="bt-row-right">' +
|
||||
'<div class="bt-rssi-container">' +
|
||||
'<div class="bt-rssi-bar-bg"><div class="bt-rssi-bar" style="width:' + rssiPercent + '%;background:' + rssiColor + ';"></div></div>' +
|
||||
'<span class="bt-rssi-value" style="color:' + rssiColor + ';">' + (rssi != null ? rssi : '--') + '</span>' +
|
||||
'</div>' +
|
||||
statusDot +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="bt-row-secondary">' + secondaryInfo + '</div>' +
|
||||
'<div class="bt-row-actions">' +
|
||||
'<button type="button" class="bt-locate-btn" data-locate-id="' + escapeAttr(device.device_id) + '">' +
|
||||
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="10" r="3"/><path d="M12 21.7C17.3 17 20 13 20 10a8 8 0 1 0-16 0c0 3 2.7 7 8 11.7z"/></svg>' +
|
||||
'Locate</button>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
return '<div class="bt-device-row' + (isTracker ? ' is-tracker' : '') + '"'
|
||||
+ ' data-bt-device-id="' + escapeAttr(device.device_id) + '"'
|
||||
+ ' data-is-new="' + isNew + '"'
|
||||
+ ' data-has-name="' + hasName + '"'
|
||||
+ ' data-rssi="' + (rssi ?? -100) + '"'
|
||||
+ ' data-is-tracker="' + isTracker + '"'
|
||||
+ ' data-search="' + escapeAttr(searchIndex) + '"'
|
||||
+ ' role="button" tabindex="0" data-keyboard-activate="true"'
|
||||
+ ' style="border-left-color:' + borderColor + ';">'
|
||||
// Top line
|
||||
+ '<div class="bt-row-top">'
|
||||
+ '<div class="bt-row-top-left">'
|
||||
+ protoBadge
|
||||
+ '<span class="bt-row-name' + (hasName ? '' : ' bt-unnamed') + '">' + name + '</span>'
|
||||
+ trackerBadge + irkBadge + riskBadge + clusterBadge
|
||||
+ '</div>'
|
||||
+ '<div class="bt-row-top-right">'
|
||||
+ flagBadges + statusDot
|
||||
+ '</div>'
|
||||
+ '</div>'
|
||||
// Bottom line
|
||||
+ '<div class="bt-row-bottom">'
|
||||
+ '<div class="bt-signal-bar-wrap">'
|
||||
+ '<div class="bt-signal-track">'
|
||||
+ '<div class="bt-signal-fill ' + fillClass + '" style="width:' + rssiPercent.toFixed(1) + '%"></div>'
|
||||
+ '</div>'
|
||||
+ '</div>'
|
||||
+ '<div class="bt-row-meta">' + metaHtml + '</div>'
|
||||
+ '</div>'
|
||||
+ '</div>';
|
||||
}
|
||||
|
||||
function getRssiColor(rssi) {
|
||||
|
||||
Reference in New Issue
Block a user