Compare commits
2 Commits
1.3
...
45bc1c2a7a
| Author | SHA1 | Date | |
|---|---|---|---|
| 45bc1c2a7a | |||
| e6f0ced475 |
@@ -22,7 +22,7 @@ import sys
|
|||||||
from typing import Iterable, Sequence
|
from typing import Iterable, Sequence
|
||||||
|
|
||||||
|
|
||||||
DOCKER_REPO = "git.alphen.cloud/bramval/SyncPlayer"
|
DOCKER_REPO = "git.alphen.cloud/bramval/syncplayer"
|
||||||
|
|
||||||
|
|
||||||
def _run(cmd: Sequence[str], *, dry_run: bool = False) -> None:
|
def _run(cmd: Sequence[str], *, dry_run: bool = False) -> None:
|
||||||
|
|||||||
126
static/admin.js
126
static/admin.js
@@ -1,5 +1,4 @@
|
|||||||
(() => {
|
(() => {
|
||||||
function initDashboardLiveUpdates() {
|
|
||||||
// Optional live updates on dashboard
|
// Optional live updates on dashboard
|
||||||
if (!document.getElementById('tbl-displays')) return;
|
if (!document.getElementById('tbl-displays')) return;
|
||||||
|
|
||||||
@@ -31,129 +30,4 @@
|
|||||||
if (!el) return;
|
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>`;
|
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();
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -1,25 +1,11 @@
|
|||||||
{% extends 'admin/base.html' %}
|
{% extends 'admin/base.html' %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h4>Upload Video</h4>
|
<h4>Upload Video</h4>
|
||||||
<form id="video-upload-form" method="post" enctype="multipart/form-data" class="mt-3" style="max-width: 640px;">
|
<form method="post" enctype="multipart/form-data" class="mt-3" style="max-width: 640px;">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<input class="form-control" type="file" name="file" accept="video/mp4,video/webm" required />
|
<input class="form-control" type="file" name="file" accept="video/mp4,video/webm" required />
|
||||||
<div class="form-text">MP4 (H264 baseline/main) recommended for SoC players.</div>
|
<div class="form-text">MP4 (H264 baseline/main) recommended for SoC players.</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="upload-progress" class="progress mb-2 d-none" aria-label="Upload progress">
|
|
||||||
<div
|
|
||||||
id="upload-progress-bar"
|
|
||||||
class="progress-bar progress-bar-striped"
|
|
||||||
role="progressbar"
|
|
||||||
style="width: 0%"
|
|
||||||
aria-valuenow="0"
|
|
||||||
aria-valuemin="0"
|
|
||||||
aria-valuemax="100"
|
|
||||||
>0%</div>
|
|
||||||
</div>
|
|
||||||
<div id="upload-status" class="form-text mt-2"></div>
|
|
||||||
|
|
||||||
<button class="btn btn-primary" type="submit">Upload</button>
|
<button class="btn btn-primary" type="submit">Upload</button>
|
||||||
<a class="btn btn-link" href="{{ url_for('admin.videos_list') }}">Cancel</a>
|
<a class="btn btn-link" href="{{ url_for('admin.videos_list') }}">Cancel</a>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
Reference in New Issue
Block a user