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:
94
app/app.py
94
app/app.py
@@ -106,6 +106,10 @@ login_manager.login_view = 'login'
|
||||
|
||||
migrate = Migrate(app, db)
|
||||
|
||||
# Global dictionary to track upload progress
|
||||
# Format: {session_id: {'status': 'uploading/converting/complete', 'progress': 0-100, 'message': 'details', 'files_total': N, 'files_processed': N}}
|
||||
upload_progress = {}
|
||||
|
||||
@app.route('/api/player-feedback', methods=['POST'])
|
||||
def api_player_feedback():
|
||||
from datetime import datetime
|
||||
@@ -259,35 +263,72 @@ def logout():
|
||||
@admin_required
|
||||
def upload_content():
|
||||
if request.method == 'POST':
|
||||
import uuid
|
||||
|
||||
target_type = request.form.get('target_type')
|
||||
target_id = request.form.get('target_id')
|
||||
files = request.files.getlist('files')
|
||||
duration = int(request.form['duration'])
|
||||
return_url = request.form.get('return_url')
|
||||
media_type = request.form['media_type']
|
||||
session_id = request.form.get('session_id', str(uuid.uuid4()))
|
||||
|
||||
print(f"Target Type: {target_type}, Target ID: {target_id}, Media Type: {media_type}")
|
||||
print(f"Target Type: {target_type}, Target ID: {target_id}, Media Type: {media_type}, Session ID: {session_id}")
|
||||
|
||||
if not target_type or not target_id:
|
||||
flash('Please select a target type and target ID.', 'danger')
|
||||
return redirect(url_for('upload_content'))
|
||||
|
||||
# Initialize progress tracking
|
||||
upload_progress[session_id] = {
|
||||
'status': 'uploading',
|
||||
'progress': 0,
|
||||
'message': 'Starting upload...',
|
||||
'files_total': len(files),
|
||||
'files_processed': 0
|
||||
}
|
||||
|
||||
try:
|
||||
# Process uploaded files and get results
|
||||
results = process_uploaded_files(app, files, media_type, duration, target_type, target_id)
|
||||
results = process_uploaded_files(app, files, media_type, duration, target_type, target_id, upload_progress, session_id)
|
||||
|
||||
# Check for any failed uploads
|
||||
failed_files = [r for r in results if not r.get('success', True)]
|
||||
if failed_files:
|
||||
for failed in failed_files:
|
||||
flash(f"Error uploading {failed.get('filename', 'unknown file')}: {failed.get('message', 'Unknown error')}", 'warning')
|
||||
# Check if video conversion is happening in background
|
||||
if media_type == 'video':
|
||||
# For videos, don't mark as complete yet - background thread will do it
|
||||
# Status remains as "converting" set by the background thread
|
||||
flash('Video upload started. Conversion is in progress...', 'info')
|
||||
else:
|
||||
flash('All files uploaded and processed successfully!', 'success')
|
||||
# For non-videos (images, PDF, PPT), mark as complete
|
||||
upload_progress[session_id] = {
|
||||
'status': 'complete',
|
||||
'progress': 100,
|
||||
'message': 'All files processed successfully!',
|
||||
'files_total': len(files),
|
||||
'files_processed': len(files)
|
||||
}
|
||||
|
||||
# Check for any failed uploads
|
||||
failed_files = [r for r in results if not r.get('success', True)]
|
||||
if failed_files:
|
||||
for failed in failed_files:
|
||||
flash(f"Error uploading {failed.get('filename', 'unknown file')}: {failed.get('message', 'Unknown error')}", 'warning')
|
||||
else:
|
||||
flash('All files uploaded and processed successfully!', 'success')
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error in upload_content: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
# Mark as error
|
||||
upload_progress[session_id] = {
|
||||
'status': 'error',
|
||||
'progress': 0,
|
||||
'message': f'Upload failed: {str(e)}',
|
||||
'files_total': len(files),
|
||||
'files_processed': 0
|
||||
}
|
||||
|
||||
flash(f'Upload failed: {str(e)}', 'danger')
|
||||
|
||||
return redirect(return_url)
|
||||
@@ -714,6 +755,22 @@ def get_playlists():
|
||||
def media(filename):
|
||||
return send_from_directory(app.config['UPLOAD_FOLDER'], filename)
|
||||
|
||||
@app.route('/api/upload_progress/<session_id>', methods=['GET'])
|
||||
@login_required
|
||||
def get_upload_progress(session_id):
|
||||
"""
|
||||
API endpoint to get upload/conversion progress for a session.
|
||||
Returns JSON with status, progress percentage, and current message.
|
||||
"""
|
||||
progress_data = upload_progress.get(session_id, {
|
||||
'status': 'unknown',
|
||||
'progress': 0,
|
||||
'message': 'No active upload found',
|
||||
'files_total': 0,
|
||||
'files_processed': 0
|
||||
})
|
||||
return jsonify(progress_data)
|
||||
|
||||
@app.context_processor
|
||||
def inject_theme():
|
||||
if current_user.is_authenticated:
|
||||
@@ -740,13 +797,32 @@ def create_group():
|
||||
@login_required
|
||||
@admin_required
|
||||
def manage_group(group_id):
|
||||
from models.player_feedback import PlayerFeedback
|
||||
|
||||
group = Group.query.get_or_404(group_id)
|
||||
content = get_group_content(group_id)
|
||||
# Debug content ordering
|
||||
print("Group content positions before sorting:", [(c.id, c.file_name, c.position) for c in content])
|
||||
content = sorted(content, key=lambda c: c.position)
|
||||
print("Group content positions after sorting:", [(c.id, c.file_name, c.position) for c in content])
|
||||
return render_template('manage_group.html', group=group, content=content)
|
||||
|
||||
# Fetch player feedback for all players in the group
|
||||
players_status = []
|
||||
for player in group.players:
|
||||
player_feedback = PlayerFeedback.query.filter_by(player_name=player.username)\
|
||||
.order_by(PlayerFeedback.timestamp.desc())\
|
||||
.limit(5)\
|
||||
.all()
|
||||
players_status.append({
|
||||
'player': player,
|
||||
'feedback': player_feedback,
|
||||
'server_playlist_version': player.playlist_version
|
||||
})
|
||||
|
||||
return render_template('manage_group.html',
|
||||
group=group,
|
||||
content=content,
|
||||
players_status=players_status)
|
||||
|
||||
@app.route('/group/<int:group_id>/edit', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
|
||||
Reference in New Issue
Block a user