checked for updates

This commit is contained in:
2025-07-16 11:19:31 +03:00
parent c36ba9dc64
commit 94fad22d85
12 changed files with 1303 additions and 354 deletions

View File

@@ -0,0 +1,173 @@
{% extends "base.html" %}
{% block title %}Upload Content - SKE Digital Signage{% endblock %}
{% block content %}
<div class="container-fluid py-4">
<!-- Page Header -->
<div class="row mb-4">
<div class="col">
<h1><i class="bi bi-cloud-upload"></i> Upload Content</h1>
<p class="text-muted">Upload images, videos, or documents to your players and groups</p>
</div>
<div class="col-auto">
<a href="{{ return_url }}" class="btn btn-secondary">
<i class="bi bi-arrow-left"></i> Back
</a>
</div>
</div>
<!-- Upload Form -->
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h5><i class="bi bi-upload"></i> Upload Files</h5>
</div>
<div class="card-body">
<form method="POST" enctype="multipart/form-data">
<!-- Target Selection -->
<div class="row mb-3">
<div class="col-md-6">
<label for="target_type" class="form-label">Target Type</label>
<select class="form-select" id="target_type" name="target_type" required>
<option value="">Select target type...</option>
<option value="player" {% if target_type == 'player' %}selected{% endif %}>Player</option>
<option value="group" {% if target_type == 'group' %}selected{% endif %}>Group</option>
</select>
</div>
<div class="col-md-6">
<label for="target_id" class="form-label">Target</label>
<select class="form-select" id="target_id" name="target_id" required>
<option value="">Select target...</option>
{% if target_type == 'player' and target_id %}
{% for player in players %}
<option value="{{ player.id }}"
{% if player.id|string == target_id|string %}selected{% endif %}>
{{ player.username }} ({{ player.hostname }})
</option>
{% endfor %}
{% elif target_type == 'group' and target_id %}
{% for group in groups %}
<option value="{{ group.id }}"
{% if group.id|string == target_id|string %}selected{% endif %}>
{{ group.name }} ({{ group.player_count }} players)
</option>
{% endfor %}
{% endif %}
</select>
</div>
</div>
<!-- File Selection -->
<div class="mb-3">
<label for="files" class="form-label">Files</label>
<input type="file" class="form-control" id="files" name="files" multiple required
accept="image/*,video/*,.pdf,.ppt,.pptx">
<div class="form-text">
Supported formats: Images (PNG, JPG, GIF), Videos (MP4, AVI, MOV), Documents (PDF, PowerPoint)
</div>
</div>
<!-- Duration Setting -->
<div class="mb-3">
<label for="duration" class="form-label">Display Duration (seconds)</label>
<input type="number" class="form-control" id="duration" name="duration"
value="10" min="1" max="3600" required>
<div class="form-text">
How long each item should be displayed (for images and documents)
</div>
</div>
<!-- Hidden fields -->
<input type="hidden" name="return_url" value="{{ return_url }}">
<!-- Submit Button -->
<div class="d-grid">
<button type="submit" class="btn btn-primary btn-lg">
<i class="bi bi-cloud-upload"></i> Upload Files
</button>
</div>
</form>
</div>
</div>
<!-- Upload Guidelines -->
<div class="card mt-4">
<div class="card-header">
<h6><i class="bi bi-info-circle"></i> Upload Guidelines</h6>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-4">
<h6>Images</h6>
<ul class="small">
<li>PNG, JPG, GIF, BMP, WebP</li>
<li>Will be optimized automatically</li>
<li>Best: 1920x1080 resolution</li>
</ul>
</div>
<div class="col-md-4">
<h6>Videos</h6>
<ul class="small">
<li>MP4, AVI, MOV, WMV, WebM</li>
<li>Converted to web-compatible MP4</li>
<li>Duration setting is ignored</li>
</ul>
</div>
<div class="col-md-4">
<h6>Documents</h6>
<ul class="small">
<li>PDF, PowerPoint (PPT/PPTX)</li>
<li>Converted to images per page/slide</li>
<li>Each page uses duration setting</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
document.addEventListener('DOMContentLoaded', function() {
const targetTypeSelect = document.getElementById('target_type');
const targetIdSelect = document.getElementById('target_id');
// Players and groups data
const players = {{ players|tojson }};
const groups = {{ groups|tojson }};
function updateTargetOptions() {
const targetType = targetTypeSelect.value;
targetIdSelect.innerHTML = '<option value="">Select target...</option>';
if (targetType === 'player') {
players.forEach(player => {
const option = document.createElement('option');
option.value = player.id;
option.textContent = `${player.username} (${player.hostname})`;
targetIdSelect.appendChild(option);
});
} else if (targetType === 'group') {
groups.forEach(group => {
const option = document.createElement('option');
option.value = group.id;
option.textContent = `${group.name} (${group.player_count} players)`;
targetIdSelect.appendChild(option);
});
}
}
targetTypeSelect.addEventListener('change', updateTargetOptions);
// Initialize if target_type is already selected
if (targetTypeSelect.value) {
updateTargetOptions();
}
});
</script>
{% endblock %}

