diff --git a/app/blueprints/admin.py b/app/blueprints/admin.py index e13b1c2..1075eaa 100644 --- a/app/blueprints/admin.py +++ b/app/blueprints/admin.py @@ -409,7 +409,7 @@ def delete_leftover_images(): try: # Find all leftover image content leftover_images = db.session.query(Content).filter( - Content.media_type == 'image', + Content.content_type == 'image', ~Content.id.in_( db.session.query(playlist_content.c.content_id) ) @@ -421,8 +421,8 @@ def delete_leftover_images(): for content in leftover_images: try: # Delete physical file - if content.file_path: - file_path = os.path.join(current_app.config['UPLOAD_FOLDER'], content.file_path) + if content.filename: + file_path = os.path.join(current_app.config['UPLOAD_FOLDER'], content.filename) if os.path.exists(file_path): os.remove(file_path) @@ -430,7 +430,7 @@ def delete_leftover_images(): db.session.delete(content) deleted_count += 1 except Exception as e: - errors.append(f"Error deleting {content.file_path}: {str(e)}") + errors.append(f"Error deleting {content.filename}: {str(e)}") db.session.commit() @@ -455,7 +455,7 @@ def delete_leftover_videos(): try: # Find all leftover video content leftover_videos = db.session.query(Content).filter( - Content.media_type == 'video', + Content.content_type == 'video', ~Content.id.in_( db.session.query(playlist_content.c.content_id) ) @@ -467,8 +467,8 @@ def delete_leftover_videos(): for content in leftover_videos: try: # Delete physical file - if content.file_path: - file_path = os.path.join(current_app.config['UPLOAD_FOLDER'], content.file_path) + if content.filename: + file_path = os.path.join(current_app.config['UPLOAD_FOLDER'], content.filename) if os.path.exists(file_path): os.remove(file_path) @@ -476,7 +476,7 @@ def delete_leftover_videos(): db.session.delete(content) deleted_count += 1 except Exception as e: - errors.append(f"Error deleting {content.file_path}: {str(e)}") + errors.append(f"Error deleting {content.filename}: {str(e)}") db.session.commit() @@ -500,8 +500,8 @@ def delete_single_leftover(content_id): content = Content.query.get_or_404(content_id) # Delete physical file - if content.file_path: - file_path = os.path.join(current_app.config['UPLOAD_FOLDER'], content.file_path) + if content.filename: + file_path = os.path.join(current_app.config['UPLOAD_FOLDER'], content.filename) if os.path.exists(file_path): os.remove(file_path) @@ -509,7 +509,7 @@ def delete_single_leftover(content_id): db.session.delete(content) db.session.commit() - flash(f'Successfully deleted {content.file_path}', 'success') + flash(f'Successfully deleted {content.filename}', 'success') except Exception as e: db.session.rollback() diff --git a/app/blueprints/content.py b/app/blueprints/content.py index e2fd531..b7c7a01 100644 --- a/app/blueprints/content.py +++ b/app/blueprints/content.py @@ -19,15 +19,88 @@ content_bp = Blueprint('content', __name__, url_prefix='/content') def content_list(): """Main playlist management page.""" playlists = Playlist.query.order_by(Playlist.created_at.desc()).all() - media_files = Content.query.order_by(Content.uploaded_at.desc()).all() + media_files = Content.query.order_by(Content.uploaded_at.desc()).limit(3).all() # Only last 3 + total_media_count = Content.query.count() # Total count for display players = Player.query.order_by(Player.name).all() return render_template('content/content_list_new.html', playlists=playlists, media_files=media_files, + total_media_count=total_media_count, players=players) +@content_bp.route('/media-library') +@login_required +def media_library(): + """View all media files in the library.""" + media_files = Content.query.order_by(Content.uploaded_at.desc()).all() + + # Group by content type + images = [m for m in media_files if m.content_type == 'image'] + videos = [m for m in media_files if m.content_type == 'video'] + pdfs = [m for m in media_files if m.content_type == 'pdf'] + presentations = [m for m in media_files if m.content_type == 'pptx'] + others = [m for m in media_files if m.content_type not in ['image', 'video', 'pdf', 'pptx']] + + return render_template('content/media_library.html', + media_files=media_files, + images=images, + videos=videos, + pdfs=pdfs, + presentations=presentations, + others=others) + + +@content_bp.route('/media//delete', methods=['POST']) +@login_required +def delete_media(media_id: int): + """Delete a media file and remove it from all playlists.""" + try: + media = Content.query.get_or_404(media_id) + filename = media.filename + + # Get all playlists containing this media + affected_playlists = list(media.playlists.all()) + + # Delete physical file + file_path = os.path.join(current_app.config['UPLOAD_FOLDER'], media.filename) + if os.path.exists(file_path): + os.remove(file_path) + log_action('info', f'Deleted physical file: {filename}') + + # Remove from all playlists (this will cascade properly) + db.session.delete(media) + + # Increment version for all affected playlists + for playlist in affected_playlists: + playlist.version += 1 + log_action('info', f'Playlist "{playlist.name}" version updated to {playlist.version} (media removed)') + + db.session.commit() + + # Clear cache for affected playlists + from app.blueprints.players import get_player_playlist + from app.extensions import cache + for playlist in affected_playlists: + for player in playlist.players: + cache.delete_memoized(get_player_playlist, player.id) + + if affected_playlists: + flash(f'Deleted "{filename}" and removed from {len(affected_playlists)} playlist(s). Playlist versions updated.', 'success') + else: + flash(f'Deleted "{filename}" successfully.', 'success') + + log_action('info', f'Media deleted: {filename} (affected {len(affected_playlists)} playlists)') + + except Exception as e: + db.session.rollback() + log_action('error', f'Error deleting media: {str(e)}') + flash(f'Error deleting media: {str(e)}', 'danger') + + return redirect(url_for('content.media_library')) + + @content_bp.route('/playlist/create', methods=['POST']) @login_required def create_playlist(): diff --git a/app/templates/content/content_list_new.html b/app/templates/content/content_list_new.html index 35723fd..517022d 100644 --- a/app/templates/content/content_list_new.html +++ b/app/templates/content/content_list_new.html @@ -249,6 +249,12 @@ background: #1a202c !important; color: #e2e8f0; } + + /* Dark mode for upload section */ + body.dark-mode .card > div[style*="background: #f8f9fa"] { + background: #2d3748 !important; + border: 1px solid #4a5568; + }
@@ -258,18 +264,17 @@
- +

