updated for beter media management

This commit is contained in:
DigiServer Developer
2025-11-22 18:24:40 +02:00
parent f4df930d82
commit b73e10cde7
5 changed files with 735 additions and 70 deletions

View File

@@ -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()

View File

@@ -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/<int:media_id>/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():

View File

@@ -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;
}
</style>
<div class="container" style="max-width: 1400px;">
@@ -258,18 +264,17 @@
</h1>
<div class="main-grid">
<!-- Create/Manage Playlists Card -->
<!-- Create Playlist Card -->
<div class="card">
<div class="card-header">
<h2 style="display: flex; align-items: center; gap: 0.5rem;">
<img src="{{ url_for('static', filename='icons/playlist.svg') }}" alt="" style="width: 24px; height: 24px; filter: brightness(0) invert(1);">
Playlists
Create New Playlist
</h2>
</div>
<!-- Create New Playlist Form -->
<form method="POST" action="{{ url_for('content.create_playlist') }}" style="margin-bottom: 25px;">
<h3 style="margin-bottom: 15px;">Create New Playlist</h3>
<form method="POST" action="{{ url_for('content.create_playlist') }}">
<div class="form-group">
<label for="playlist_name">Playlist Name *</label>
<input type="text" name="name" id="playlist_name" class="form-control" required
@@ -294,47 +299,6 @@
Create Playlist
</button>
</form>
<hr style="margin: 25px 0;">
<!-- Existing Playlists -->
<h3 style="margin-bottom: 15px;">Existing Playlists</h3>
<div class="playlist-list">
{% if playlists %}
{% for playlist in playlists %}
<div class="playlist-item">
<div class="playlist-info">
<h3>{{ playlist.name }}</h3>
<div class="playlist-stats">
📊 {{ playlist.content_count }} items |
👥 {{ playlist.player_count }} players |
🔄 v{{ playlist.version }}
</div>
</div>
<div class="playlist-actions">
<a href="{{ url_for('content.manage_playlist_content', playlist_id=playlist.id) }}"
class="btn btn-primary btn-sm">
✏️ Manage
</a>
<form method="POST"
action="{{ url_for('content.delete_playlist', playlist_id=playlist.id) }}"
style="display: inline;"
onsubmit="return confirm('Delete playlist {{ playlist.name }}?');">
<button type="submit" class="btn btn-danger btn-sm" style="display: flex; align-items: center; gap: 0.3rem;">
<img src="{{ url_for('static', filename='icons/trash.svg') }}" alt="" style="width: 14px; height: 14px; filter: brightness(0) invert(1);">
Delete
</button>
</form>
</div>
</div>
{% endfor %}
{% else %}
<div style="text-align: center; padding: 40px; color: #999;">
<img src="{{ url_for('static', filename='icons/playlist.svg') }}" alt="" style="width: 64px; height: 64px; opacity: 0.3; margin-bottom: 10px;">
<p>No playlists yet. Create your first playlist above!</p>
</div>
{% endif %}
</div>
</div>
<!-- Upload Media Card -->
@@ -356,9 +320,10 @@
<!-- Media Library with Thumbnails -->
<h3 style="margin-bottom: 15px; display: flex; align-items: center; justify-content: space-between;">
<span>📚 Available Media ({{ media_files|length }})</span>
<span>📚 Last 3 Added Media</span>
<small style="color: #6c757d; font-size: 0.85rem;">Total: {{ total_media_count }}</small>
</h3>
<div class="media-library" style="max-height: 500px; overflow-y: auto;">
<div class="media-library" style="max-height: 350px; overflow-y: auto;">
{% if media_files %}
{% for media in media_files %}
<div class="media-item" title="{{ media.filename }}">
@@ -393,6 +358,63 @@
</div>
{% endif %}
</div>
<!-- View All Media Button -->
{% if total_media_count > 3 %}
<div style="text-align: center; padding: 15px; border-top: 1px solid #dee2e6; margin-top: 10px;">
<a href="{{ url_for('content.media_library') }}" class="btn btn-primary" style="display: inline-flex; align-items: center; gap: 0.5rem;">
<span>📚</span>
View All Media ({{ total_media_count }} files)
</a>
</div>
{% endif %}
</div>
</div>
<!-- Existing Playlists Card -->
<div class="card full-width">
<div class="card-header">
<h2 style="display: flex; align-items: center; gap: 0.5rem;">
<img src="{{ url_for('static', filename='icons/playlist.svg') }}" alt="" style="width: 24px; height: 24px; filter: brightness(0) invert(1);">
Existing Playlists
</h2>
</div>
<div class="playlist-list">
{% if playlists %}
{% for playlist in playlists %}
<div class="playlist-item">
<div class="playlist-info">
<h3>{{ playlist.name }}</h3>
<div class="playlist-stats">
📊 {{ playlist.content_count }} items |
👥 {{ playlist.player_count }} players |
🔄 v{{ playlist.version }}
</div>
</div>
<div class="playlist-actions">
<a href="{{ url_for('content.manage_playlist_content', playlist_id=playlist.id) }}"
class="btn btn-primary btn-sm">
✏️ Manage
</a>
<form method="POST"
action="{{ url_for('content.delete_playlist', playlist_id=playlist.id) }}"
style="display: inline;"
onsubmit="return confirm('Delete playlist {{ playlist.name }}?');">
<button type="submit" class="btn btn-danger btn-sm" style="display: flex; align-items: center; gap: 0.3rem;">
<img src="{{ url_for('static', filename='icons/trash.svg') }}" alt="" style="width: 14px; height: 14px; filter: brightness(0) invert(1);">
Delete
</button>
</form>
</div>
</div>
{% endfor %}
{% else %}
<div style="text-align: center; padding: 40px; color: #999;">
<img src="{{ url_for('static', filename='icons/playlist.svg') }}" alt="" style="width: 64px; height: 64px; opacity: 0.3; margin-bottom: 10px;">
<p>No playlists yet. Create your first playlist above!</p>
</div>
{% endif %}
</div>
</div>

