160 lines
6.2 KiB
JavaScript
160 lines
6.2 KiB
JavaScript
(() => {
|
||
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 = `<div><strong>${msg.event_name}</strong> (#${msg.event_id})</div><div>Start: <code>${msg.start_time_ms.toFixed(3)}</code></div>`;
|
||
});
|
||
}
|
||
|
||
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();
|
||
})();
|