Add real-time upload progress tracking, mobile-optimized manage_group page with player status cards

Features:
- Real-time upload progress tracking with AJAX polling and session-based monitoring
- API endpoint /api/upload_progress/<session_id> for progress updates
- Video conversion progress tracking with background threads
- Mobile-responsive design for manage_group page
- Player status cards with feedback, playlist sync, and last activity
- Bootstrap Icons integration throughout UI
- Responsive layout (1/4 group info, 3/4 players on desktop)
- Video thumbnails with play icon, image thumbnails in media lists
- Bulk selection and delete for group media
- Enhanced logging for video conversion debugging
This commit is contained in:
DigiServer Developer
2025-11-03 16:09:18 +02:00
parent 52344a27a6
commit d0fbfe25b3
5 changed files with 720 additions and 160 deletions

View File

@@ -57,7 +57,7 @@
{% endif %}
<h1 class="mb-0">Upload Content</h1>
</div>
<form id="upload-form" action="{{ url_for('upload_content') }}" method="post" enctype="multipart/form-data" onsubmit="showStatusModal()">
<form id="upload-form" action="{{ url_for('upload_content') }}" method="post" enctype="multipart/form-data" onsubmit="handleFormSubmit(event)">
<input type="hidden" name="return_url" value="{{ return_url }}">
<div class="row">
<div class="col-md-6 col-12">
@@ -223,82 +223,127 @@
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js"></script>
<script>
let progressInterval = null;
let sessionId = null;
let statusModal = null;
let returnUrl = '{{ return_url }}';
// Generate unique session ID for this upload
function generateSessionId() {
return 'upload_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
}
function handleFormSubmit(event) {
event.preventDefault(); // Prevent default form submission
// Generate session ID and add it to the form
sessionId = generateSessionId();
const form = document.getElementById('upload-form');
let sessionInput = document.getElementById('session_id_input');
if (!sessionInput) {
sessionInput = document.createElement('input');
sessionInput.type = 'hidden';
sessionInput.name = 'session_id';
sessionInput.id = 'session_id_input';
form.appendChild(sessionInput);
}
sessionInput.value = sessionId;
// Show modal
showStatusModal();
// Submit form via AJAX
const formData = new FormData(form);
fetch(form.action, {
method: 'POST',
body: formData
})
.then(response => {
if (!response.ok) {
throw new Error('Upload failed');
}
console.log('Form submitted successfully');
// Don't redirect yet - keep polling until status is complete
})
.catch(error => {
console.error('Form submission error:', error);
if (upload_progress && sessionId) {
upload_progress[sessionId] = {
'status': 'error',
'progress': 0,
'message': 'Upload failed: ' + error.message
};
}
});
}
function showStatusModal() {
console.log("Processing popup triggered");
const statusModal = new bootstrap.Modal(document.getElementById('statusModal'));
statusModal = new bootstrap.Modal(document.getElementById('statusModal'));
statusModal.show();
// Update status message based on media type
const mediaType = document.getElementById('media_type').value;
{% if system_info %}
// Start system monitoring updates
startModalSystemMonitoring();
{% endif %}
// Start polling progress
pollUploadProgress();
}
function pollUploadProgress() {
const statusMessage = document.getElementById('status-message');
const progressBar = document.getElementById('progress-bar');
let progress = 0;
if (mediaType === 'video') {
statusMessage.textContent = 'Uploading video...';
// Stage 1: Uploading (0-40%)
let uploadInterval = setInterval(() => {
progress += 8;
if (progress >= 40) {
clearInterval(uploadInterval);
progress = 40;
progressBar.style.width = `${progress}%`;
progressBar.setAttribute('aria-valuenow', progress);
// Stage 2: Converting
statusMessage.textContent = 'Converting video to standard format (29.97 fps, 1080p)...';
let convertProgress = 40;
let convertInterval = setInterval(() => {
convertProgress += 3;
progressBar.style.width = `${convertProgress}%`;
progressBar.setAttribute('aria-valuenow', convertProgress);
if (convertProgress >= 100) {
clearInterval(convertInterval);
statusMessage.textContent = 'Video uploaded and converted successfully!';
// Stop system monitoring updates
{% if system_info %}
stopModalSystemMonitoring();
{% endif %}
document.querySelector('[data-bs-dismiss="modal"]').disabled = false;
// Poll every 500ms for real-time updates
progressInterval = setInterval(() => {
fetch(`/api/upload_progress/${sessionId}`)
.then(response => response.json())
.then(data => {
console.log('Progress update:', data);
// Update progress bar
progressBar.style.width = `${data.progress}%`;
progressBar.setAttribute('aria-valuenow', data.progress);
// Update status message
statusMessage.textContent = data.message;
// If complete or error, stop polling and enable close button
if (data.status === 'complete' || data.status === 'error') {
clearInterval(progressInterval);
progressInterval = null;
{% if system_info %}
stopModalSystemMonitoring();
{% endif %}
const closeBtn = document.querySelector('[data-bs-dismiss="modal"]');
closeBtn.disabled = false;
// Change progress bar color based on status
if (data.status === 'complete') {
progressBar.classList.remove('progress-bar-animated');
progressBar.classList.add('bg-success');
// Auto-close after 2 seconds and redirect
setTimeout(() => {
statusModal.hide();
window.location.href = returnUrl;
}, 2000);
} else if (data.status === 'error') {
progressBar.classList.remove('progress-bar-animated', 'progress-bar-striped');
progressBar.classList.add('bg-danger');
}
}, 600);
} else {
progressBar.style.width = `${progress}%`;
progressBar.setAttribute('aria-valuenow', progress);
}
}, 400);
} else {
// Default for other media types
switch(mediaType) {
case 'image':
statusMessage.textContent = 'Uploading images...';
break;
case 'pdf':
statusMessage.textContent = 'Converting PDF to 4K images. This may take a while...';
break;
case 'ppt':
statusMessage.textContent = 'Converting PowerPoint to images (PPTX → PDF → Images). This may take 2-5 minutes...';
break;
default:
statusMessage.textContent = 'Uploading and processing your files. Please wait...';
}
// Simulate progress updates
let interval = setInterval(() => {
const increment = (mediaType === 'image') ? 20 : 5;
progress += increment;
if (progress >= 100) {
clearInterval(interval);
statusMessage.textContent = 'Files uploaded and processed successfully!';
{% if system_info %}
stopModalSystemMonitoring();
{% endif %}
document.querySelector('[data-bs-dismiss="modal"]').disabled = false;
} else {
progressBar.style.width = `${progress}%`;
progressBar.setAttribute('aria-valuenow', progress);
}
}, 500);
}
}
})
.catch(error => {
console.error('Error fetching progress:', error);
statusMessage.textContent = 'Error tracking upload progress';
});
}, 500); // Poll every 500ms
}
{% if system_info %}