diff --git a/public/packets.js b/public/packets.js index bb3f638b..512ae404 100644 --- a/public/packets.js +++ b/public/packets.js @@ -233,8 +233,8 @@
- - + +
@@ -243,8 +243,8 @@
- - + +
RegionTimeHashSizeTypeObserverPathRptDetailsRegionTimeHashSizeTypeObserverPathRptDetails
@@ -278,10 +278,14 @@ const fNode = document.getElementById('fNode'); const fNodeDrop = document.getElementById('fNodeDropdown'); fNode.value = filters.nodeName || ''; + let nodeActiveIdx = -1; fNode.addEventListener('input', debounce(async (e) => { const q = e.target.value.trim(); + nodeActiveIdx = -1; + fNode.setAttribute('aria-activedescendant', ''); if (!q) { fNodeDrop.classList.add('hidden'); + fNode.setAttribute('aria-expanded', 'false'); if (filters.node) { filters.node = undefined; filters.nodeName = undefined; loadPackets(); } return; } @@ -289,23 +293,64 @@ const resp = await fetch('/api/nodes/search?q=' + encodeURIComponent(q)); const data = await resp.json(); const nodes = data.nodes || []; - if (nodes.length === 0) { fNodeDrop.classList.add('hidden'); return; } - fNodeDrop.innerHTML = nodes.map(n => - `
${escapeHtml(n.name || n.public_key.slice(0,8))} ${n.public_key.slice(0,8)}
` + if (nodes.length === 0) { fNodeDrop.classList.add('hidden'); fNode.setAttribute('aria-expanded', 'false'); return; } + fNodeDrop.innerHTML = nodes.map((n, i) => + `
${escapeHtml(n.name || n.public_key.slice(0,8))} ${n.public_key.slice(0,8)}
` ).join(''); fNodeDrop.classList.remove('hidden'); + fNode.setAttribute('aria-expanded', 'true'); fNodeDrop.querySelectorAll('.node-filter-option').forEach(opt => { opt.addEventListener('click', () => { - filters.node = opt.dataset.key; - filters.nodeName = opt.dataset.name; - fNode.value = opt.dataset.name; - fNodeDrop.classList.add('hidden'); - loadPackets(); + selectNodeOption(opt); }); }); } catch {} }, 250)); - fNode.addEventListener('blur', () => { setTimeout(() => fNodeDrop.classList.add('hidden'), 200); }); + + function selectNodeOption(opt) { + filters.node = opt.dataset.key; + filters.nodeName = opt.dataset.name; + fNode.value = opt.dataset.name; + fNodeDrop.classList.add('hidden'); + fNode.setAttribute('aria-expanded', 'false'); + fNode.setAttribute('aria-activedescendant', ''); + nodeActiveIdx = -1; + loadPackets(); + } + + fNode.addEventListener('keydown', (e) => { + const options = fNodeDrop.querySelectorAll('.node-filter-option'); + if (!options.length || fNodeDrop.classList.contains('hidden')) return; + if (e.key === 'ArrowDown') { + e.preventDefault(); + nodeActiveIdx = Math.min(nodeActiveIdx + 1, options.length - 1); + updateNodeActive(options); + } else if (e.key === 'ArrowUp') { + e.preventDefault(); + nodeActiveIdx = Math.max(nodeActiveIdx - 1, 0); + updateNodeActive(options); + } else if (e.key === 'Enter') { + e.preventDefault(); + if (nodeActiveIdx >= 0 && options[nodeActiveIdx]) selectNodeOption(options[nodeActiveIdx]); + } else if (e.key === 'Escape') { + fNodeDrop.classList.add('hidden'); + fNode.setAttribute('aria-expanded', 'false'); + nodeActiveIdx = -1; + } + }); + + function updateNodeActive(options) { + options.forEach((o, i) => { + o.classList.toggle('node-filter-active', i === nodeActiveIdx); + o.setAttribute('aria-selected', i === nodeActiveIdx ? 'true' : 'false'); + }); + if (nodeActiveIdx >= 0 && options[nodeActiveIdx]) { + fNode.setAttribute('aria-activedescendant', options[nodeActiveIdx].id); + options[nodeActiveIdx].scrollIntoView({ block: 'nearest' }); + } + } + + fNode.addEventListener('blur', () => { setTimeout(() => { fNodeDrop.classList.add('hidden'); fNode.setAttribute('aria-expanded', 'false'); }, 200); }); // Delegated click/keyboard handler for table rows const pktBody = document.getElementById('pktBody'); @@ -353,14 +398,14 @@ const isSingle = p.count <= 1; html += ` ${isSingle ? '' : (isExpanded ? '▼' : '▶')} - ${groupRegion ? `${groupRegion}` : '—'} + ${groupRegion ? `${groupRegion}` : '—'} ${timeAgo(p.latest)} ${truncate(p.hash || '—', 8)} - ${groupSize ? groupSize + 'B' : '—'} + ${groupSize ? groupSize + 'B' : '—'} ${p.payload_type != null ? `${groupTypeName}` : '—'} ${isSingle ? truncate(p.observer_name || p.observer_id || '—', 16) : truncate(p.observer_name || p.observer_id || '—', 10) + (p.observer_count > 1 ? ' +' + (p.observer_count - 1) : '')} ${groupPathStr} - ${isSingle ? '' : p.count} + ${isSingle ? '' : p.count} ${getDetailPreview((() => { try { return JSON.parse(p.decoded_json || '{}'); } catch { return {}; } })())} `; // Child rows (loaded async when expanded) @@ -374,14 +419,14 @@ try { childPath = JSON.parse(c.path_json || '[]'); } catch {} const childPathStr = renderPath(childPath); html += ` - ${childRegion ? `${childRegion}` : '—'} + ${childRegion ? `${childRegion}` : '—'} ${timeAgo(c.timestamp)} ${truncate(c.hash || '', 8)} - ${size}B + ${size}B ${typeName} ${truncate(c.observer_name || c.observer_id || '—', 16)} ${childPathStr} - + ${getDetailPreview((() => { try { return JSON.parse(c.decoded_json); } catch { return {}; } })())} `; } @@ -404,14 +449,14 @@ const detail = getDetailPreview(decoded); return ` - ${region ? `${region}` : '—'} + ${region ? `${region}` : '—'} ${timeAgo(p.timestamp)} ${truncate(p.hash || String(p.id), 8)} - ${size}B + ${size}B ${typeName} ${truncate(p.observer_name || p.observer_id || '—', 16)} ${pathStr} - + ${detail} `; }).join(''); @@ -664,9 +709,10 @@ // BYOP modal — decode only, no DB injection function showBYOP() { + const triggerBtn = document.querySelector('[data-action="pkt-byop"]'); const overlay = document.createElement('div'); overlay.className = 'modal-overlay'; - overlay.innerHTML = '