Implement editing users management and permission updates

- Added auto-creation of PlayerUser records from player metadata (user_card_data)
- Fixed player_user table schema (removed player_id, made user_code unique globally)
- Created admin page for managing editing users (view, update names, delete)
- Updated permissions: normal users can now access admin panel, editing users, and leftover media
- Admin-only access: user management, system dependencies, logo customization
- Fixed edited media workflow to preserve original files
- Content.filename now points to edited_media folder, keeping originals intact
- Added user display names in edited media page (shows name if set, code otherwise)
- Fixed leftover media file size calculation (handle None values)
- Split editing users into separate card on admin panel with description
This commit is contained in:
DigiServer Developer
2025-12-14 14:14:04 +02:00
parent 88e24f8fec
commit 3829d98e91
6 changed files with 45 additions and 32 deletions

View File

@@ -31,7 +31,6 @@ def admin_required(f):
@admin_bp.route('/')
@login_required
@admin_required
def admin_panel():
"""Display admin panel with system overview."""
try:
@@ -351,7 +350,6 @@ def system_info():
@admin_bp.route('/leftover-media')
@login_required
@admin_required
def leftover_media():
"""Display leftover media files not assigned to any playlist."""
from app.models.playlist import playlist_content
@@ -374,12 +372,15 @@ def leftover_media():
leftover_pdfs = [c for c in leftover_content if c.content_type == 'pdf']
leftover_pptx = [c for c in leftover_content if c.content_type == 'pptx']
# Calculate storage
total_leftover_size = sum(c.file_size for c in leftover_content)
images_size = sum(c.file_size for c in leftover_images)
videos_size = sum(c.file_size for c in leftover_videos)
pdfs_size = sum(c.file_size for c in leftover_pdfs)
pptx_size = sum(c.file_size for c in leftover_pptx)
# Calculate storage (handle None values)
def safe_file_size(content_list):
return sum(c.file_size or 0 for c in content_list)
total_leftover_size = safe_file_size(leftover_content)
images_size = safe_file_size(leftover_images)
videos_size = safe_file_size(leftover_videos)
pdfs_size = safe_file_size(leftover_pdfs)
pptx_size = safe_file_size(leftover_pptx)
return render_template('admin/leftover_media.html',
leftover_images=leftover_images,
@@ -401,7 +402,6 @@ def leftover_media():
@admin_bp.route('/delete-leftover-images', methods=['POST'])
@login_required
@admin_required
def delete_leftover_images():
"""Delete all leftover images that are not part of any playlist"""
from app.models.playlist import playlist_content
@@ -457,7 +457,6 @@ def delete_leftover_images():
@admin_bp.route('/delete-leftover-videos', methods=['POST'])
@login_required
@admin_required
def delete_leftover_videos():
"""Delete all leftover videos that are not part of any playlist"""
from app.models.playlist import playlist_content
@@ -513,7 +512,6 @@ def delete_leftover_videos():
@admin_bp.route('/delete-single-leftover/<int:content_id>', methods=['POST'])
@login_required
@admin_required
def delete_single_leftover(content_id):
"""Delete a single leftover content file"""
try:
@@ -776,7 +774,6 @@ def upload_login_logo():
@admin_bp.route('/editing-users')
@login_required
@admin_required
def manage_editing_users():
"""Display and manage users that edit images on players."""
try:
@@ -803,7 +800,6 @@ def manage_editing_users():
@admin_bp.route('/editing-users/<int:user_id>/update', methods=['POST'])
@login_required
@admin_required
def update_editing_user(user_id: int):
"""Update editing user name."""
try:
@@ -828,7 +824,6 @@ def update_editing_user(user_id: int):
@admin_bp.route('/editing-users/<int:user_id>/delete', methods=['POST'])
@login_required
@admin_required
def delete_editing_user(user_id: int):
"""Delete editing user."""
try:

View File

@@ -777,14 +777,10 @@ def receive_edited_media():
with open(metadata_path, 'w') as f:
json.dump(metadata, f, indent=2)
# Copy the versioned image to the main uploads folder
import shutil
versioned_upload_path = os.path.join(base_upload_dir, new_filename)
shutil.copy2(edited_file_path, versioned_upload_path)
# Update the content record to reference the new versioned filename
# Update the content record to reference the edited version path
# Keep original filename unchanged, point to edited_media folder
old_filename = content.filename
content.filename = new_filename
content.filename = f"edited_media/{content.id}/{new_filename}"
# Create edit record
time_of_mod = None

View File

@@ -343,6 +343,7 @@ def edited_media(player_id: int):
# Get all edited media history from player
from app.models.player_edit import PlayerEdit
from app.models.player_user import PlayerUser
edited_media = PlayerEdit.query.filter_by(player_id=player_id)\
.order_by(PlayerEdit.created_at.desc())\
@@ -356,10 +357,21 @@ def edited_media(player_id: int):
if content:
content_files[edit.content_id] = content
# Get user mappings for display names
user_mappings = {}
for edit in edited_media:
if edit.user and edit.user not in user_mappings:
player_user = PlayerUser.query.filter_by(user_code=edit.user).first()
if player_user:
user_mappings[edit.user] = player_user.user_name or edit.user
else:
user_mappings[edit.user] = edit.user
return render_template('players/edited_media.html',
player=player,
edited_media=edited_media,
content_files=content_files)
content_files=content_files,
user_mappings=user_mappings)
except Exception as e:
log_action('error', f'Error loading edited media for player {player_id}: {str(e)}')
flash('Error loading edited media.', 'danger')

