updated features to upload pptx files
This commit is contained in:
@@ -3,6 +3,215 @@
|
||||
{% 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;
|
||||
}
|
||||
</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/monitor.svg') }}" alt="" style="width: 32px; height: 32px;">
|
||||
@@ -15,7 +224,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Player Status Overview -->
|
||||
<div class="card" style="margin-bottom: 2rem; background: {% if player.status == 'online' %}#d4edda{% elif player.status == 'offline' %}#f8d7da{% else %}#fff3cd{% endif %};">
|
||||
<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' %}
|
||||
@@ -52,6 +261,47 @@
|
||||
</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.strftime('%Y-%m-%d %H:%M') }}</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;">
|
||||
|
||||
@@ -64,33 +314,44 @@
|
||||
<form method="POST" style="margin-top: 1rem;">
|
||||
<input type="hidden" name="action" value="update_credentials">
|
||||
|
||||
<div style="margin-bottom: 1rem;">
|
||||
<label for="name" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">Player Name *</label>
|
||||
<div class="form-group">
|
||||
<label for="name">Player Name *</label>
|
||||
<input type="text" id="name" name="name" value="{{ player.name }}"
|
||||
required minlength="3"
|
||||
style="width: 100%; padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px;">
|
||||
required minlength="3" class="form-control">
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 1rem;">
|
||||
<label for="location" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">Location</label>
|
||||
<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"
|
||||
style="width: 100%; padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px;">
|
||||
placeholder="e.g., Main Lobby" class="form-control">
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 1rem;">
|
||||
<label for="orientation" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">Orientation</label>
|
||||
<select id="orientation" name="orientation"
|
||||
style="width: 100%; padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px;">
|
||||
<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="padding: 1rem; background: #f8f9fa; border-radius: 4px; margin-bottom: 1rem;">
|
||||
<p style="margin: 0; font-size: 0.9rem;"><strong>Hostname:</strong> {{ player.hostname }}</p>
|
||||
<p style="margin: 0.5rem 0 0 0; font-size: 0.9rem;"><strong>Auth Code:</strong> <code>{{ player.auth_code }}</code></p>
|
||||
<p style="margin: 0.5rem 0 0 0; font-size: 0.9rem;"><strong>Quick Connect:</strong> {{ player.quickconnect_code or 'Not set' }}</p>
|
||||
<div class="info-box neutral">
|
||||
<h4 style="margin: 0 0 1rem 0; font-size: 0.95rem; color: #495057;">🔑 Player Credentials</h4>
|
||||
|
||||
<div class="credential-item">
|
||||
<span class="credential-label">Hostname</span>
|
||||
<div class="credential-value">{{ player.hostname }}</div>
|
||||
</div>
|
||||
|
||||
<div class="credential-item">
|
||||
<span class="credential-label">Auth Code</span>
|
||||
<div class="credential-value">{{ player.auth_code }}</div>
|
||||
</div>
|
||||
|
||||
<div class="credential-item">
|
||||
<span class="credential-label">Quick Connect Code (Hashed)</span>
|
||||
<div class="credential-value" style="font-size: 0.75rem;">{{ player.quickconnect_code or 'Not set' }}</div>
|
||||
<small style="display: block; margin-top: 0.25rem; color: #6c757d;">⚠️ This is the hashed version for security</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-success" style="width: 100%; display: flex; align-items: center; justify-content: center; gap: 0.5rem;">
|
||||
@@ -109,10 +370,9 @@
|
||||
<form method="POST" style="margin-top: 1rem;">
|
||||
<input type="hidden" name="action" value="assign_playlist">
|
||||
|
||||
<div style="margin-bottom: 1rem;">
|
||||
<label for="playlist_id" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">Select Playlist</label>
|
||||
<select id="playlist_id" name="playlist_id"
|
||||
style="width: 100%; padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px;">
|
||||
<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 }}"
|
||||
@@ -124,8 +384,8 @@
|
||||
</div>
|
||||
|
||||
{% if current_playlist %}
|
||||
<div style="padding: 1rem; background: #d4edda; border-radius: 4px; margin-bottom: 1rem;">
|
||||
<h4 style="margin: 0 0 0.5rem 0; color: #155724;">Currently Assigned:</h4>
|
||||
<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>
|
||||
@@ -134,8 +394,8 @@
|
||||
</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<div style="padding: 1rem; background: #fff3cd; border-radius: 4px; margin-bottom: 1rem;">
|
||||
<p style="margin: 0; color: #856404; display: flex; align-items: center; gap: 0.5rem;">
|
||||
<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>
|
||||
|
||||
@@ -1,10 +1,234 @@
|
||||
{% extends "base.html" %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ player.name }} - Live Preview</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #000;
|
||||
overflow: hidden;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
#player-container {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#media-display {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#media-display.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.loading {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: white;
|
||||
font-size: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.info-overlay {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
left: 20px;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.controls {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
z-index: 1000;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.controls button {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
color: white;
|
||||
padding: 10px 15px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.controls button:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.no-content {
|
||||
color: white;
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="player-container">
|
||||
<div class="loading" id="loading">Loading playlist...</div>
|
||||
<img id="media-display" alt="Content">
|
||||
<video id="video-display" style="display: none; max-width: 100%; max-height: 100%; width: 100%; height: 100%; object-fit: contain;"></video>
|
||||
<div class="no-content" id="no-content" style="display: none;">
|
||||
<p>💭 No content in playlist</p>
|
||||
<p style="font-size: 16px; margin-top: 10px; opacity: 0.7;">Add content to the playlist to preview</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-overlay" id="info-overlay" style="display: none;">
|
||||
<div id="current-item">Item: -</div>
|
||||
<div id="playlist-info">Playlist: {{ playlist|length }} items</div>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button onclick="toggleFullscreen()">🔳 Fullscreen</button>
|
||||
<button onclick="restartPlaylist()">🔄 Restart</button>
|
||||
<button onclick="window.close()">✖️ Close</button>
|
||||
</div>
|
||||
|
||||
{% block title %}Player Fullscreen{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<h2>Player Fullscreen View</h2>
|
||||
<p>Fullscreen player view - placeholder</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
<script>
|
||||
const playlist = {{ playlist|tojson }};
|
||||
let currentIndex = 0;
|
||||
let timer = null;
|
||||
let inactivityTimer = null;
|
||||
|
||||
const imgDisplay = document.getElementById('media-display');
|
||||
const videoDisplay = document.getElementById('video-display');
|
||||
const loading = document.getElementById('loading');
|
||||
const noContent = document.getElementById('no-content');
|
||||
const infoOverlay = document.getElementById('info-overlay');
|
||||
const currentItemDiv = document.getElementById('current-item');
|
||||
const controls = document.querySelector('.controls');
|
||||
|
||||
function playNext() {
|
||||
if (!playlist || playlist.length === 0) {
|
||||
loading.style.display = 'none';
|
||||
noContent.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
const item = playlist[currentIndex];
|
||||
loading.style.display = 'none';
|
||||
infoOverlay.style.display = 'block';
|
||||
|
||||
// Update info
|
||||
currentItemDiv.textContent = `Item ${currentIndex + 1}/${playlist.length}: ${item.filename}`;
|
||||
|
||||
// Hide both displays
|
||||
imgDisplay.style.display = 'none';
|
||||
videoDisplay.style.display = 'none';
|
||||
videoDisplay.pause();
|
||||
|
||||
if (item.type === 'video') {
|
||||
videoDisplay.src = item.url;
|
||||
videoDisplay.style.display = 'block';
|
||||
videoDisplay.play();
|
||||
|
||||
// When video ends, move to next
|
||||
videoDisplay.onended = () => {
|
||||
currentIndex = (currentIndex + 1) % playlist.length;
|
||||
playNext();
|
||||
};
|
||||
} else {
|
||||
// Image or PDF
|
||||
imgDisplay.src = item.url;
|
||||
imgDisplay.style.display = 'block';
|
||||
imgDisplay.classList.add('active');
|
||||
|
||||
// Clear any existing timer
|
||||
if (timer) clearTimeout(timer);
|
||||
|
||||
// Show for specified duration
|
||||
timer = setTimeout(() => {
|
||||
currentIndex = (currentIndex + 1) % playlist.length;
|
||||
playNext();
|
||||
}, (item.duration || 10) * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleFullscreen() {
|
||||
if (!document.fullscreenElement) {
|
||||
document.documentElement.requestFullscreen();
|
||||
} else {
|
||||
document.exitFullscreen();
|
||||
}
|
||||
}
|
||||
|
||||
function restartPlaylist() {
|
||||
currentIndex = 0;
|
||||
if (timer) clearTimeout(timer);
|
||||
videoDisplay.pause();
|
||||
playNext();
|
||||
}
|
||||
|
||||
// Auto-hide controls after 5 seconds of inactivity
|
||||
function resetInactivityTimer() {
|
||||
// Show controls
|
||||
controls.style.opacity = '1';
|
||||
controls.style.pointerEvents = 'auto';
|
||||
|
||||
// Clear existing timer
|
||||
if (inactivityTimer) {
|
||||
clearTimeout(inactivityTimer);
|
||||
}
|
||||
|
||||
// Set new timer to hide controls after 5 seconds
|
||||
inactivityTimer = setTimeout(() => {
|
||||
controls.style.opacity = '0';
|
||||
controls.style.pointerEvents = 'none';
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// Track user activity to show/hide controls
|
||||
document.addEventListener('mousemove', resetInactivityTimer);
|
||||
document.addEventListener('mousedown', resetInactivityTimer);
|
||||
document.addEventListener('keydown', resetInactivityTimer);
|
||||
document.addEventListener('touchstart', resetInactivityTimer);
|
||||
|
||||
// Start playing when page loads
|
||||
window.addEventListener('load', () => {
|
||||
playNext();
|
||||
resetInactivityTimer(); // Start inactivity timer
|
||||
});
|
||||
|
||||
// Handle keyboard shortcuts
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'f' || e.key === 'F') {
|
||||
toggleFullscreen();
|
||||
} else if (e.key === 'r' || e.key === 'R') {
|
||||
restartPlaylist();
|
||||
} else if (e.key === 'Escape' && document.fullscreenElement) {
|
||||
document.exitFullscreen();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -3,6 +3,129 @@
|
||||
{% block title %}Players - DigiServer v2{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<style>
|
||||
body.dark-mode h1 {
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
.players-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.players-table thead tr {
|
||||
background: #f8f9fa;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
body.dark-mode .players-table thead tr {
|
||||
background: #1a202c;
|
||||
}
|
||||
|
||||
.players-table th {
|
||||
padding: 12px;
|
||||
border-bottom: 2px solid #dee2e6;
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
body.dark-mode .players-table th {
|
||||
border-bottom-color: #4a5568;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
.players-table tbody tr {
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
body.dark-mode .players-table tbody tr {
|
||||
border-bottom-color: #4a5568;
|
||||
}
|
||||
|
||||
.players-table tbody tr:hover {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
body.dark-mode .players-table tbody tr:hover {
|
||||
background: #2d3748;
|
||||
}
|
||||
|
||||
.players-table td {
|
||||
padding: 12px;
|
||||
color: #2d3748;
|
||||
}
|
||||
|
||||
body.dark-mode .players-table td {
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
.players-table td strong {
|
||||
color: #2d3748;
|
||||
}
|
||||
|
||||
body.dark-mode .players-table td strong {
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
.players-table code {
|
||||
background: #f8f9fa;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
body.dark-mode .players-table code {
|
||||
background: #1a202c;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 3px 8px;
|
||||
border-radius: 3px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.status-badge.online {
|
||||
background: #28a745;
|
||||
}
|
||||
|
||||
.status-badge.offline {
|
||||
background: #6c757d;
|
||||
}
|
||||
|
||||
.text-muted {
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
body.dark-mode .text-muted {
|
||||
color: #718096;
|
||||
}
|
||||
|
||||
.info-box {
|
||||
background: #d1ecf1;
|
||||
border: 1px solid #bee5eb;
|
||||
color: #0c5460;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
body.dark-mode .info-box {
|
||||
background: #1a365d;
|
||||
border-color: #2c5282;
|
||||
color: #90cdf4;
|
||||
}
|
||||
|
||||
.info-box a {
|
||||
color: #0c5460;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
body.dark-mode .info-box a {
|
||||
color: #90cdf4;
|
||||
}
|
||||
</style>
|
||||
<div class="container">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
|
||||
<h1>Players</h1>
|
||||
@@ -11,49 +134,49 @@
|
||||
|
||||
{% if players %}
|
||||
<div class="card">
|
||||
<table style="width: 100%; border-collapse: collapse;">
|
||||
<table class="players-table">
|
||||
<thead>
|
||||
<tr style="background: #f8f9fa; text-align: left;">
|
||||
<th style="padding: 12px; border-bottom: 2px solid #dee2e6;">Name</th>
|
||||
<th style="padding: 12px; border-bottom: 2px solid #dee2e6;">Hostname</th>
|
||||
<th style="padding: 12px; border-bottom: 2px solid #dee2e6;">Location</th>
|
||||
<th style="padding: 12px; border-bottom: 2px solid #dee2e6;">Orientation</th>
|
||||
<th style="padding: 12px; border-bottom: 2px solid #dee2e6;">Status</th>
|
||||
<th style="padding: 12px; border-bottom: 2px solid #dee2e6;">Last Seen</th>
|
||||
<th style="padding: 12px; border-bottom: 2px solid #dee2e6;">Actions</th>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Hostname</th>
|
||||
<th>Location</th>
|
||||
<th>Orientation</th>
|
||||
<th>Status</th>
|
||||
<th>Last Seen</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for player in players %}
|
||||
<tr style="border-bottom: 1px solid #dee2e6;">
|
||||
<td style="padding: 12px;">
|
||||
<tr>
|
||||
<td>
|
||||
<strong>{{ player.name }}</strong>
|
||||
</td>
|
||||
<td style="padding: 12px;">
|
||||
<code style="background: #f8f9fa; padding: 2px 6px; border-radius: 3px;">{{ player.hostname }}</code>
|
||||
<td>
|
||||
<code>{{ player.hostname }}</code>
|
||||
</td>
|
||||
<td style="padding: 12px;">
|
||||
<td>
|
||||
{{ player.location or '-' }}
|
||||
</td>
|
||||
<td style="padding: 12px;">
|
||||
<td>
|
||||
{{ player.orientation or 'Landscape' }}
|
||||
</td>
|
||||
<td style="padding: 12px;">
|
||||
<td>
|
||||
{% set status = player_statuses.get(player.id, {}) %}
|
||||
{% if status.get('is_online') %}
|
||||
<span style="background: #28a745; color: white; padding: 3px 8px; border-radius: 3px; font-size: 12px;">Online</span>
|
||||
<span class="status-badge online">Online</span>
|
||||
{% else %}
|
||||
<span style="background: #6c757d; color: white; padding: 3px 8px; border-radius: 3px; font-size: 12px;">Offline</span>
|
||||
<span class="status-badge offline">Offline</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td style="padding: 12px;">
|
||||
<td>
|
||||
{% if player.last_heartbeat %}
|
||||
{{ player.last_heartbeat.strftime('%Y-%m-%d %H:%M') }}
|
||||
{% else %}
|
||||
<span style="color: #6c757d;">Never</span>
|
||||
<span class="text-muted">Never</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td style="padding: 12px;">
|
||||
<td>
|
||||
<a href="{{ url_for('players.manage_player', player_id=player.id) }}"
|
||||
class="btn btn-info btn-sm" title="Manage Player">
|
||||
⚙️ Manage
|
||||
@@ -65,8 +188,8 @@
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div style="background: #d1ecf1; border: 1px solid #bee5eb; color: #0c5460; padding: 15px; border-radius: 5px;">
|
||||
ℹ️ No players yet. <a href="{{ url_for('players.add_player') }}" style="color: #0c5460; text-decoration: underline;">Add your first player</a>
|
||||
<div class="info-box">
|
||||
ℹ️ No players yet. <a href="{{ url_for('players.add_player') }}">Add your first player</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user