- Playlists + Create New Playlist

-
-

Create New Playlist

+
- -
- - -

Existing Playlists

-
- {% if playlists %} - {% for playlist in playlists %} -
-
-

{{ playlist.name }}

-
- 📊 {{ playlist.content_count }} items | - 👥 {{ playlist.player_count }} players | - 🔄 v{{ playlist.version }} -
-
-
- - ✏️ Manage - -
- -
-
-
- {% endfor %} - {% else %} -
- -

No playlists yet. Create your first playlist above!

-
- {% endif %} -
@@ -356,9 +320,10 @@

- 📚 Available Media ({{ media_files|length }}) + 📚 Last 3 Added Media + Total: {{ total_media_count }}

-
+
{% if media_files %} {% for media in media_files %}
@@ -393,6 +358,63 @@
{% endif %}
+ + + {% if total_media_count > 3 %} + + {% endif %} +
+
+ + +
+
+

+ + Existing Playlists +

+
+ +
+ {% if playlists %} + {% for playlist in playlists %} +
+
+

{{ playlist.name }}

+
+ 📊 {{ playlist.content_count }} items | + 👥 {{ playlist.player_count }} players | + 🔄 v{{ playlist.version }} +
+
+
+ + ✏️ Manage + +
+ +
+
+
+ {% endfor %} + {% else %} +
+ +

No playlists yet. Create your first playlist above!

+
+ {% endif %}
diff --git a/app/templates/content/media_library.html b/app/templates/content/media_library.html new file mode 100644 index 0000000..41d119f --- /dev/null +++ b/app/templates/content/media_library.html @@ -0,0 +1,539 @@ +{% extends "base.html" %} + +{% block title %}Media Library - DigiServer v2{% endblock %} + +{% block content %} + + +
+

+ + 📚 Media Library +

+ + + Back to Playlists + +
+ + +
+
+
{{ media_files|length }}
+
Total Files
+
+
+
{{ images|length }}
+
Images
+
+
+
{{ videos|length }}
+
Videos
+
+
+
{{ pdfs|length }}
+
PDFs
+
+
+
{{ presentations|length }}
+
Presentations
+
+
+ + + + + +{% if images %} +
+ 📷 +

Images ({{ images|length }})

+
+
+ {% for media in images %} +
+ +
+ {{ media.filename }} +
+
{{ media.filename }}
+
+ Image +
{{ "%.1f"|format(media.file_size_mb) }} MB
+
{{ media.uploaded_at.strftime('%Y-%m-%d') }}
+ {% if media.playlists.count() > 0 %} +
📋 In {{ media.playlists.count() }} playlist(s)
+ {% else %} +
✓ Not in use
+ {% endif %} +
+
+ {% endfor %} +
+{% endif %} + + +{% if videos %} +
+ 🎥 +

Videos ({{ videos|length }})

