Files
SyncPlayer/static/admin.js

160 lines
6.2 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(() => {
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 its 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();
})();