View File

@@ -59,7 +59,7 @@
<div class="d-flex justify-content-between">
<div>
<h5 class="card-title">Active Players</h5>
<h2>{{ players|selectattr('is_active')|list|length }}</h2>
<h2>{{ active_players }}</h2>
</div>
<div class="align-self-center">
<i class="bi bi-play-circle display-6"></i>
@@ -75,7 +75,7 @@
<div class="d-flex justify-content-between">
<div>
<h5 class="card-title">Total Content</h5>
<h2>{{ players|sum(attribute='content')|length }}</h2>
<h2>{{ total_content }}</h2>
</div>
<div class="align-self-center">
<i class="bi bi-file-earmark-play display-6"></i>

View File

@@ -0,0 +1,83 @@
{% extends "base.html" %}
{% block title %}Add Player - SKE Digital Signage{% endblock %}
{% block content %}
<div class="container-fluid py-4">
<!-- Page Header -->
<div class="row mb-4">
<div class="col">
<h1><i class="bi bi-plus-circle"></i> Add Player</h1>
<p class="text-muted">Create a new digital signage player</p>
</div>
<div class="col-auto">
<a href="{{ url_for('dashboard.index') }}" class="btn btn-secondary">
<i class="bi bi-arrow-left"></i> Back to Dashboard
</a>
</div>
</div>
<!-- Add Player Form -->
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h5><i class="bi bi-display"></i> Player Information</h5>
</div>
<div class="card-body">
<form method="POST">
<div class="mb-3">
<label for="username" class="form-label">Username *</label>
<input type="text" class="form-control" id="username" name="username"
required placeholder="Enter player username">
<div class="form-text">Display name for this player</div>
</div>
<div class="mb-3">
<label for="hostname" class="form-label">Hostname *</label>
<input type="text" class="form-control" id="hostname" name="hostname"
required placeholder="Enter unique hostname">
<div class="form-text">Unique identifier for API access (e.g., display-001)</div>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password *</label>
<input type="password" class="form-control" id="password" name="password"
required placeholder="Enter secure password">
<div class="form-text">Password for manual authentication</div>
</div>
<div class="mb-3">
<label for="quickconnect_password" class="form-label">Quick Connect Code</label>
<input type="password" class="form-control" id="quickconnect_password"
name="quickconnect_password" placeholder="Enter quick connect code">
<div class="form-text">Optional code for quick API access</div>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary">
<i class="bi bi-plus"></i> Create Player
</button>
</div>
</form>
</div>
</div>
<!-- Help Card -->
<div class="card mt-4">
<div class="card-header">
<h6><i class="bi bi-question-circle"></i> Player Setup Guide</h6>
</div>
<div class="card-body">
<ol class="small">
<li><strong>Username:</strong> Choose a descriptive name (e.g., "Lobby Display", "Conference Room")</li>
<li><strong>Hostname:</strong> Must be unique across all players (e.g., "lobby-01", "conf-room-a")</li>
<li><strong>Password:</strong> Used for manual authentication in fullscreen mode</li>
<li><strong>Quick Connect:</strong> Optional code for automated player client connections</li>
</ol>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,59 @@
{% extends "base.html" %}
{% block title %}Player Authentication - SKE Digital Signage{% endblock %}
{% block content %}
<div class="container-fluid py-4">
<div class="row justify-content-center">
<div class="col-md-4">
<div class="card">
<div class="card-header text-center">
<h4><i class="bi bi-shield-lock"></i> Player Authentication</h4>
<p class="text-muted mb-0">{{ player.username }}</p>
</div>
<div class="card-body">
{% if error %}
<div class="alert alert-danger">{{ error }}</div>
{% endif %}
<form method="POST">
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control form-control-lg"
id="password" name="password" required autofocus
placeholder="Enter player password">
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary btn-lg">
<i class="bi bi-unlock"></i> Authenticate
</button>
</div>
</form>
<hr>
<div class="text-center">
<small class="text-muted">
<i class="bi bi-info-circle"></i>
Enter the password to access this player's display
</small>
</div>
</div>
</div>
</div>
</div>
</div>
<style>
body {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
}
.card {
box-shadow: 0 10px 25px rgba(0,0,0,0.1);
border: none;
}
</style>
{% endblock %}

