v2.6.0: BT Radar — consent-gated bluetooth scan/whitelist/push (paired-device consent model), /api/btradar, drawer BT panel; auto-crack.py for nightmare (chromium v10 offline decrypt verified)
This commit is contained in:
@@ -725,3 +725,92 @@ async function updateAllOutdated() {
|
||||
});
|
||||
mo.observe(document.body, { childList: true, subtree: true });
|
||||
})();
|
||||
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════════
|
||||
// BT RADAR — consent-gated bluetooth device inventory
|
||||
// ═════════════════════════════════════════════════════════════════════
|
||||
|
||||
async function drawerBtScan(nodeId) {
|
||||
const el = document.getElementById('btBox') || document.getElementById('pingResult');
|
||||
try {
|
||||
await api(`/api/nodes/${nodeId}/command`, {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ actionType: 'bt_scan', command: 'bt_scan', payload: {} })
|
||||
});
|
||||
// poll btradar for this node
|
||||
let tries = 0;
|
||||
const iv = setInterval(async () => {
|
||||
tries++;
|
||||
const r = await api('/api/btradar').catch(() => null);
|
||||
const entry = r && r.radar && r.radar[nodeId];
|
||||
if (entry) {
|
||||
clearInterval(iv);
|
||||
renderBtList(nodeId, entry);
|
||||
} else if (tries > 12) {
|
||||
clearInterval(iv);
|
||||
}
|
||||
}, 4000);
|
||||
} catch (e) { console.error(e); }
|
||||
}
|
||||
|
||||
function renderBtList(nodeId, entry) {
|
||||
let box = document.getElementById('btBox');
|
||||
const body = document.getElementById('ndBody');
|
||||
if (!body) return;
|
||||
if (!box) {
|
||||
box = document.createElement('div');
|
||||
box.id = 'btBox';
|
||||
box.style.cssText = 'border:1px solid #1e293b;border-radius:8px;padding:0.75rem;';
|
||||
body.appendChild(box);
|
||||
}
|
||||
const devs = (entry.devices || []);
|
||||
const ago = Math.round((Date.now() - entry.at) / 60000);
|
||||
const esc = escapeHtml;
|
||||
box.innerHTML = `
|
||||
<div style="display:flex;justify-content:space-between;margin-bottom:0.4rem;">
|
||||
<b>BT Radar</b><span style="color:#64748b;font-size:0.7rem;">scanned ${ago}m ago</span>
|
||||
</div>
|
||||
${devs.length ? devs.map(d => `
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;gap:0.5rem;padding:0.2rem 0;border-bottom:1px solid #1e293b;">
|
||||
<span style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">
|
||||
${d.mine ? '<span style="color:#4ade80;">●</span> ' : '<span style="color:#94a3b8;">○</span> '}
|
||||
<b>${esc(d.name || 'unnamed')}</b>
|
||||
<span style="color:#64748b;font-size:0.7rem;">${esc(d.mac)}${d.rssi ? ' · ' + esc(d.rssi) + 'dBm' : ''}</span>
|
||||
</span>
|
||||
${d.mine
|
||||
? '<span style="color:#4ade80;font-size:0.7rem;">mine</span>'
|
||||
: `<button class="btn btn-secondary" style="padding:0.15rem 0.5rem;font-size:0.7rem;" onclick="btWhitelist('${nodeId}','${esc(d.mac)}',true)">Approve</button>`}
|
||||
</div>`).join('')
|
||||
: '<div style="color:#64748b;">no devices in range</div>'}
|
||||
<div style="display:flex;gap:0.4rem;margin-top:0.5rem;">
|
||||
<button class="btn btn-secondary" onclick="drawerBtScan('${nodeId}')">Re-scan</button>
|
||||
</div>
|
||||
<div style="color:#64748b;font-size:0.65rem;margin-top:0.3rem;">● = on your approved list (push-enabled) · ○ = discovered, push blocked until approved</div>`;
|
||||
}
|
||||
|
||||
async function btWhitelist(nodeId, mac, add) {
|
||||
await api(`/api/nodes/${nodeId}/command`, {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ actionType: 'bt_whitelist', command: 'bt_whitelist', payload: { add: add ? mac : undefined, remove: add ? undefined : mac } })
|
||||
});
|
||||
// re-scan to refresh the view
|
||||
drawerBtScan(nodeId);
|
||||
}
|
||||
|
||||
// inject BT button into drawer action row
|
||||
(function injectBtBtn() {
|
||||
const mo = new MutationObserver(() => {
|
||||
const row = document.querySelector('#nexusDrawer div[style*="flex-wrap"]');
|
||||
if (row && !document.getElementById('btScanBtn')) {
|
||||
const b = document.createElement('button');
|
||||
b.id = 'btScanBtn';
|
||||
b.className = 'btn btn-secondary';
|
||||
b.innerHTML = '<i class="fa-brands fa-bluetooth-b"></i> BT Radar';
|
||||
b.title = 'Scan bluetooth devices in range of this node. Push (sync) only works on devices you Approve — pairing on the device itself is the consent.';
|
||||
b.onclick = () => drawerBtScan(window._drawerNodeId);
|
||||
row.appendChild(b);
|
||||
}
|
||||
});
|
||||
mo.observe(document.body, { childList: true, subtree: true });
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user