154 lines
5.1 KiB
JavaScript
154 lines
5.1 KiB
JavaScript
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';
|
|
}
|
|
});
|