View File

@@ -0,0 +1,168 @@
{% extends "base.html" %}
{% block title %}Edit Player - SKE Digital Signage{% endblock %}
{% block content %}
<div class="container-fluid py-4">
<!-- Page Header -->
<div class="row mb-4">
<div class="col">
<h1><i class="bi bi-pencil-square"></i> Edit Player: {{ player.username }}</h1>
<p class="text-muted">Modify player settings</p>
</div>
<div class="col-auto">
<a href="{{ url_for('dashboard.index') }}" class="btn btn-secondary">
<i class="bi bi-arrow-left"></i> Back to Dashboard
</a>
</div>
</div>
<div class="row">
<!-- Edit Form -->
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h5><i class="bi bi-gear"></i> Player Settings</h5>
</div>
<div class="card-body">
<form method="POST">
<div class="mb-3">
<label for="username" class="form-label">Username *</label>
<input type="text" class="form-control" id="username" name="username"
value="{{ player.username }}" required>
</div>
<div class="mb-3">
<label for="hostname" class="form-label">Hostname *</label>
<input type="text" class="form-control" id="hostname" name="hostname"
value="{{ player.hostname }}" required>
<div class="form-text">Must be unique across all players</div>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password"
placeholder="Leave blank to keep current password">
</div>
<div class="mb-3">
<label for="quickconnect_password" class="form-label">Quick Connect Code</label>
<input type="password" class="form-control" id="quickconnect_password"
name="quickconnect_password" value="{{ player.quickconnect_password or '' }}"
placeholder="Optional quick connect code">
</div>
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="is_active" name="is_active"
{{ 'checked' if player.is_active else '' }}>
<label class="form-check-label" for="is_active">
Active Player
</label>
</div>
<div class="form-text">Inactive players cannot receive content updates</div>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary">
<i class="bi bi-check"></i> Update Player
</button>
</div>
</form>
</div>
</div>
</div>
<!-- Player Info -->
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h5><i class="bi bi-info-circle"></i> Player Details</h5>
</div>
<div class="card-body">
<div class="row mb-2">
<div class="col-sm-4"><strong>ID:</strong></div>
<div class="col-sm-8">{{ player.id }}</div>
</div>
<div class="row mb-2">
<div class="col-sm-4"><strong>Created:</strong></div>
<div class="col-sm-8">{{ player.created_at.strftime('%Y-%m-%d %H:%M') if player.created_at else 'N/A' }}</div>
</div>
<div class="row mb-2">
<div class="col-sm-4"><strong>Last Seen:</strong></div>
<div class="col-sm-8">
{% if player.last_seen %}
{{ player.last_seen.strftime('%Y-%m-%d %H:%M') }}
{% else %}
<span class="text-muted">Never</span>
{% endif %}
</div>
</div>
<div class="row mb-2">
<div class="col-sm-4"><strong>Status:</strong></div>
<div class="col-sm-8">
{% if player.is_active %}
<span class="badge bg-success">Active</span>
{% else %}
<span class="badge bg-secondary">Inactive</span>
{% endif %}
</div>
</div>
<div class="row mb-2">
<div class="col-sm-4"><strong>Groups:</strong></div>
<div class="col-sm-8">
{% if player.groups %}
{% for group in player.groups %}
<span class="badge bg-info">{{ group.name }}</span>
{% endfor %}
{% else %}
<span class="text-muted">No groups assigned</span>
{% endif %}
</div>
</div>
</div>
</div>
<!-- Actions -->
<div class="card mt-3">
<div class="card-header">
<h6><i class="bi bi-tools"></i> Actions</h6>
</div>
<div class="card-body">
<div class="d-grid gap-2">
<a href="{{ url_for('player.fullscreen', player_id=player.id) }}"
class="btn btn-info" target="_blank">
<i class="bi bi-fullscreen"></i> Open Fullscreen Display
</a>
<button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">
<i class="bi bi-trash"></i> Delete Player
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Delete Confirmation Modal -->
<div class="modal fade" id="deleteModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Confirm Delete</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p>Are you sure you want to delete player <strong>{{ player.username }}</strong>?</p>
<p class="text-danger small">This action cannot be undone and will remove all player data.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<form method="POST" action="{{ url_for('player.delete', player_id=player.id) }}" style="display:inline;">
<button type="submit" class="btn btn-danger">Delete Player</button>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,273 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ player.username }} - Digital Signage Display</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #000;
color: #fff;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
overflow: hidden;
cursor: none;
}
.fullscreen-container {
width: 100vw;
height: 100vh;
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
.content-item {
width: 100%;
height: 100%;
display: none;
align-items: center;
justify-content: center;
position: absolute;
top: 0;
left: 0;
}
.content-item.active {
display: flex;
}
.content-item img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.content-item video {
width: 100%;
height: 100%;
object-fit: cover;
}
.no-content {
text-align: center;
color: #666;
}
.no-content h1 {
font-size: 3rem;
margin-bottom: 1rem;
}
.no-content p {
font-size: 1.5rem;
}
.player-info {
position: absolute;
bottom: 20px;
right: 20px;
background: rgba(0,0,0,0.7);
padding: 10px 15px;
border-radius: 5px;
font-size: 0.9rem;
z-index: 1000;
}
.loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 2rem;
color: #666;
}
.error-message {
color: #ff6b6b;
text-align: center;
font-size: 1.5rem;
}
</style>
</head>
<body>
<div class="fullscreen-container">
<div id="loading" class="loading">
<i></i> Loading content...
</div>
{% if content %}
{% for item in content %}
<div class="content-item" data-duration="{{ item.duration }}" data-id="{{ item.id }}">
{% if item.content_type.startswith('image/') %}
<img src="{{ url_for('static', filename='uploads/' + item.file_name) }}"
alt="{{ item.original_name or item.file_name }}">
{% elif item.content_type.startswith('video/') %}
<video muted autoplay>
<source src="{{ url_for('static', filename='uploads/' + item.file_name) }}"
type="{{ item.content_type }}">
</video>
{% endif %}
</div>
{% endfor %}
{% else %}
<div class="no-content">
<h1>📺</h1>
<h2>{{ player.username }}</h2>
<p>No content available</p>
<p style="margin-top: 2rem; font-size: 1rem; color: #888;">
Waiting for content assignment...
</p>
</div>
{% endif %}
<div class="player-info">
<div>{{ player.username }}</div>
<div style="font-size: 0.8rem; color: #ccc;">{{ player.hostname }}</div>
<div style="font-size: 0.7rem; color: #999;">Last updated: <span id="lastUpdate">--:--:--</span></div>
</div>
</div>
<script>
class DigitalSignagePlayer {
constructor() {
this.contentItems = document.querySelectorAll('.content-item');
this.currentIndex = 0;
this.isPlaying = false;
this.refreshInterval = 30000; // 30 seconds
this.playerId = {{ player.id|tojson }};
this.init();
}
init() {
// Hide loading
document.getElementById('loading').style.display = 'none';
// Set initial time
this.updateDisplayTime();
if (this.contentItems.length > 0) {
this.startSlideshow();
} else {
console.log('No content available');
}
// Set up content refresh
setInterval(() => this.checkForUpdates(), this.refreshInterval);
// Update last update time
setInterval(() => this.updateLastSeen(), 60000);
}
startSlideshow() {
if (this.contentItems.length === 0) return;
this.isPlaying = true;
this.showContent(0);
}
showContent(index) {
if (index >= this.contentItems.length) {
index = 0;
}
// Hide all content
this.contentItems.forEach(item => {
item.classList.remove('active');
const video = item.querySelector('video');
if (video) {
video.pause();
video.currentTime = 0;
}
});
// Show current content
const currentItem = this.contentItems[index];
currentItem.classList.add('active');
const video = currentItem.querySelector('video');
if (video) {
video.play();
}
// Get duration and schedule next
const duration = parseInt(currentItem.dataset.duration) * 1000;
setTimeout(() => {
this.currentIndex = (index + 1) % this.contentItems.length;
this.showContent(this.currentIndex);
}, duration);
}
checkForUpdates() {
fetch(`/api/player/${this.playerId}/content`)
.then(response => response.json())
.then(data => {
if (data.updated) {
console.log('Content updated, reloading...');
window.location.reload();
}
})
.catch(error => {
console.error('Error checking for updates:', error);
});
}
updateLastSeen() {
fetch(`/api/player/${this.playerId}/heartbeat`, {
method: 'POST'
}).catch(error => {
console.error('Error updating last seen:', error);
});
// Update display time
this.updateDisplayTime();
}
updateDisplayTime() {
const now = new Date();
document.getElementById('lastUpdate').textContent =
now.toLocaleTimeString('en-US', { hour12: false });
}
}
// Start the player when page loads
document.addEventListener('DOMContentLoaded', () => {
new DigitalSignagePlayer();
});
// Prevent right-click context menu
document.addEventListener('contextmenu', e => e.preventDefault());
// Handle keyboard shortcuts
document.addEventListener('keydown', (e) => {
if (e.key === 'F11') {
// Allow F11 for fullscreen toggle
return;
}
if (e.key === 'Escape') {
// Allow Escape to exit fullscreen
return;
}
// Prevent other keys
e.preventDefault();
});
// Auto-enter fullscreen on load
document.addEventListener('DOMContentLoaded', () => {
if (document.documentElement.requestFullscreen) {
document.documentElement.requestFullscreen().catch(err => {
console.log('Fullscreen not supported or denied');
});
}
});
</script>
</body>
</html>

