Files
digiserver-v2/app/templates/players/manage_player.html
T
ske087 4dc8506c9b Fix all remaining dark-theme colour bugs across the templates
Follow-up to the player-modal / maintenance-page fix: the same root cause --
colours hard-coded for the light theme -- affected the whole app, in two
places.

1) Inline style attributes (inline outranks the theme rules, so the light
   value survives dark mode and the inherited text colour turns
   light-on-light). base.html now carries a single, property-scoped
   dark-mode override layer that maps every light literal found in the
   templates to the theme variables: light backgrounds/whites -> dark
   surfaces, semantic pastels -> dark equivalents, dark text -> light text,
   light borders -> dark borders, plus .back-link. Scoped to
   body.dark-mode, so the light theme is untouched. This covers all
   templates, including ones not edited here and any added later.

2) Page-level <style> blocks with no dark support:
   * admin/https_config.html hard-coded a light-only palette (blue-tint
     panels, dark labels) -- added a body.dark-mode block for it.
   * dashboard.html / help.html / manage_player.html coloured text links
     with --primary-color (#7c3aed in dark = too dim). Introduced a
     dedicated --link-color (#667eea light / #a78bfa dark) and used it for
     text links, leaving --primary-color for button/background use.

Verified with an automated luminance scan of all 18 pages in both themes:
dark mode has 0 light backgrounds and 0 dark-on-dark text (the only
remaining light surfaces are the intentional white buttons on the admin
gradient cards), and light mode is unchanged.
2026-10-14 22:45:09 +03:00

840 lines
34 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block title %}Manage Player - {{ player.name }}{% endblock %}
{% block content %}
<style>
.form-group {
margin-bottom: 1rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: bold;
}
body.dark-mode .form-group label {
color: #e2e8f0;
}
.form-control {
width: 100%;
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
}
body.dark-mode .form-control {
background: #1a202c;
border-color: #4a5568;
color: #e2e8f0;
}
body.dark-mode .form-control:focus {
border-color: #7c3aed;
outline: none;
}
.info-box {
padding: 1rem;
border-radius: 4px;
margin-bottom: 1rem;
}
.info-box.neutral {
background: #f8f9fa;
}
body.dark-mode .info-box.neutral {
background: #1a202c;
border: 1px solid #4a5568;
}
.info-box.success {
background: #d4edda;
color: #155724;
}
body.dark-mode .info-box.success {
background: #1a4d2e;
color: #86efac;
border: 1px solid #48bb78;
}
.info-box.warning {
background: #fff3cd;
color: #856404;
}
body.dark-mode .info-box.warning {
background: #4a3800;
color: #fbbf24;
border: 1px solid #ecc94b;
}
.log-item {
padding: 0.75rem;
margin-bottom: 0.5rem;
border-radius: 4px;
background: #f8f9fa;
border-left: 4px solid;
}
body.dark-mode .log-item {
background: #2d3748;
}
.log-item pre {
background: #fff;
border: 1px solid #ddd;
}
body.dark-mode .log-item pre {
background: #1a202c;
border-color: #4a5568;
color: #e2e8f0;
}
body.dark-mode h1,
body.dark-mode h2,
body.dark-mode h3,
body.dark-mode h4 {
color: #e2e8f0;
}
body.dark-mode p {
color: #a0aec0;
}
body.dark-mode strong {
color: #e2e8f0;
}
body.dark-mode code {
background: #1a202c;
color: #e2e8f0;
padding: 2px 6px;
border-radius: 3px;
}
body.dark-mode small {
color: #718096;
}
.credential-item {
margin-bottom: 0.75rem;
padding-bottom: 0.75rem;
border-bottom: 1px solid #e2e8f0;
}
body.dark-mode .credential-item {
border-bottom-color: #4a5568;
}
.credential-item:last-child {
margin-bottom: 0;
padding-bottom: 0;
border-bottom: none;
}
.credential-label {
font-weight: 600;
display: block;
margin-bottom: 0.25rem;
font-size: 0.85rem;
color: #495057;
}
body.dark-mode .credential-label {
color: #a0aec0;
}
.credential-value {
font-family: 'Courier New', monospace;
background: #f8f9fa;
padding: 0.5rem;
border-radius: 4px;
word-break: break-all;
font-size: 0.85rem;
border: 1px solid #dee2e6;
}
body.dark-mode .credential-value {
background: #0d1117;
border-color: #4a5568;
color: #e2e8f0;
}
.status-card {
margin-bottom: 2rem;
}
.status-card.online {
background: #d4edda;
}
body.dark-mode .status-card.online {
background: #1a4d2e;
border: 1px solid #48bb78;
}
.status-card.offline {
background: #f8d7da;
}
body.dark-mode .status-card.offline {
background: #4a1a1a;
border: 1px solid #dc3545;
}
.status-card.other {
background: #fff3cd;
}
body.dark-mode .status-card.other {
background: #4a3800;
border: 1px solid #ecc94b;
}
.playlist-stats {
padding: 1rem;
background: #f8f9fa;
border-radius: 4px;
}
body.dark-mode .playlist-stats {
background: #1a202c;
border: 1px solid #4a5568;
}
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;
}
/* Edited Media Cards Dark Mode */
body.dark-mode .card[style*="border: 2px solid #7c3aed"] {
background: linear-gradient(135deg, #2d3748 0%, #1a202c 100%) !important;
border-color: #8b5cf6 !important;
}
body.dark-mode .card[style*="border: 2px solid #7c3aed"] h3 {
color: #a78bfa !important;
}
body.dark-mode .card[style*="border: 2px solid #7c3aed"] div[style*="background: white"] {
background: #374151 !important;
}
body.dark-mode .card[style*="border: 2px solid #7c3aed"] strong {
color: #a78bfa !important;
}
body.dark-mode .card[style*="border: 2px solid #7c3aed"] p[style*="color: #475569"],
body.dark-mode .card[style*="border: 2px solid #7c3aed"] p[style*="color: #64748b"] {
color: #cbd5e1 !important;
}
body.dark-mode div[style*="background: #f8f9fa; border-radius: 8px"] {
background: #2d3748 !important;
}
body.dark-mode .card > div[style*="text-align: center"] p {
color: #a0aec0 !important;
}
</style>
<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="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>
</div>
<!-- Delete Confirmation Modal -->
<div id="deleteModal" class="modal-overlay">
<div class="modal-box">
<h2 class="modal-title">
<span style="font-size: 1.5rem;">⚠️</span>
Confirm Delete
</h2>
<p class="modal-text">
Are you sure you want to delete player <strong class="modal-player">"{{ player.name }}"</strong>?
</p>
<p class="modal-warning">
<strong>Warning:</strong> This action cannot be undone. All feedback logs for this player will also be deleted.
</p>
<div class="modal-actions">
<button onclick="closeDeleteModal()" class="btn modal-cancel">
Cancel
</button>
<form method="POST" action="{{ url_for('players.delete_player', player_id=player.id) }}" style="margin: 0;">
<button type="submit" class="btn btn-danger">
Yes, Delete Player
</button>
</form>
</div>
</div>
</div>
<style>
/* Delete confirmation modal — themed via the CSS variables defined in
base.html so it follows the light/dark theme. Inline colours are
deliberately avoided: they would win over the theme rules and leave
the modal white (with light text) in dark mode. */
.modal-overlay {
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);
}
.modal-box {
background-color: var(--card-bg);
color: var(--text-color);
margin: 15% auto;
padding: 20px;
border: 1px solid var(--border-color);
border-radius: 8px;
width: 90%;
max-width: 500px;
box-shadow: var(--shadow-lg);
}
.modal-title {
margin-top: 0;
color: #dc3545;
display: flex;
align-items: center;
gap: 0.5rem;
}
body.dark-mode .modal-title {
color: #f87171;
}
.modal-text {
font-size: 1.1rem;
margin: 1.5rem 0;
color: var(--text-color);
}
.modal-player {
color: var(--link-color);
}
.modal-warning {
margin: 1rem 0;
padding: 0.75rem 1rem;
border-radius: 6px;
background: #fff3cd;
color: #856404;
}
body.dark-mode .modal-warning {
background: #4a3800;
color: #fbbf24;
}
.modal-actions {
display: flex;
gap: 0.5rem;
justify-content: flex-end;
margin-top: 2rem;
}
.modal-cancel {
background: #6c757d;
}
.modal-cancel:hover {
background: #5a6268;
}
</style>
<script>
function confirmDelete() {
document.getElementById('deleteModal').style.display = 'block';
}
function closeDeleteModal() {
document.getElementById('deleteModal').style.display = 'none';
}
// Close modal when clicking outside of it
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>
<!-- Player Status Overview -->
<div class="card status-card {% if player.status == 'online' %}online{% elif player.status == 'offline' %}offline{% else %}other{% endif %}">
<h3 style="display: flex; align-items: center; gap: 0.5rem;">
Status:
{% if player.status == 'online' %}
<span style="color: #28a745; display: flex; align-items: center; gap: 0.3rem;">
<img src="{{ url_for('static', filename='icons/info.svg') }}" alt="" style="width: 20px; height: 20px; color: #28a745;">
Online
</span>
{% elif player.status == 'offline' %}
<span style="color: #dc3545; display: flex; align-items: center; gap: 0.3rem;">
<img src="{{ url_for('static', filename='icons/warning.svg') }}" alt="" style="width: 20px; height: 20px; color: #dc3545;">
Offline
</span>
{% else %}
<span style="color: #ffc107; display: flex; align-items: center; gap: 0.3rem;">
<img src="{{ url_for('static', filename='icons/info.svg') }}" alt="" style="width: 20px; height: 20px; color: #ffc107;">
{{ player.status|title }}
</span>
{% endif %}
</h3>
<p><strong>Hostname:</strong> {{ player.hostname }}</p>
<p><strong>Last Seen:</strong>
{% if player.last_seen %}
{{ player.last_seen | localtime('%Y-%m-%d %H:%M:%S') }}
{% else %}
Never
{% endif %}
</p>
<p><strong>Assigned Playlist:</strong>
{% if current_playlist %}
<span style="color: #28a745; font-weight: bold;">{{ current_playlist.name }} (v{{ current_playlist.version }})</span>
{% else %}
<span style="color: #dc3545;">No playlist assigned</span>
{% endif %}
</p>
</div>
<!-- Playlist Overview Card -->
{% if current_playlist %}
<div class="card" style="margin-bottom: 2rem;">
<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;">
Current Playlist: {{ current_playlist.name }}
</h2>
<div class="playlist-stats" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1rem; margin-top: 1rem;">
<div>
<div style="font-size: 0.85rem; color: #6c757d; margin-bottom: 0.25rem;">Total Items</div>
<div style="font-size: 1.5rem; font-weight: bold;">{{ current_playlist.contents.count() }}</div>
</div>
<div>
<div style="font-size: 0.85rem; color: #6c757d; margin-bottom: 0.25rem;">Playlist Version</div>
<div style="font-size: 1.5rem; font-weight: bold;">v{{ current_playlist.version }}</div>
</div>
<div>
<div style="font-size: 0.85rem; color: #6c757d; margin-bottom: 0.25rem;">Last Updated</div>
<div style="font-size: 1rem; font-weight: bold;">{{ current_playlist.updated_at | localtime }}</div>
</div>
<div>
<div style="font-size: 0.85rem; color: #6c757d; margin-bottom: 0.25rem;">Orientation</div>
<div style="font-size: 1.5rem; font-weight: bold;">{{ current_playlist.orientation }}</div>
</div>
</div>
<div style="margin-top: 1rem; display: flex; gap: 0.5rem;">
<a href="{{ url_for('content.manage_playlist_content', playlist_id=current_playlist.id) }}"
class="btn btn-primary" style="display: inline-flex; align-items: center; gap: 0.5rem;">
<img src="{{ url_for('static', filename='icons/edit.svg') }}" alt="" style="width: 18px; height: 18px; filter: brightness(0) invert(1);">
Edit Playlist Content
</a>
<a href="{{ url_for('players.player_fullscreen', player_id=player.id) }}"
class="btn btn-success" style="display: inline-flex; align-items: center; gap: 0.5rem;"
target="_blank">
<img src="{{ url_for('static', filename='icons/monitor.svg') }}" alt="" style="width: 18px; height: 18px; filter: brightness(0) invert(1);">
View Live Content
</a>
</div>
</div>
{% endif %}
<!-- Three Column Layout -->
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 1.5rem;">
<!-- Card 1: Edit Credentials -->
<div class="card">
<h2 style="display: flex; align-items: center; gap: 0.5rem;">
<img src="{{ url_for('static', filename='icons/edit.svg') }}" alt="" style="width: 24px; height: 24px;">
Edit Credentials
</h2>
<form method="POST" style="margin-top: 1rem;">
<input type="hidden" name="action" value="update_credentials">
<div class="form-group">
<label for="name">Player Name *</label>
<input type="text" id="name" name="name" value="{{ player.name }}"
required minlength="3" class="form-control">
</div>
<div class="form-group">
<label for="location">Location</label>
<input type="text" id="location" name="location" value="{{ player.location or '' }}"
placeholder="e.g., Main Lobby" class="form-control">
</div>
<div class="form-group">
<label for="orientation">Orientation</label>
<select id="orientation" name="orientation" class="form-control">
<option value="Landscape" {% if player.orientation == 'Landscape' %}selected{% endif %}>Landscape</option>
<option value="Portrait" {% if player.orientation == 'Portrait' %}selected{% endif %}>Portrait</option>
</select>
</div>
<div style="border-top: 1px solid #ddd; margin: 1.5rem 0; padding-top: 1.5rem;">
<h4 style="margin: 0 0 1rem 0; font-size: 0.95rem;">🔑 Authentication Settings</h4>
<div class="form-group">
<label for="hostname">Hostname *</label>
<input type="text" id="hostname" name="hostname" value="{{ player.hostname }}"
required minlength="3" class="form-control"
placeholder="e.g., tv-terasa">
<small style="display: block; margin-top: 0.25rem; color: #6c757d;">
ℹ️ This is the unique identifier for the player
</small>
</div>
<div class="form-group">
<label for="password">New Password (leave blank to keep current)</label>
<input type="password" id="password" name="password" class="form-control"
placeholder="Enter new password">
<small style="display: block; margin-top: 0.25rem; color: #6c757d;">
🔒 Optional: Set a new password for player authentication
</small>
</div>
<div class="form-group">
<label for="quickconnect_code">Quick Connect Code</label>
<input type="text" id="quickconnect_code" name="quickconnect_code" class="form-control"
placeholder="e.g., 8887779">
<small style="display: block; margin-top: 0.25rem; color: #6c757d;">
🔗 Enter the plain text code (e.g., 8887779) - will be hashed automatically
</small>
</div>
</div>
<button type="submit" class="btn btn-success" style="width: 100%; display: flex; align-items: center; justify-content: center; gap: 0.5rem;">
<img src="{{ url_for('static', filename='icons/edit.svg') }}" alt="" style="width: 18px; height: 18px; filter: brightness(0) invert(1);">
Save Changes
</button>
</form>
</div>
<!-- Card 2: Assign Playlist -->
<div class="card">
<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;">
Assign Playlist
</h2>
<form method="POST" style="margin-top: 1rem;">
<input type="hidden" name="action" value="assign_playlist">
<div class="form-group">
<label for="playlist_id">Select Playlist</label>
<select id="playlist_id" name="playlist_id" class="form-control">
<option value="">-- No Playlist (Unassign) --</option>
{% for playlist in playlists %}
<option value="{{ playlist.id }}"
{% if player.playlist_id == playlist.id %}selected{% endif %}>
{{ playlist.name }} (v{{ playlist.version }}) - {{ playlist.contents.count() }} items
</option>
{% endfor %}
</select>
</div>
{% if current_playlist %}
<div class="info-box success">
<h4 style="margin: 0 0 0.5rem 0;">Currently Assigned:</h4>
<p style="margin: 0;"><strong>{{ current_playlist.name }}</strong></p>
<p style="margin: 0.25rem 0 0 0; font-size: 0.9rem;">Version: {{ current_playlist.version }}</p>
<p style="margin: 0.25rem 0 0 0; font-size: 0.9rem;">Content Items: {{ current_playlist.contents.count() }}</p>
<p style="margin: 0.25rem 0 0 0; font-size: 0.9rem; color: #6c757d;">
Updated: {{ current_playlist.updated_at | localtime }}
</p>
</div>
{% else %}
<div class="info-box warning">
<p style="margin: 0; display: flex; align-items: center; gap: 0.5rem;">
<img src="{{ url_for('static', filename='icons/warning.svg') }}" alt="" style="width: 18px; height: 18px;">
No playlist currently assigned to this player.
</p>
</div>
{% endif %}
<button type="submit" class="btn btn-success" style="width: 100%; display: flex; align-items: center; justify-content: center; gap: 0.5rem;">
<img src="{{ url_for('static', filename='icons/playlist.svg') }}" alt="" style="width: 18px; height: 18px; filter: brightness(0) invert(1);">
Assign Playlist
</button>
</form>
<div style="margin-top: 1.5rem; padding-top: 1.5rem; border-top: 1px solid #ddd;">
<h4>Quick Actions:</h4>
<a href="{{ url_for('content.content_list') }}" class="btn" style="width: 100%; margin-top: 0.5rem; display: flex; align-items: center; justify-content: center; gap: 0.5rem;">
<img src="{{ url_for('static', filename='icons/playlist.svg') }}" alt="" style="width: 18px; height: 18px; filter: brightness(0) invert(1);">
Create New Playlist
</a>
{% if current_playlist %}
<a href="{{ url_for('content.manage_playlist_content', playlist_id=current_playlist.id) }}"
class="btn" style="width: 100%; margin-top: 0.5rem; display: flex; align-items: center; justify-content: center; gap: 0.5rem;">
<img src="{{ url_for('static', filename='icons/edit.svg') }}" alt="" style="width: 18px; height: 18px; filter: brightness(0) invert(1);">
Edit Current Playlist
</a>
{% endif %}
<button onclick="confirmDelete()" class="btn" style="width: 100%; margin-top: 0.5rem; background: #dc3545; display: flex; align-items: center; justify-content: center; gap: 0.5rem;">
<span style="font-size: 1.2rem;">🗑️</span>
Delete Player
</button>
</div>
</div>
<!-- Card 3: Player Logs -->
<div class="card">
<h2 style="display: flex; align-items: center; gap: 0.5rem;">
<img src="{{ url_for('static', filename='icons/info.svg') }}" alt="" style="width: 24px; height: 24px;">
Player Logs
</h2>
<p style="color: #6c757d; font-size: 0.9rem;">Recent feedback from the player device</p>
<div style="max-height: 500px; overflow-y: auto; margin-top: 1rem;">
{% if recent_logs %}
{% for log in recent_logs %}
<div style="padding: 0.75rem; margin-bottom: 0.5rem; border-left: 4px solid
{% if log.status == 'error' %}#dc3545
{% elif log.status == 'warning' %}#ffc107
{% elif log.status == 'playing' %}#28a745
{% else %}#17a2b8{% endif %};
background: #f8f9fa; border-radius: 4px;">
<div style="display: flex; justify-content: space-between; align-items: start;">
<div style="flex: 1;">
<strong style="color:
{% if log.status == 'error' %}#dc3545
{% elif log.status == 'warning' %}#ffc107
{% elif log.status == 'playing' %}#28a745
{% else %}#17a2b8{% endif %};">
{% if log.status == 'error' %}❌
{% elif log.status == 'warning' %}⚠️
{% elif log.status == 'playing' %}▶️
{% elif log.status == 'restarting' %}🔄
{% else %}ℹ️{% endif %}
{{ log.status|upper }}
</strong>
<p style="margin: 0.25rem 0 0 0; font-size: 0.9rem;">{{ log.message }}</p>
{% if log.playlist_version %}
<p style="margin: 0.25rem 0 0 0; font-size: 0.85rem; color: #6c757d;">
Playlist v{{ log.playlist_version }}
</p>
{% endif %}
{% if log.error_details %}
<details style="margin-top: 0.5rem;">
<summary style="cursor: pointer; font-size: 0.85rem; color: #dc3545;">Error Details</summary>
<pre style="margin: 0.5rem 0 0 0; padding: 0.5rem; background: #fff; border: 1px solid #ddd; border-radius: 4px; font-size: 0.8rem; overflow-x: auto;">{{ log.error_details }}</pre>
</details>
{% endif %}
</div>
<small style="color: #6c757d; white-space: nowrap; margin-left: 1rem;">
{{ log.timestamp | localtime('%m/%d %H:%M') }}
</small>
</div>
</div>
{% endfor %}
{% else %}
<div style="text-align: center; padding: 2rem; color: #6c757d;">
<p>📭 No logs received yet</p>
<p style="font-size: 0.9rem;">Logs will appear here once the player starts sending feedback</p>
</div>
{% endif %}
</div>
</div>
</div>
<!-- Edited Media Section - Full Width -->
<div class="card" style="margin-top: 2rem;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem;">
<h2 style="display: flex; align-items: center; gap: 0.5rem; margin: 0;">
<img src="{{ url_for('static', filename='icons/edit.svg') }}" alt="" style="width: 24px; height: 24px;">
Edited Media on the Player
</h2>
{% if edited_media %}
<div style="display: flex; gap: 0.5rem;">
<a href="{{ url_for('players.edited_media', player_id=player.id) }}"
class="btn"
style="background: #7c3aed; color: white; padding: 0.5rem 1rem; text-decoration: none; border-radius: 6px; font-size: 0.9rem; display: inline-flex; align-items: center; gap: 0.5rem; transition: background 0.2s;"
onmouseover="this.style.background='#6d28d9'"
onmouseout="this.style.background='#7c3aed'">
📋 View All Edited Media
</a>
<a href="{{ url_for('players.edited_media_report', player_id=player.id) }}"
class="btn"
style="background: #059669; color: white; padding: 0.5rem 1rem; text-decoration: none; border-radius: 6px; font-size: 0.9rem; display: inline-flex; align-items: center; gap: 0.5rem; transition: background 0.2s;"
onmouseover="this.style.background='#047857'"
onmouseout="this.style.background='#059669'">
📊 Launch Report
</a>
</div>
{% endif %}
</div>
<p style="color: #6c757d; font-size: 0.9rem; margin-top: 0.5rem;">Latest 3 edited files with their most recent versions</p>
{% if edited_media %}
{% set edited_by_content = {} %}
{% for edit in edited_media %}
{% if edit.content_id not in edited_by_content %}
{% set _ = edited_by_content.update({edit.content_id: {'original_name': edit.original_name, 'content_id': edit.content_id, 'latest_version': edit}}) %}
{% endif %}
{% endfor %}
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 1.5rem; margin-top: 1.5rem;">
{% for content_id, data in edited_by_content.items() %}
{% if loop.index <= 3 %}
<div class="card" style="background: linear-gradient(135deg, #f8f9fa 0%, #fff 100%); border: 2px solid #7c3aed; box-shadow: 0 2px 8px rgba(124, 58, 237, 0.1);">
{% set edit = data.latest_version %}
<!-- Image Preview if it's an image -->
{% if edit.new_name.lower().endswith(('.jpg', '.jpeg', '.png', '.gif', '.webp')) %}
<div style="width: 100%; height: 200px; overflow: hidden; border-radius: 8px 8px 0 0; background: #000;">
<img src="{{ url_for('static', filename='uploads/edited_media/' ~ edit.content_id ~ '/' ~ edit.new_name) }}"
alt="{{ edit.new_name }}"
style="width: 100%; height: 100%; object-fit: contain;">
</div>
{% endif %}
<div style="padding: 1rem;">
<h3 style="margin: 0 0 0.75rem 0; color: #7c3aed; font-size: 1rem; display: flex; align-items: center; gap: 0.5rem;">
✏️ {{ data.original_name }}
</h3>
<div style="padding: 0.75rem; background: white; border-radius: 6px; border-left: 4px solid #7c3aed; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
<div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 0.5rem;">
<strong style="color: #7c3aed; font-size: 0.95rem;">
Version {{ edit.version }}
<span style="background: #7c3aed; color: white; padding: 0.15rem 0.5rem; border-radius: 12px; font-size: 0.75rem; margin-left: 0.5rem;">Latest</span>
</strong>
<small style="color: #6c757d; white-space: nowrap;">
{{ edit.created_at | localtime('%m/%d %H:%M') }}
</small>
</div>
<p style="margin: 0.25rem 0; font-size: 0.85rem; color: #475569;">
📄 {{ edit.new_name }}
</p>
{% if edit.user %}
<p style="margin: 0.25rem 0; font-size: 0.8rem; color: #64748b;">
👤 {{ edit.user }}
</p>
{% endif %}
{% if edit.time_of_modification %}
<p style="margin: 0.25rem 0; font-size: 0.8rem; color: #64748b;">
🕒 {{ edit.time_of_modification | localtime('%Y-%m-%d %H:%M') }}
</p>
{% endif %}
<div style="margin-top: 0.75rem; display: flex; gap: 0.5rem;">
<a href="{{ url_for('static', filename='uploads/edited_media/' ~ edit.content_id ~ '/' ~ edit.new_name) }}"
target="_blank"
style="display: inline-flex; align-items: center; gap: 0.25rem; padding: 0.4rem 0.75rem; background: #7c3aed; color: white; text-decoration: none; border-radius: 4px; font-size: 0.8rem; transition: background 0.2s;"
onmouseover="this.style.background='#6d28d9'"
onmouseout="this.style.background='#7c3aed'">
📥 View File
</a>
<a href="{{ url_for('static', filename='uploads/edited_media/' ~ edit.content_id ~ '/' ~ edit.new_name) }}"
download
style="display: inline-flex; align-items: center; gap: 0.25rem; padding: 0.4rem 0.75rem; background: #64748b; color: white; text-decoration: none; border-radius: 4px; font-size: 0.8rem; transition: background 0.2s;"
onmouseover="this.style.background='#475569'"
onmouseout="this.style.background='#64748b'">
💾 Download
</a>
</div>
</div>
</div>
</div>
{% endif %}
{% endfor %}
</div>
{% else %}
<div style="text-align: center; padding: 3rem; color: #6c757d; background: #f8f9fa; border-radius: 8px; margin-top: 1.5rem;">
<p style="font-size: 2rem; margin: 0;">📝</p>
<p style="margin: 0.5rem 0 0 0; font-size: 1.1rem; font-weight: 500;">No edited media yet</p>
<p style="font-size: 0.9rem; margin: 0.5rem 0 0 0;">Media edits will appear here once the player sends edited files</p>
</div>
{% endif %}
</div>
<!-- Additional Info Section -->
<div class="card" style="margin-top: 2rem;">
<h2>ℹ️ Player Information</h2>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1rem; margin-top: 1rem;">
<div>
<p><strong>Player ID:</strong> {{ player.id }}</p>
<p><strong>Created:</strong> {{ (player.created_at | localtime) if player.created_at else 'N/A' }}</p>
</div>
<div>
<p><strong>Orientation:</strong> {{ player.orientation }}</p>
<p><strong>Location:</strong> {{ player.location or 'Not set' }}</p>
</div>
<div>
<p><strong>Last Heartbeat:</strong>
{% if player.last_heartbeat %}
{{ player.last_heartbeat | localtime('%Y-%m-%d %H:%M:%S') }}
{% else %}
Never
{% endif %}
</p>
</div>
</div>
</div>
{% endblock %}