const scanButton = document.querySelector('#scanButton'); const buttonText = document.querySelector('#buttonText'); const results = document.querySelector('#results'); const empty = document.querySelector('#empty'); const errorBox = document.querySelector('#error'); const list = document.querySelector('#cameraList'); const title = document.querySelector('#resultTitle'); const meta = document.querySelector('#scanMeta'); const errorText = document.querySelector('#errorText'); const exportButton = document.querySelector('#exportCsvButton'); const actionStatus = document.querySelector('#actionStatus'); let currentCameras = []; function hidePanels() { results.hidden = true; empty.hidden = true; errorBox.hidden = true; actionStatus.textContent = ''; } function cameraCard(camera) { const article = document.createElement('article'); article.className = 'camera'; const details = document.createElement('div'); const key = document.createElement('div'); key.className = 'camera-key'; key.textContent = camera.alta_key; const host = document.createElement('a'); host.className = 'camera-host'; host.href = camera.web_ui_url; host.target = '_blank'; host.rel = 'noopener noreferrer'; host.textContent = camera.hostname; host.title = 'Open camera Web UI'; details.append(key, host); const network = document.createElement('div'); network.className = 'network-data'; const ip = document.createElement('span'); ip.className = 'camera-ip'; ip.textContent = camera.ip_address; const networkLabel = document.createElement('span'); networkLabel.className = 'network-label'; networkLabel.textContent = 'HTTPS · mDNS'; network.append(ip, networkLabel); const copy = document.createElement('button'); copy.className = 'copy-button'; copy.type = 'button'; copy.dataset.altaKey = camera.alta_key; copy.textContent = 'Copy key'; copy.setAttribute('aria-label', `Copy Alta Key ${camera.alta_key}`); article.append(details, network, copy); return article; } async function copyText(text) { if (window.altaDiscovery) { await window.altaDiscovery.copyText(text); return; } if (navigator.clipboard && window.isSecureContext) { await navigator.clipboard.writeText(text); return; } const textarea = document.createElement('textarea'); textarea.value = text; textarea.setAttribute('readonly', ''); textarea.style.position = 'fixed'; textarea.style.opacity = '0'; document.body.append(textarea); textarea.select(); const copied = document.execCommand('copy'); textarea.remove(); if (!copied) throw new Error('Clipboard access was blocked by the browser.'); } list.addEventListener('click', async (event) => { const copyButton = event.target.closest('.copy-button'); if (!copyButton) return; try { await copyText(copyButton.dataset.altaKey); copyButton.textContent = 'Copied'; copyButton.classList.add('copied'); actionStatus.textContent = `${copyButton.dataset.altaKey} copied to clipboard.`; window.setTimeout(() => { copyButton.textContent = 'Copy key'; copyButton.classList.remove('copied'); }, 1800); } catch (error) { actionStatus.textContent = error.message; } }); exportButton.addEventListener('click', async () => { if (window.altaDiscovery) { try { const result = await window.altaDiscovery.exportCsv(); actionStatus.textContent = result.canceled ? 'CSV export canceled.' : 'CSV exported successfully.'; } catch (error) { actionStatus.textContent = error.message; } return; } const csv = AltaExportUtils.buildCsv(currentCameras); const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = 'alta-camera-keys.csv'; document.body.append(link); link.click(); link.remove(); URL.revokeObjectURL(url); actionStatus.textContent = 'CSV exported with Alta Keys and camera Web UI URLs.'; }); scanButton.addEventListener('click', async () => { hidePanels(); currentCameras = []; scanButton.disabled = true; scanButton.classList.add('scanning'); buttonText.textContent = 'Scanning…'; try { let data; if (window.altaDiscovery) { data = await window.altaDiscovery.scan(); } else { const response = await fetch('/api/scan', { cache: 'no-store' }); data = await response.json(); if (!response.ok) throw new Error(data.error || 'The scanner returned an error.'); } if (!data.ok) throw new Error(data.error || 'The scanner returned an error.'); currentCameras = data.cameras; if (!currentCameras.length) { empty.hidden = false; } else { list.replaceChildren(...currentCameras.map(cameraCard)); title.textContent = `${data.count} camera${data.count === 1 ? '' : 's'} found`; meta.textContent = `Scan completed in ${data.duration_seconds}s`; results.hidden = false; } } catch (error) { errorText.textContent = error.message; errorBox.hidden = false; } finally { scanButton.disabled = false; scanButton.classList.remove('scanning'); buttonText.textContent = 'Discover again'; } });