View File

@@ -0,0 +1,271 @@
{% extends "base.html" %}
{% block title %}{{ player.username }} - Player View{% endblock %}
{% block content %}
<div class="container-fluid py-4">
<!-- Page Header -->
<div class="row mb-4">
<div class="col">
<h1><i class="bi bi-display"></i> {{ player.username }}</h1>
<p class="text-muted">Player details and content management</p>
</div>
<div class="col-auto">
<a href="{{ url_for('dashboard.index') }}" class="btn btn-secondary">
<i class="bi bi-arrow-left"></i> Back to Dashboard
</a>
<a href="{{ url_for('player.edit', player_id=player.id) }}" class="btn btn-primary">
<i class="bi bi-pencil"></i> Edit Player
</a>
</div>
</div>
<div class="row">
<!-- Player Information -->
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h5><i class="bi bi-info-circle"></i> Player Information</h5>
</div>
<div class="card-body">
<div class="row mb-2">
<div class="col-sm-5"><strong>Username:</strong></div>
<div class="col-sm-7">{{ player.username }}</div>
</div>
<div class="row mb-2">
<div class="col-sm-5"><strong>Hostname:</strong></div>
<div class="col-sm-7"><code>{{ player.hostname }}</code></div>
</div>
<div class="row mb-2">
<div class="col-sm-5"><strong>Status:</strong></div>
<div class="col-sm-7">
{% if player.is_active %}
<span class="badge bg-success">Active</span>
{% else %}
<span class="badge bg-secondary">Inactive</span>
{% endif %}
</div>
</div>
<div class="row mb-2">
<div class="col-sm-5"><strong>Created:</strong></div>
<div class="col-sm-7">{{ player.created_at.strftime('%Y-%m-%d %H:%M') if player.created_at else 'N/A' }}</div>
</div>
<div class="row mb-2">
<div class="col-sm-5"><strong>Last Seen:</strong></div>
<div class="col-sm-7">
{% if player.last_seen %}
{{ player.last_seen.strftime('%Y-%m-%d %H:%M') }}
{% else %}
<span class="text-muted">Never</span>
{% endif %}
</div>
</div>
<div class="row mb-2">
<div class="col-sm-5"><strong>Groups:</strong></div>
<div class="col-sm-7">
{% if player.groups %}
{% for group in player.groups %}
<span class="badge bg-info me-1">{{ group.name }}</span>
{% endfor %}
{% else %}
<span class="text-muted">No groups</span>
{% endif %}
</div>
</div>
</div>
</div>
<!-- Quick Actions -->
<div class="card mt-3">
<div class="card-header">
<h6><i class="bi bi-lightning"></i> Quick Actions</h6>
</div>
<div class="card-body">
<div class="d-grid gap-2">
<a href="{{ url_for('player.fullscreen', player_id=player.id) }}"
class="btn btn-info" target="_blank">
<i class="bi bi-fullscreen"></i> Open Display
</a>
<button type="button" class="btn btn-success" onclick="refreshPlayer()">
<i class="bi bi-arrow-clockwise"></i> Refresh Content
</button>
</div>
</div>
</div>
</div>
<!-- Content Management -->
<div class="col-md-8">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5><i class="bi bi-collection-play"></i> Player Content</h5>
<a href="{{ url_for('content.upload') }}" class="btn btn-sm btn-primary">
<i class="bi bi-plus"></i> Add Content
</a>
</div>
<div class="card-body">
{% if content %}
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Position</th>
<th>Filename</th>
<th>Type</th>
<th>Duration</th>
<th>Uploaded</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for item in content %}
<tr>
<td>
<span class="badge bg-primary">{{ item.position }}</span>
</td>
<td>
<strong>{{ item.file_name }}</strong>
{% if item.original_name %}
<br><small class="text-muted">{{ item.original_name }}</small>
{% endif %}
</td>
<td>
{% if item.content_type.startswith('image/') %}
<span class="badge bg-success"><i class="bi bi-image"></i> Image</span>
{% elif item.content_type.startswith('video/') %}
<span class="badge bg-info"><i class="bi bi-play-circle"></i> Video</span>
{% else %}
<span class="badge bg-secondary">{{ item.content_type }}</span>
{% endif %}
</td>
<td>{{ item.duration }}s</td>
<td>{{ item.uploaded_at.strftime('%m/%d %H:%M') if item.uploaded_at else 'N/A' }}</td>
<td>
<div class="btn-group btn-group-sm">
<button type="button" class="btn btn-outline-primary"
onclick="previewContent('{{ item.id }}', '{{ item.file_name }}', '{{ item.content_type }}')">
<i class="bi bi-eye"></i>
</button>
<button type="button" class="btn btn-outline-danger"
onclick="removeContent('{{ item.id }}', '{{ item.file_name }}')">
<i class="bi bi-trash"></i>
</button>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-center py-4">
<i class="bi bi-collection-play text-muted" style="font-size: 3rem;"></i>
<h5 class="text-muted mt-2">No Content Available</h5>
<p class="text-muted">This player doesn't have any content assigned yet.</p>
<a href="{{ url_for('content.upload') }}" class="btn btn-primary">
<i class="bi bi-plus"></i> Upload First Content
</a>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
<!-- Content Preview Modal -->
<div class="modal fade" id="previewModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Content Preview</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body text-center" id="previewContent">
<!-- Content will be loaded here -->
</div>
</div>
</div>
</div>
<!-- Remove Content Modal -->
<div class="modal fade" id="removeModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Remove Content</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p>Are you sure you want to remove <strong id="removeFilename"></strong> from this player?</p>
<p class="text-warning small">This will only remove the content from this player, not delete the file.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger" onclick="confirmRemoveContent()">Remove Content</button>
</div>
</div>
</div>
</div>
<script>
let currentContentId = null;
function refreshPlayer() {
// Placeholder for content refresh functionality
alert('Content refresh signal sent to player!');
}
function previewContent(contentId, filename, contentType) {
const modal = new bootstrap.Modal(document.getElementById('previewModal'));
const previewDiv = document.getElementById('previewContent');
if (contentType.startsWith('image/')) {
previewDiv.innerHTML = `<img src="/static/uploads/${filename}" class="img-fluid" alt="${filename}">`;
} else if (contentType.startsWith('video/')) {
previewDiv.innerHTML = `<video controls class="w-100" style="max-height: 400px;">
<source src="/static/uploads/${filename}" type="${contentType}">
Your browser does not support the video tag.
</video>`;
} else {
previewDiv.innerHTML = `<p>Preview not available for this content type: ${contentType}</p>`;
}
modal.show();
}
function removeContent(contentId, filename) {
currentContentId = contentId;
document.getElementById('removeFilename').textContent = filename;
const modal = new bootstrap.Modal(document.getElementById('removeModal'));
modal.show();
}
function confirmRemoveContent() {
if (currentContentId) {
// Make API call to remove content from player
fetch(`/api/content/${currentContentId}/remove-from-player`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
player_id: {{ player.id|tojson }}
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert('Error removing content: ' + data.message);
}
})
.catch(error => {
console.error('Error:', error);
alert('Error removing content');
});
}
}
</script>
{% endblock %}