View File

@@ -48,7 +48,8 @@
</div>
</div>
<!-- User Management Card -->
{% if current_user.is_admin %}
<!-- User Management Card (Admin Only) -->
<div class="card management-card">
<h2>👥 User Management</h2>
<p>Manage application users, roles and permissions</p>
@@ -56,8 +57,17 @@
<a href="{{ url_for('admin.user_management') }}" class="btn btn-primary">
Manage Users
</a>
<a href="{{ url_for('admin.manage_editing_users') }}" class="btn btn-secondary" style="margin-left: 0.5rem;">
Manage Users That Edited Images
</div>
</div>
{% endif %}
<!-- Editing Users Card -->
<div class="card management-card">
<h2>✏️ Editing Users</h2>
<p>Manage user codes from players that edit images on-screen</p>
<div class="card-actions">
<a href="{{ url_for('admin.manage_editing_users') }}" class="btn btn-primary">
Manage Editing Users
</a>
</div>
</div>
@@ -73,7 +83,8 @@
</div>
</div>
<!-- System Dependencies Card -->
{% if current_user.is_admin %}
<!-- System Dependencies Card (Admin Only) -->
<div class="card management-card" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);">
<h2>🔧 System Dependencies</h2>
<p>Check and install required software dependencies</p>
@@ -84,7 +95,7 @@
</div>
</div>
<!-- Logo Customization Card -->
<!-- Logo Customization Card (Admin Only) -->
<div class="card management-card" style="background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);">
<h2>🎨 Logo Customization</h2>
<p>Upload custom logos for header and login page</p>
@@ -94,6 +105,7 @@
</a>
</div>
</div>
{% endif %}
<!-- Quick Actions Card -->
<div class="card">

View File

@@ -393,9 +393,7 @@
<img src="{{ url_for('static', filename='icons/playlist.svg') }}" alt="">
Playlists
</a>
{% if current_user.is_admin %}
<a href="{{ url_for('admin.admin_panel') }}">Admin</a>
{% endif %}
<a href="{{ url_for('auth.logout') }}">Logout ({{ current_user.username }})</a>
<button class="dark-mode-toggle" onclick="toggleDarkMode()" title="Toggle Dark Mode">
<img id="theme-icon" src="{{ url_for('static', filename='icons/moon.svg') }}" alt="Toggle theme">

View File

@@ -376,7 +376,7 @@
</div>
<div class="preview-info-item">
<span class="preview-info-label">👤 Edited by:</span>
<span class="preview-info-value" id="info-user-{{ content_id }}">{{ latest.user or 'Unknown' }}</span>
<span class="preview-info-value" id="info-user-{{ content_id }}">{{ user_mappings.get(latest.user, latest.user or 'Unknown') }}</span>
</div>
<div class="preview-info-item">
<span class="preview-info-label">🕒 Modified:</span>
@@ -398,7 +398,7 @@
{% for edit in data.versions|sort(attribute='version', reverse=True) %}
<div class="version-item {% if loop.first %}active{% endif %}"
id="version-{{ content_id }}-{{ edit.version }}"
onclick="event.stopPropagation(); selectVersion({{ content_id }}, {{ edit.version }}, '{{ edit.new_name }}', '{{ edit.user or 'Unknown' }}', '{{ edit.time_of_modification | localtime('%Y-%m-%d %H:%M') if edit.time_of_modification else 'N/A' }}', '{{ edit.created_at | localtime('%Y-%m-%d %H:%M') }}', '{{ url_for('static', filename='uploads/edited_media/' ~ edit.content_id ~ '/' ~ edit.new_name) }}')">
onclick="event.stopPropagation(); selectVersion({{ content_id }}, {{ edit.version }}, '{{ edit.new_name }}', '{{ user_mappings.get(edit.user, edit.user or 'Unknown') }}', '{{ edit.time_of_modification | localtime('%Y-%m-%d %H:%M') if edit.time_of_modification else 'N/A' }}', '{{ edit.created_at | localtime('%Y-%m-%d %H:%M') }}', '{{ url_for('static', filename='uploads/edited_media/' ~ edit.content_id ~ '/' ~ edit.new_name) }}')">
<div class="version-thumbnail">
{% if edit.new_name.lower().endswith(('.jpg', '.jpeg', '.png', '.gif', '.webp')) %}
<img src="{{ url_for('static', filename='uploads/edited_media/' ~ edit.content_id ~ '/' ~ edit.new_name) }}"