View File

@@ -0,0 +1,539 @@
{% extends "base.html" %}
{% block title %}Media Library - DigiServer v2{% endblock %}
{% block content %}
<style>
.media-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 15px;
margin-top: 20px;
}
.media-card {
background: white;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 10px;
transition: all 0.3s ease;
cursor: pointer;
position: relative;
}
.media-card:hover {
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
transform: translateY(-2px);
}
body.dark-mode .media-card {
background: #1a202c;
border-color: #4a5568;
}
.media-thumbnail {
width: 100%;
height: 150px;
border-radius: 6px;
overflow: hidden;
background: #f0f0f0;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 10px;
}
body.dark-mode .media-thumbnail {
background: #2d3748;
}
.media-thumbnail img {
width: 100%;
height: 100%;
object-fit: cover;
}
.media-icon {
font-size: 64px;
}
.media-info {
font-size: 12px;
color: #6c757d;
}
body.dark-mode .media-info {
color: #a0aec0;
}
.media-filename {
font-weight: 500;
margin-bottom: 5px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
body.dark-mode .media-filename {
color: #e2e8f0;
}
.delete-btn {
position: absolute;
top: 5px;
right: 5px;
background: #dc3545;
color: white;
border: none;
border-radius: 50%;
width: 28px;
height: 28px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
opacity: 0;
transition: opacity 0.3s ease;
}
.media-card:hover .delete-btn {
opacity: 1;
}
.delete-btn:hover {
background: #a02834;
}
.playlist-badge {
display: inline-block;
padding: 3px 8px;
border-radius: 10px;
font-size: 10px;
font-weight: 600;
margin-top: 5px;
}
.playlist-badge.in-use {
background: #ffc107;
color: #000;
}
.playlist-badge.unused {
background: #28a745;
color: white;
}
body.dark-mode .playlist-badge.in-use {
background: #856404;
color: #ffc107;
}
body.dark-mode .upload-section {
background: #2d3748 !important;
border: 1px solid #4a5568;
}
body.dark-mode .playlist-badge.unused {
background: #1a4d2e;
color: #86efac;
}
.type-badge {
display: inline-block;
padding: 2px 8px;
border-radius: 12px;
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
}
.type-badge.image { background: #d4edda; color: #155724; }
.type-badge.video { background: #cce5ff; color: #004085; }
.type-badge.pdf { background: #fff3cd; color: #856404; }
.type-badge.pptx { background: #f8d7da; color: #721c24; }
body.dark-mode .type-badge.image { background: #1a4d2e; color: #86efac; }
body.dark-mode .type-badge.video { background: #1e3a5f; color: #93c5fd; }
body.dark-mode .type-badge.pdf { background: #4a3800; color: #fbbf24; }
body.dark-mode .type-badge.pptx { background: #4a1a1a; color: #fca5a5; }
.stats-box {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 15px;
margin-bottom: 30px;
}
.stat-item {
background: white;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 15px;
text-align: center;
}
body.dark-mode .stat-item {
background: #1a202c;
border-color: #4a5568;
}
.stat-value {
font-size: 32px;
font-weight: bold;
color: #7c3aed;
}
body.dark-mode .stat-value {
color: #a78bfa;
}
.stat-label {
font-size: 14px;
color: #6c757d;
margin-top: 5px;
}
body.dark-mode .stat-label {
color: #a0aec0;
}
.section-header {
display: flex;
align-items: center;
gap: 10px;
margin: 30px 0 15px 0;
padding-bottom: 10px;
border-bottom: 2px solid #7c3aed;
}
body.dark-mode .section-header {
border-bottom-color: #a78bfa;
}
.section-header h2 {
margin: 0;
font-size: 24px;
}
body.dark-mode .section-header h2 {
color: #e2e8f0;
}
</style>
<div style="margin-bottom: 2rem;">
<h1 style="display: inline-block; margin-right: 1rem; display: flex; align-items: center; gap: 0.5rem;">
<img src="{{ url_for('static', filename='icons/playlist.svg') }}" alt="" style="width: 32px; height: 32px;">
📚 Media Library
</h1>
<a href="{{ url_for('content.content_list') }}" class="btn" style="float: right; display: inline-flex; align-items: center; gap: 0.5rem;">
<img src="{{ url_for('static', filename='icons/playlist.svg') }}" alt="" style="width: 18px; height: 18px; filter: brightness(0) invert(1);">
Back to Playlists
</a>
</div>
<!-- Statistics -->
<div class="stats-box">
<div class="stat-item">
<div class="stat-value">{{ media_files|length }}</div>
<div class="stat-label">Total Files</div>
</div>
<div class="stat-item">
<div class="stat-value">{{ images|length }}</div>
<div class="stat-label">Images</div>
</div>
<div class="stat-item">
<div class="stat-value">{{ videos|length }}</div>
<div class="stat-label">Videos</div>
</div>
<div class="stat-item">
<div class="stat-value">{{ pdfs|length }}</div>
<div class="stat-label">PDFs</div>
</div>
<div class="stat-item">
<div class="stat-value">{{ presentations|length }}</div>
<div class="stat-label">Presentations</div>
</div>
</div>
<!-- Upload Button -->
<div class="upload-section" style="text-align: center; padding: 20px; background: #f8f9fa; border-radius: 8px; margin-bottom: 30px;">
<a href="{{ url_for('content.upload_media_page') }}" class="btn btn-success" style="display: inline-flex; align-items: center; gap: 0.5rem;">
<img src="{{ url_for('static', filename='icons/upload.svg') }}" alt="" style="width: 18px; height: 18px; filter: brightness(0) invert(1);">
Upload New Media
</a>
</div>
<!-- Images Section -->
{% if images %}
<div class="section-header">
<span style="font-size: 32px;">📷</span>
<h2>Images ({{ images|length }})</h2>
</div>
<div class="media-grid">
{% for media in images %}
<div class="media-card">
<button class="delete-btn" onclick="confirmDelete({{ media.id }}, '{{ media.filename }}', {{ media.playlists.count() }})" title="Delete">🗑️</button>
<div class="media-thumbnail">
<img src="{{ url_for('static', filename='uploads/' + media.filename) }}"
alt="{{ media.filename }}"
onerror="this.style.display='none'; this.parentElement.innerHTML='<span class=\'media-icon\'>📷</span>'">
</div>
<div class="media-filename" title="{{ media.filename }}">{{ media.filename }}</div>
<div class="media-info">
<span class="type-badge image">Image</span>
<div style="margin-top: 5px;">{{ "%.1f"|format(media.file_size_mb) }} MB</div>
<div style="font-size: 10px; margin-top: 3px;">{{ media.uploaded_at.strftime('%Y-%m-%d') }}</div>
{% if media.playlists.count() > 0 %}
<div class="playlist-badge in-use">📋 In {{ media.playlists.count() }} playlist(s)</div>
{% else %}
<div class="playlist-badge unused">✓ Not in use</div>
{% endif %}
</div>
</div>
{% endfor %}
</div>
{% endif %}
<!-- Videos Section -->
{% if videos %}
<div class="section-header">
<span style="font-size: 32px;">🎥</span>
<h2>Videos ({{ videos|length }})</h2>
</div>
<div class="media-grid">
{% for media in videos %}
<div class="media-card">
<button class="delete-btn" onclick="confirmDelete({{ media.id }}, '{{ media.filename }}', {{ media.playlists.count() }})" title="Delete">🗑️</button>
<div class="media-thumbnail">
<span class="media-icon">🎥</span>
</div>
<div class="media-filename" title="{{ media.filename }}">{{ media.filename }}</div>
<div class="media-info">
<span class="type-badge video">Video</span>
<div style="margin-top: 5px;">{{ "%.1f"|format(media.file_size_mb) }} MB</div>
<div style="font-size: 10px; margin-top: 3px;">{{ media.uploaded_at.strftime('%Y-%m-%d') }}</div>
{% if media.playlists.count() > 0 %}
<div class="playlist-badge in-use">📋 In {{ media.playlists.count() }} playlist(s)</div>
{% else %}
<div class="playlist-badge unused">✓ Not in use</div>
{% endif %}
</div>
</div>
{% endfor %}
</div>
{% endif %}
<!-- PDFs Section -->
{% if pdfs %}
<div class="section-header">
<span style="font-size: 32px;">📄</span>
<h2>PDFs ({{ pdfs|length }})</h2>
</div>
<div class="media-grid">
{% for media in pdfs %}
<div class="media-card">
<button class="delete-btn" onclick="confirmDelete({{ media.id }}, '{{ media.filename }}', {{ media.playlists.count() }})" title="Delete">🗑️</button>
<div class="media-thumbnail">
<span class="media-icon">📄</span>
</div>
<div class="media-filename" title="{{ media.filename }}">{{ media.filename }}</div>
<div class="media-info">
<span class="type-badge pdf">PDF</span>
<div style="margin-top: 5px;">{{ "%.1f"|format(media.file_size_mb) }} MB</div>
<div style="font-size: 10px; margin-top: 3px;">{{ media.uploaded_at.strftime('%Y-%m-%d') }}</div>
{% if media.playlists.count() > 0 %}
<div class="playlist-badge in-use">📋 In {{ media.playlists.count() }} playlist(s)</div>
{% else %}
<div class="playlist-badge unused">✓ Not in use</div>
{% endif %}
</div>
</div>
{% endfor %}
</div>
{% endif %}
<!-- Presentations Section -->
{% if presentations %}
<div class="section-header">
<span style="font-size: 32px;">📊</span>
<h2>Presentations ({{ presentations|length }})</h2>
</div>
<div class="media-grid">
{% for media in presentations %}
<div class="media-card">
<button class="delete-btn" onclick="confirmDelete({{ media.id }}, '{{ media.filename }}', {{ media.playlists.count() }})" title="Delete">🗑️</button>
<div class="media-thumbnail">
<span class="media-icon">📊</span>
</div>
<div class="media-filename" title="{{ media.filename }}">{{ media.filename }}</div>
<div class="media-info">
<span class="type-badge pptx">PPTX</span>
<div style="margin-top: 5px;">{{ "%.1f"|format(media.file_size_mb) }} MB</div>
<div style="font-size: 10px; margin-top: 3px;">{{ media.uploaded_at.strftime('%Y-%m-%d') }}</div>
{% if media.playlists.count() > 0 %}
<div class="playlist-badge in-use">📋 In {{ media.playlists.count() }} playlist(s)</div>
{% else %}
<div class="playlist-badge unused">✓ Not in use</div>
{% endif %}
</div>
</div>
{% endfor %}
</div>
{% endif %}
<!-- Others Section -->
{% if others %}
<div class="section-header">
<span style="font-size: 32px;">📁</span>
<h2>Other Files ({{ others|length }})</h2>
</div>
<div class="media-grid">
{% for media in others %}
<div class="media-card">
<button class="delete-btn" onclick="confirmDelete({{ media.id }}, '{{ media.filename }}', {{ media.playlists.count() }})" title="Delete">🗑️</button>
<div class="media-thumbnail">
<span class="media-icon">📁</span>
</div>
<div class="media-filename" title="{{ media.filename }}">{{ media.filename }}</div>
<div class="media-info">
<span class="type-badge">{{ media.content_type }}</span>
<div style="margin-top: 5px;">{{ "%.1f"|format(media.file_size_mb) }} MB</div>
<div style="font-size: 10px; margin-top: 3px;">{{ media.uploaded_at.strftime('%Y-%m-%d') }}</div>
{% if media.playlists.count() > 0 %}
<div class="playlist-badge in-use">📋 In {{ media.playlists.count() }} playlist(s)</div>
{% else %}
<div class="playlist-badge unused">✓ Not in use</div>
{% endif %}
</div>
</div>
{% endfor %}
</div>
{% endif %}
{% if not media_files %}
<div style="text-align: center; padding: 80px 20px;">
<div style="font-size: 96px; margin-bottom: 20px;">📭</div>
<h2 style="color: #6c757d;">No Media Files Yet</h2>
<p style="color: #999; margin-bottom: 30px;">Start by uploading your first media file!</p>
<a href="{{ url_for('content.upload_media_page') }}" class="btn btn-success" style="display: inline-flex; align-items: center; gap: 0.5rem;">
<img src="{{ url_for('static', filename='icons/upload.svg') }}" alt="" style="width: 18px; height: 18px; filter: brightness(0) invert(1);">
Upload Media
</a>
</div>
{% endif %}
<!-- Delete Confirmation Modal -->
<div id="deleteModal" style="display: none; position: fixed; z-index: 1000; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.5);">
<div style="background-color: #fefefe; margin: 10% auto; padding: 30px; border-radius: 12px; width: 90%; max-width: 500px; box-shadow: 0 4px 20px rgba(0,0,0,0.3);">
<h2 style="margin-top: 0; color: #dc3545; display: flex; align-items: center; gap: 0.5rem;">
<span style="font-size: 2rem;">⚠️</span>
Confirm Delete
</h2>
<p style="font-size: 1.1rem; margin: 1.5rem 0;">
Are you sure you want to delete <strong id="deleteFilename"></strong>?
</p>
<div id="playlistWarning" style="display: none; background: #fff3cd; border-left: 4px solid #ffc107; padding: 12px; margin: 1rem 0; border-radius: 4px;">
<strong style="color: #856404;">⚠️ Warning:</strong>
<p style="margin: 0.5rem 0 0 0; color: #856404;">This file is used in <strong id="playlistCount"></strong> playlist(s). Deleting it will remove it from all playlists and increment their version numbers.</p>
</div>
<p style="color: #dc3545; margin: 1rem 0;">
<strong>This action cannot be undone!</strong>
</p>
<div style="display: flex; gap: 10px; justify-content: flex-end; margin-top: 2rem;">
<button onclick="closeDeleteModal()" class="btn" style="background: #6c757d;">
Cancel
</button>
<form id="deleteForm" method="POST" style="margin: 0;">
<button type="submit" class="btn" style="background: #dc3545;">
Yes, Delete File
</button>
</form>
</div>
</div>
</div>
<style>
body.dark-mode #deleteModal {
background-color: rgba(0, 0, 0, 0.8);
}
body.dark-mode #deleteModal > div {
background-color: #1a202c;
border: 1px solid #4a5568;
color: #e2e8f0;
}
body.dark-mode #deleteModal h2 {
color: #f87171;
}
body.dark-mode #deleteModal p {
color: #e2e8f0;
}
body.dark-mode #deleteModal strong {
color: #fbbf24;
}
body.dark-mode #playlistWarning {
background: #4a3800;
border-left-color: #ffc107;
}
body.dark-mode #playlistWarning strong,
body.dark-mode #playlistWarning p {
color: #fbbf24;
}
body.dark-mode #deleteForm button {
background: #dc3545 !important;
color: white !important;
}
body.dark-mode #deleteForm button:hover {
background: #a02834 !important;
}
</style>
<script>
let deleteMediaId = null;
function confirmDelete(mediaId, filename, playlistCount) {
deleteMediaId = mediaId;
document.getElementById('deleteFilename').textContent = filename;
document.getElementById('deleteForm').action = "{{ url_for('content.delete_media', media_id=0) }}".replace('/0', '/' + mediaId);
// Show playlist warning if file is in use
if (playlistCount > 0) {
document.getElementById('playlistWarning').style.display = 'block';
document.getElementById('playlistCount').textContent = playlistCount;
} else {
document.getElementById('playlistWarning').style.display = 'none';
}
document.getElementById('deleteModal').style.display = 'block';
}
function closeDeleteModal() {
document.getElementById('deleteModal').style.display = 'none';
deleteMediaId = null;
}
// Close modal when clicking outside
window.onclick = function(event) {
const modal = document.getElementById('deleteModal');
if (event.target == modal) {
closeDeleteModal();
}
}
// Close modal with ESC key
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape') {
closeDeleteModal();
}
});
</script>
{% endblock %}

