(() => { function initDashboardLiveUpdates() { // Optional live updates on dashboard if (!document.getElementById('tbl-displays')) return; const socket = io({ transports: ['websocket'], upgrade: false }); socket.on('connect', () => { socket.emit('admin_join'); }); function updateRow(publicId, patch) { const row = document.querySelector(`tr[data-public-id="${publicId}"]`); if (!row) return; if (patch.is_online !== undefined) row.querySelector('.st').textContent = patch.is_online ? 'online' : 'offline'; if (patch.last_seen) row.querySelector('.ls').textContent = patch.last_seen; if (patch.latency_ms !== undefined) row.querySelector('.lat').textContent = patch.latency_ms ? `${patch.latency_ms.toFixed(1)}ms` : ''; if (patch.offset_ms !== undefined) row.querySelector('.off').textContent = patch.offset_ms ? `${patch.offset_ms.toFixed(1)}ms` : ''; } socket.on('admin_snapshot', (msg) => { const live = msg.live || {}; Object.keys(live).forEach(pid => updateRow(pid, { ...live[pid], is_online: true })); }); socket.on('admin_display_update', (msg) => { updateRow(msg.public_id, msg); }); socket.on('admin_event_triggered', (msg) => { const el = document.getElementById('active-event'); if (!el) return; el.innerHTML = `
${msg.event_name} (#${msg.event_id})
Start: ${msg.start_time_ms.toFixed(3)}
`; }); } function initVideoUploadProgress() { const form = document.getElementById('video-upload-form'); if (!form) return; const fileInput = form.querySelector('input[type="file"][name="file"]'); const submitBtn = form.querySelector('button[type="submit"]'); const progressWrap = document.getElementById('upload-progress'); const progressBar = document.getElementById('upload-progress-bar') || (progressWrap ? progressWrap.querySelector('.progress-bar') : null); const statusEl = document.getElementById('upload-status'); // Tiny boot indicator so it’s obvious the JS is active on this page. setStatus('Ready to upload.', 'form-text mt-2 text-muted'); if (fileInput) { fileInput.addEventListener('change', () => { if (fileInput.files && fileInput.files.length > 0) { showProgress(); setProgress(0, { indeterminate: false, variant: 'info' }); setStatus(`Selected: ${fileInput.files[0].name}`, 'form-text mt-2 text-muted'); } }); } function setStatus(msg, cls) { if (!statusEl) return; statusEl.textContent = msg || ''; statusEl.className = cls ? cls : 'form-text mt-2'; } function showProgress() { if (!progressWrap) return; progressWrap.classList.remove('d-none'); progressWrap.classList.add('d-block'); } function setProgress(percent, opts = {}) { if (!progressWrap || !progressBar) return; const pct = Math.max(0, Math.min(100, Math.round(percent))); progressBar.setAttribute('aria-valuenow', String(pct)); progressBar.style.width = `${pct}%`; progressBar.textContent = `${pct}%`; if (opts.indeterminate) { progressBar.classList.add('progress-bar-striped', 'progress-bar-animated'); } else { progressBar.classList.remove('progress-bar-animated'); // keep striped (optional) to make it visible progressBar.classList.add('progress-bar-striped'); } if (opts.variant) { progressBar.classList.remove('bg-success', 'bg-danger', 'bg-warning', 'bg-info'); progressBar.classList.add(`bg-${opts.variant}`); } } form.addEventListener('submit', (e) => { e.preventDefault(); if (!fileInput || !fileInput.files || fileInput.files.length === 0) { setStatus('Please select a file to upload.', 'form-text mt-2 text-danger'); return; } // Reset UI showProgress(); setProgress(0, { indeterminate: true, variant: 'info' }); setStatus('Uploading…', 'form-text mt-2'); if (submitBtn) submitBtn.disabled = true; if (fileInput) fileInput.disabled = true; const xhr = new XMLHttpRequest(); const targetUrl = form.getAttribute('action') || window.location.href; xhr.open('POST', targetUrl, true); xhr.upload.addEventListener('progress', (evt) => { if (!evt.lengthComputable) { setProgress(0, { indeterminate: true, variant: 'info' }); return; } const percent = (evt.loaded / evt.total) * 100; setProgress(percent, { indeterminate: false, variant: 'info' }); }); xhr.upload.addEventListener('load', () => { // Upload bytes are done; server might still be processing. setProgress(100, { indeterminate: true, variant: 'info' }); setStatus('Processing on server…', 'form-text mt-2'); }); xhr.addEventListener('load', () => { if (xhr.status >= 200 && xhr.status < 300) { setProgress(100, { indeterminate: false, variant: 'success' }); setStatus('Upload complete. Redirecting…', 'form-text mt-2 text-success'); window.location.assign(xhr.responseURL || targetUrl); } else { setProgress(100, { indeterminate: false, variant: 'danger' }); setStatus(`Upload failed (HTTP ${xhr.status}).`, 'form-text mt-2 text-danger'); if (submitBtn) submitBtn.disabled = false; if (fileInput) fileInput.disabled = false; } }); xhr.addEventListener('error', () => { setProgress(100, { indeterminate: false, variant: 'danger' }); setStatus('Upload failed (network error).', 'form-text mt-2 text-danger'); if (submitBtn) submitBtn.disabled = false; if (fileInput) fileInput.disabled = false; }); xhr.addEventListener('abort', () => { setProgress(0, { indeterminate: false, variant: 'warning' }); setStatus('Upload aborted.', 'form-text mt-2 text-warning'); if (submitBtn) submitBtn.disabled = false; if (fileInput) fileInput.disabled = false; }); const data = new FormData(form); xhr.send(data); }); } initDashboardLiveUpdates(); initVideoUploadProgress(); })();