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:
@@ -3,8 +3,9 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Manage Group</title>
|
||||
<title>Manage Group - {{ group.name }}</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css" rel="stylesheet">
|
||||
<style>
|
||||
body.dark-mode {
|
||||
background-color: #121212;
|
||||
@@ -17,16 +18,72 @@
|
||||
.dark-mode label, .dark-mode th, .dark-mode td {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* Logo styling */
|
||||
.logo {
|
||||
max-height: 80px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
/* Mobile optimizations */
|
||||
@media (max-width: 768px) {
|
||||
.logo {
|
||||
max-height: 50px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
h1 {
|
||||
font-size: 1.5rem;
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
h5 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
h6 {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.btn {
|
||||
font-size: 0.9rem;
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.85rem;
|
||||
padding: 0.4rem 0.8rem;
|
||||
}
|
||||
.btn-sm {
|
||||
font-size: 0.75rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
}
|
||||
.card {
|
||||
margin-bottom: 1rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
.card-body {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
.badge {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
/* Stack buttons vertically on mobile */
|
||||
.action-buttons .btn {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
/* Smaller text on mobile */
|
||||
small {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
/* Reduce padding in tables */
|
||||
.list-group-item {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Smaller screens - further optimization */
|
||||
@media (max-width: 576px) {
|
||||
.container-fluid {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
h1 {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
.card-header h5 {
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,38 +106,135 @@
|
||||
.drag-over {
|
||||
border-top: 2px solid #0d6efd;
|
||||
}
|
||||
|
||||
/* Player status card compact design */
|
||||
.player-status-card {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.player-status-card {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="{{ 'dark-mode' if theme == 'dark' else '' }}">
|
||||
<div class="container py-5">
|
||||
<h1 class="text-center mb-4">Manage Group: {{ group.name }}</h1>
|
||||
|
||||
<!-- Group Information Card -->
|
||||
<div class="card mb-4 {{ 'dark-mode' if theme == 'dark' else '' }}">
|
||||
<div class="card-header bg-info text-white">
|
||||
<h2>Group Info</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p><strong>Group Name:</strong> {{ group.name }}</p>
|
||||
<p><strong>Number of Players:</strong> {{ group.players|length }}</p>
|
||||
<div class="container-fluid py-3 py-md-4 py-lg-5">
|
||||
<!-- Header with Logo and Title -->
|
||||
<div class="d-flex justify-content-start align-items-center mb-3 mb-md-4">
|
||||
{% if logo_exists %}
|
||||
<img src="{{ url_for('static', filename='resurse/logo.png') }}" alt="Logo" class="logo">
|
||||
{% endif %}
|
||||
<div>
|
||||
<h1 class="mb-1">Manage Group</h1>
|
||||
<p class="text-muted mb-0 d-none d-md-block">{{ group.name }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- List of Players in the Group -->
|
||||
<div class="card mb-4 {{ 'dark-mode' if theme == 'dark' else '' }}">
|
||||
<div class="card-header bg-secondary text-white">
|
||||
<h2>Players in Group</h2>
|
||||
<!-- Mobile: Show group name if not shown in header -->
|
||||
<div class="d-md-none mb-3">
|
||||
<div class="badge bg-primary fs-6">{{ group.name }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Row with Group Info (left) and Players Status (right) -->
|
||||
<div class="row mb-3 mb-md-4">
|
||||
<!-- Group Information Card - Responsive width -->
|
||||
<div class="col-lg-3 col-md-4 col-12 mb-3">
|
||||
<div class="card h-100 {{ 'dark-mode' if theme == 'dark' else '' }}">
|
||||
<div class="card-header bg-info text-white">
|
||||
<h5 class="mb-0"><i class="bi bi-info-circle me-2"></i>Group Info</h5>
|
||||
</div>
|
||||
<div class="card-body p-3">
|
||||
<div class="mb-2">
|
||||
<small class="text-muted">Group Name</small>
|
||||
<p class="mb-0"><strong>{{ group.name }}</strong></p>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<small class="text-muted">Players</small>
|
||||
<p class="mb-0"><strong>{{ group.players|length }}</strong></p>
|
||||
</div>
|
||||
<div class="mb-0">
|
||||
<small class="text-muted">Playlist Version</small>
|
||||
<p class="mb-0"><span class="badge bg-info mt-1">v{{ group.playlist_version }}</span></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul class="list-group">
|
||||
{% for player in group.players %}
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<strong>{{ player.username }}</strong> ({{ player.hostname }})
|
||||
|
||||
<!-- Players Status Cards Container - 3/4 width on large screens -->
|
||||
<div class="col-lg-9 col-md-8 col-12">
|
||||
<div class="card {{ 'dark-mode' if theme == 'dark' else '' }}">
|
||||
<div class="card-header bg-success text-white">
|
||||
<h5 class="mb-0"><i class="bi bi-display me-2"></i>Players ({{ group.players|length }})</h5>
|
||||
</div>
|
||||
<div class="card-body p-2 p-md-3">
|
||||
{% if players_status %}
|
||||
<div class="row g-2 g-md-3">
|
||||
{% for player_status in players_status %}
|
||||
<div class="col-xl-4 col-lg-6 col-12 mb-2">
|
||||
<div class="card h-100 border-primary player-status-card {{ 'dark-mode' if theme == 'dark' else '' }}">
|
||||
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center py-2">
|
||||
<h6 class="mb-0"><i class="bi bi-tv me-1"></i>{{ player_status.player.username }}</h6>
|
||||
<a href="{{ url_for('player_page', player_id=player_status.player.id) }}"
|
||||
class="btn btn-sm btn-light py-0 px-2" title="View Details">
|
||||
<i class="bi bi-eye"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-body p-2">
|
||||
<div class="mb-2">
|
||||
<small class="text-muted"><i class="bi bi-hdd-network me-1"></i>Hostname:</small>
|
||||
<small class="d-block">{{ player_status.player.hostname }}</small>
|
||||
</div>
|
||||
|
||||
{% if player_status.feedback %}
|
||||
<div class="mb-2">
|
||||
<small class="text-muted"><i class="bi bi-activity me-1"></i>Status:</small>
|
||||
<span class="badge bg-{{ 'success' if player_status.feedback[0].status in ['active', 'playing'] else 'danger' }}">
|
||||
{{ player_status.feedback[0].status|title }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<small class="text-muted"><i class="bi bi-clock me-1"></i>Last Activity:</small>
|
||||
<small class="d-block">{{ player_status.feedback[0].timestamp.strftime('%Y-%m-%d %H:%M:%S') }}</small>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<small class="text-muted"><i class="bi bi-chat-dots me-1"></i>Message:</small>
|
||||
<small class="d-block text-muted">{{ player_status.feedback[0].message[:50] }}{% if player_status.feedback[0].message|length > 50 %}...{% endif %}</small>
|
||||
</div>
|
||||
<div class="mb-0">
|
||||
<small class="text-muted"><i class="bi bi-list-check me-1"></i>Playlist:</small>
|
||||
{% if player_status.feedback[0].playlist_version %}
|
||||
{% if player_status.feedback[0].playlist_version|int == player_status.server_playlist_version %}
|
||||
<span class="badge bg-success">v{{ player_status.feedback[0].playlist_version }} ✓</span>
|
||||
<small class="text-success d-block">In sync</small>
|
||||
{% else %}
|
||||
<span class="badge bg-warning text-dark">v{{ player_status.feedback[0].playlist_version }}</span>
|
||||
<small class="text-warning d-block">⚠ Out of sync (server: v{{ player_status.server_playlist_version }})</small>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">Unknown</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center text-muted py-2">
|
||||
<p class="mb-1"><small>No status data</small></p>
|
||||
<small>Player hasn't reported yet</small>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center text-muted py-3">
|
||||
<i class="bi bi-inbox display-4 d-block mb-2"></i>
|
||||
<p>No players in this group</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -130,16 +284,23 @@
|
||||
|
||||
<!-- Media Thumbnail and Name -->
|
||||
<div class="flex-grow-1 mb-2 mb-md-0 d-flex align-items-center">
|
||||
<img src="{{ url_for('static', filename='uploads/' ~ media.file_name) }}"
|
||||
alt="thumbnail"
|
||||
style="width: 48px; height: 48px; object-fit: cover; margin-right: 10px; border-radius: 4px;"
|
||||
onerror="this.style.display='none';">
|
||||
{% set file_ext = media.file_name.lower().split('.')[-1] %}
|
||||
{% if file_ext in ['mp4', 'avi', 'mkv', 'mov', 'webm'] %}
|
||||
<!-- Video file - show generic video icon -->
|
||||
<div style="width: 48px; height: 48px; margin-right: 10px; border-radius: 4px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); display: flex; align-items: center; justify-content: center;">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 5v14l11-7z" fill="white"/>
|
||||
</svg>
|
||||
</div>
|
||||
{% else %}
|
||||
<!-- Image file - show actual thumbnail -->
|
||||
<img src="{{ url_for('static', filename='uploads/' ~ media.file_name) }}"
|
||||
alt="thumbnail"
|
||||
style="width: 48px; height: 48px; object-fit: cover; margin-right: 10px; border-radius: 4px;"
|
||||
onerror="this.style.display='none';">
|
||||
{% endif %}
|
||||
<p class="mb-0"><strong>Media Name:</strong> {{ media.file_name }}</p>
|
||||
</div>
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
|
||||
>>>>>>> 2255cc2 (Show media thumbnails in manage group page, matching player page style)
|
||||
<form action="{{ url_for('edit_group_media_route', group_id=group.id, content_id=media.id) }}" method="post" class="d-flex align-items-center">
|
||||
<div class="input-group me-2">
|
||||
<span class="input-group-text">seconds</span>
|
||||
@@ -162,12 +323,19 @@
|
||||
</div>
|
||||
|
||||
<!-- Upload Media Button -->
|
||||
<div class="text-center mb-4">
|
||||
<a href="{{ url_for('upload_content', target_type='group', target_id=group.id, return_url=url_for('manage_group', group_id=group.id)) }}" class="btn btn-primary btn-lg">Go to Upload Media</a>
|
||||
<div class="text-center mb-3 action-buttons">
|
||||
<a href="{{ url_for('upload_content', target_type='group', target_id=group.id, return_url=url_for('manage_group', group_id=group.id)) }}"
|
||||
class="btn btn-primary btn-lg">
|
||||
<i class="bi bi-cloud-upload me-2"></i>Upload Media
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Back to Dashboard Button -->
|
||||
<a href="{{ url_for('dashboard') }}" class="btn btn-secondary">Back to Dashboard</a>
|
||||
<div class="text-center mb-3">
|
||||
<a href="{{ url_for('dashboard') }}" class="btn btn-secondary">
|
||||
<i class="bi bi-arrow-left me-2"></i>Back to Dashboard
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
Reference in New Issue
Block a user