View File

@@ -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;
}
</style>
<div style="margin-bottom: 2rem;">
<h1 style="display: inline-block; margin-right: 1rem; display: flex; align-items: center; gap: 0.5rem;">
<div style="margin-bottom: 2rem; display: flex; justify-content: space-between; align-items: flex-start;">
<h1 style="margin: 0; display: flex; align-items: center; gap: 0.5rem;">
<img src="{{ url_for('static', filename='icons/monitor.svg') }}" alt="" style="width: 32px; height: 32px;">
Manage Player: {{ player.name }}
</h1>
<a href="{{ url_for('players.list') }}" class="btn" style="float: right; display: inline-flex; align-items: center; gap: 0.5rem;">
<a href="{{ url_for('players.list') }}" class="btn" style="display: inline-flex; align-items: center; gap: 0.5rem;">
<img src="{{ url_for('static', filename='icons/monitor.svg') }}" alt="" style="width: 18px; height: 18px; filter: brightness(0) invert(1);">
Back to Players
</a>
@@ -435,14 +474,6 @@ document.addEventListener('keydown', function(event) {
🔗 Enter the plain text code (e.g., 8887779) - will be hashed automatically
</small>
</div>
<div class="info-box neutral" style="margin-top: 1rem;">
<h4 style="margin: 0 0 0.5rem 0; font-size: 0.85rem;">📋 Current Credentials (Read-Only)</h4>
<div style="font-size: 0.85rem;">
<strong>Auth Code:</strong> <code style="font-size: 0.8rem;">{{ player.auth_code }}</code><br>
<strong>Quick Connect Hash:</strong> <code style="font-size: 0.7rem;">{{ player.quickconnect_code[:50] if player.quickconnect_code else 'Not set' }}...</code>
</div>
</div>
</div>
<button type="submit" class="btn btn-success" style="width: 100%; display: flex; align-items: center; justify-content: center; gap: 0.5rem;">