import { api } from '../api.js'; import { on, off, requestScreenshot } from '../socket.js'; import { showToast } from '../components/toast.js'; let statusHandler = null; let screenshotHandler = null; let refreshInterval = null; function formatTimeAgo(timestamp) { if (!timestamp) return 'Never'; const seconds = Math.floor(Date.now() / 1000 - timestamp); if (seconds < 60) return 'Just now'; if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`; if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`; return `${Math.floor(seconds / 86400)}d ago`; } function formatBytes(mb) { if (mb === null || mb === undefined) return '--'; if (mb >= 1024) return `${(mb / 1024).toFixed(1)} GB`; return `${mb} MB`; } function renderDeviceCard(device) { const token = localStorage.getItem('token'); const screenshotUrl = device.screenshot_path ? `/api/devices/${device.id}/screenshot?t=${device.screenshot_at || ''}&token=${token}` : null; return `
${screenshotUrl ? `Screenshot` : `
No preview available
` }
${device.status === 'provisioning' ? 'Awaiting Pairing' : device.status}
${device.status === 'provisioning' && device.pairing_code ? `
${device.pairing_code}
` : ''}
${device.name}
${device.owner_name || device.owner_email ? `
${device.owner_name || device.owner_email}
` : ''}
${formatTimeAgo(device.last_heartbeat)}
${device.battery_level !== null && device.battery_level !== undefined ? `
${device.battery_level}%
` : ''} ${device.wifi_rssi ? `
${device.wifi_rssi} dBm
` : ''} ${device.storage_free_mb ? `
${formatBytes(device.storage_free_mb)} free
` : ''}
`; } export function render(container) { container.innerHTML = `

Loading displays...

`; const addBtn = container.querySelector('#addDeviceBtn'); addBtn.addEventListener('click', () => { document.getElementById('addDeviceModal').style.display = 'flex'; document.getElementById('pairingCodeInput').value = ''; document.getElementById('deviceNameInput').value = ''; document.getElementById('pairingCodeInput').focus(); }); // Search and filter document.getElementById('deviceSearch').oninput = () => filterDevices(); document.getElementById('deviceFilter').onchange = () => filterDevices(); function filterDevices() { const search = document.getElementById('deviceSearch').value.toLowerCase(); const status = document.getElementById('deviceFilter').value; document.querySelectorAll('.device-card').forEach(card => { const name = card.querySelector('.device-card-name')?.textContent.toLowerCase() || ''; const deviceStatus = card.querySelector('.device-card-status span:last-child')?.textContent || ''; const matchSearch = !search || name.includes(search); const matchStatus = !status || deviceStatus === status; card.style.display = (matchSearch && matchStatus) ? '' : 'none'; }); } // Setup pairing const pairBtn = document.getElementById('pairDeviceBtn'); pairBtn.onclick = async () => { const code = document.getElementById('pairingCodeInput').value.trim(); const name = document.getElementById('deviceNameInput').value.trim(); if (!code || code.length !== 6) { showToast('Enter a valid 6-digit pairing code', 'error'); return; } try { await api.pairDevice(code, name || undefined); document.getElementById('addDeviceModal').style.display = 'none'; showToast('Display paired successfully!', 'success'); loadDevices(); } catch (err) { showToast(err.message, 'error'); } }; // Load devices loadDevices(); // Real-time updates statusHandler = (data) => { const card = document.querySelector(`[data-device-id="${data.device_id}"]`); if (card) { const statusEl = card.querySelector('.device-card-status'); statusEl.innerHTML = `${data.status}`; } }; screenshotHandler = (data) => { const preview = document.getElementById(`preview-${data.device_id}`); if (preview) { const imgSrc = data.image_data || (data.url + '&token=' + localStorage.getItem('token')); const img = preview.querySelector('img'); if (img) { img.src = imgSrc; } else { preview.innerHTML = `Screenshot` + preview.querySelector('.device-card-status').outerHTML; } } }; // Device added/removed - refresh the whole list const deviceAddedHandler = () => loadDevices(); const deviceRemovedHandler = () => loadDevices(); on('device-status', statusHandler); on('screenshot-ready', screenshotHandler); on('device-added', deviceAddedHandler); on('device-removed', deviceRemovedHandler); // Request fresh screenshots on load setTimeout(() => { document.querySelectorAll('.device-card').forEach(card => { requestScreenshot(card.dataset.deviceId); }); }, 2000); // Refresh screenshots periodically refreshInterval = setInterval(() => { document.querySelectorAll('.device-card').forEach(card => { requestScreenshot(card.dataset.deviceId); }); }, 30000); } async function loadDevices() { const grid = document.getElementById('deviceGrid'); if (!grid) return; try { const devices = await api.getDevices(); // Stats cards const online = devices.filter(d => d.status === 'online').length; const offline = devices.filter(d => d.status === 'offline').length; const provisioning = devices.filter(d => d.status === 'provisioning').length; const statsEl = document.getElementById('dashStats'); if (statsEl) { statsEl.innerHTML = `
Total Displays
${devices.length}
Online
${online}
Offline
${offline}
${provisioning > 0 ? `
Awaiting Pairing
${provisioning}
` : ''} `; } if (devices.length === 0) { grid.innerHTML = `

No displays yet

Install the ScreenTinker app on your Apolosign TV and pair it using the button above.

`; } else { grid.innerHTML = devices.map(renderDeviceCard).join(''); } } catch (err) { grid.innerHTML = `

Failed to load displays

${err.message}

`; } } export function cleanup() { if (statusHandler) off('device-status', statusHandler); if (screenshotHandler) off('screenshot-ready', screenshotHandler); off('device-added', () => {}); off('device-removed', () => {}); if (refreshInterval) clearInterval(refreshInterval); statusHandler = null; screenshotHandler = null; refreshInterval = null; }