+
+
+ {% for media in videos %} +
+ +
+ 🎥 +
+
{{ media.filename }}
+
+ Video +
{{ "%.1f"|format(media.file_size_mb) }} MB
+
{{ media.uploaded_at.strftime('%Y-%m-%d') }}
+ {% if media.playlists.count() > 0 %} +
📋 In {{ media.playlists.count() }} playlist(s)
+ {% else %} +
✓ Not in use
+ {% endif %} +
+
+ {% endfor %} +
+{% endif %} + + +{% if pdfs %} +
+ 📄 +

PDFs ({{ pdfs|length }})

+
+
+ {% for media in pdfs %} +
+ +
+ 📄 +
+
{{ media.filename }}
+
+ PDF +
{{ "%.1f"|format(media.file_size_mb) }} MB
+
{{ media.uploaded_at.strftime('%Y-%m-%d') }}
+ {% if media.playlists.count() > 0 %} +
📋 In {{ media.playlists.count() }} playlist(s)
+ {% else %} +
✓ Not in use
+ {% endif %} +
+
+ {% endfor %} +
+{% endif %} + + +{% if presentations %} +
+ 📊 +

Presentations ({{ presentations|length }})

+
+
+ {% for media in presentations %} +
+ +
+ 📊 +
+
{{ media.filename }}
+
+ PPTX +
{{ "%.1f"|format(media.file_size_mb) }} MB
+
{{ media.uploaded_at.strftime('%Y-%m-%d') }}
+ {% if media.playlists.count() > 0 %} +
📋 In {{ media.playlists.count() }} playlist(s)
+ {% else %} +
✓ Not in use
+ {% endif %} +
+
+ {% endfor %} +
+{% endif %} + + +{% if others %} +
+ 📁 +

Other Files ({{ others|length }})

+
+
+ {% for media in others %} +
+ +
+ 📁 +
+
{{ media.filename }}
+
+ {{ media.content_type }} +
{{ "%.1f"|format(media.file_size_mb) }} MB
+
{{ media.uploaded_at.strftime('%Y-%m-%d') }}
+ {% if media.playlists.count() > 0 %} +
📋 In {{ media.playlists.count() }} playlist(s)
+ {% else %} +
✓ Not in use
+ {% endif %} +
+
+ {% endfor %} +
+{% endif %} + +{% if not media_files %} +
+
📭
+

No Media Files Yet

+

Start by uploading your first media file!

+ + + Upload Media + +
+{% endif %} + + + + + + + + +{% endblock %} diff --git a/app/templates/players/manage_player.html b/app/templates/players/manage_player.html index 988d4c8..b350280 100644 --- a/app/templates/players/manage_player.html +++ b/app/templates/players/manage_player.html @@ -211,13 +211,52 @@ body.dark-mode .playlist-stats > div > div:first-child { color: #a0aec0; } + + /* Player Logs Dark Mode */ + body.dark-mode .card p[style*="color: #6c757d"] { + color: #a0aec0 !important; + } + + body.dark-mode .card > div[style*="max-height: 500px"] > div[style*="padding: 0.75rem"] { + background: #2d3748 !important; + } + + body.dark-mode .card > div[style*="max-height: 500px"] > div[style*="padding: 0.75rem"] p { + color: #e2e8f0 !important; + } + + body.dark-mode .card > div[style*="max-height: 500px"] > div[style*="padding: 0.75rem"] p[style*="color: #6c757d"] { + color: #a0aec0 !important; + } + + body.dark-mode .card > div[style*="max-height: 500px"] > div[style*="padding: 0.75rem"] small { + color: #a0aec0 !important; + } + + body.dark-mode .card > div[style*="max-height: 500px"] > div[style*="padding: 0.75rem"] details summary { + color: #f87171 !important; + } + + body.dark-mode .card > div[style*="max-height: 500px"] > div[style*="padding: 0.75rem"] pre { + background: #1a202c !important; + border-color: #4a5568 !important; + color: #e2e8f0 !important; + } + + body.dark-mode .card > div[style*="text-align: center"] { + color: #a0aec0 !important; + } + + body.dark-mode .card > div[style*="text-align: center"] p { + color: #a0aec0 !important; + } -
-

+
+

Manage Player: {{ player.name }}

- + Back to Players @@ -435,14 +474,6 @@ document.addEventListener('keydown', function(event) { 🔗 Enter the plain text code (e.g., 8887779) - will be hashed automatically
- -
-

📋 Current Credentials (Read-Only)

-
- Auth Code: {{ player.auth_code }}
- Quick Connect Hash: {{ player.quickconnect_code[:50] if player.quickconnect_code else 'Not set' }}... -
-