updated to delete player and edit fields

This commit is contained in:
DigiServer Developer
2025-11-21 22:51:28 +02:00
parent a2281e90e7
commit f4df930d82
5 changed files with 202 additions and 25 deletions

View File

@@ -240,8 +240,8 @@ def get_playlist_by_quickconnect():
'playlist_version': 0
}), 403
# Check if quickconnect matches
if player.quickconnect_code != quickconnect_code:
# Check if quickconnect matches (using bcrypt verification)
if not player.check_quickconnect_code(quickconnect_code):
log_action('warning', f'Invalid quickconnect code for player: {hostname}')
return jsonify({
'error': 'Invalid quickconnect code',
@@ -430,21 +430,26 @@ def receive_player_feedback():
return jsonify({'error': 'No data provided'}), 400
player_name = data.get('player_name')
hostname = data.get('hostname') # Also accept hostname
quickconnect_code = data.get('quickconnect_code')
if not player_name or not quickconnect_code:
return jsonify({'error': 'player_name and quickconnect_code required'}), 400
if (not player_name and not hostname) or not quickconnect_code:
return jsonify({'error': 'player_name/hostname and quickconnect_code required'}), 400
# Find player by name and validate quickconnect
player = Player.query.filter_by(name=player_name).first()
# Find player by hostname first (more reliable), then by name
player = None
if hostname:
player = Player.query.filter_by(hostname=hostname).first()
if not player and player_name:
player = Player.query.filter_by(name=player_name).first()
if not player:
log_action('warning', f'Player feedback from unknown player: {player_name}')
log_action('warning', f'Player feedback from unknown player: {player_name or hostname}')
return jsonify({'error': 'Player not found'}), 404
# Validate quickconnect code
if player.quickconnect_code != quickconnect_code:
log_action('warning', f'Invalid quickconnect in feedback from: {player_name}')
# Validate quickconnect code (using bcrypt verification)
if not player.check_quickconnect_code(quickconnect_code):
log_action('warning', f'Invalid quickconnect in feedback from: {player.name} ({player.hostname})')
return jsonify({'error': 'Invalid quickconnect code'}), 403
# Create feedback record
@@ -466,7 +471,7 @@ def receive_player_feedback():
db.session.commit()
log_action('info', f'Feedback received from {player_name}: {status} - {message}')
log_action('info', f'Feedback received from {player.name} ({player.hostname}): {status} - {message}')
return jsonify({
'success': True,

View File

@@ -228,21 +228,48 @@ def manage_player(player_id: int):
try:
if action == 'update_credentials':
# Update player name, location, orientation
# Update player name, location, orientation, and authentication
name = request.form.get('name', '').strip()
location = request.form.get('location', '').strip()
orientation = request.form.get('orientation', 'Landscape')
hostname = request.form.get('hostname', '').strip()
password = request.form.get('password', '').strip()
quickconnect_code = request.form.get('quickconnect_code', '').strip()
if not name or len(name) < 3:
flash('Player name must be at least 3 characters long.', 'warning')
return redirect(url_for('players.manage_player', player_id=player_id))
if not hostname or len(hostname) < 3:
flash('Hostname must be at least 3 characters long.', 'warning')
return redirect(url_for('players.manage_player', player_id=player_id))
# Check if hostname is taken by another player
if hostname != player.hostname:
existing = Player.query.filter_by(hostname=hostname).first()
if existing:
flash(f'Hostname "{hostname}" is already in use by another player.', 'warning')
return redirect(url_for('players.manage_player', player_id=player_id))
# Update basic info
player.name = name
player.hostname = hostname
player.location = location or None
player.orientation = orientation
# Update password if provided
if password:
player.set_password(password)
log_action('info', f'Password updated for player "{name}"')
# Update quickconnect code if provided
if quickconnect_code:
player.set_quickconnect_code(quickconnect_code)
log_action('info', f'QuickConnect code updated for player "{name}" to: {quickconnect_code}')
db.session.commit()
log_action('info', f'Player "{name}" credentials updated')
log_action('info', f'Player "{name}" (hostname: {hostname}) credentials updated')
flash(f'Player "{name}" updated successfully.', 'success')
elif action == 'assign_playlist':

View File

@@ -71,6 +71,7 @@ class ProductionConfig(Config):
DEBUG = False
TESTING = False
TEMPLATES_AUTO_RELOAD = True # Force template reload
# Database - construct absolute path
_basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))

View File

@@ -223,6 +223,77 @@
</a>
</div>
<!-- Delete Confirmation Modal -->
<div id="deleteModal" style="display: none; position: fixed; z-index: 1000; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.4);">
<div style="background-color: #fefefe; margin: 15% auto; padding: 20px; border: 1px solid #888; border-radius: 8px; width: 90%; max-width: 500px; box-shadow: 0 4px 6px rgba(0,0,0,0.1);">
<h2 style="margin-top: 0; color: #dc3545; display: flex; align-items: center; gap: 0.5rem;">
<span style="font-size: 1.5rem;">⚠️</span>
Confirm Delete
</h2>
<p style="font-size: 1.1rem; margin: 1.5rem 0;">
Are you sure you want to delete player <strong>"{{ player.name }}"</strong>?
</p>
<p style="color: #dc3545; margin: 1rem 0;">
<strong>Warning:</strong> This action cannot be undone. All feedback logs for this player will also be deleted.
</p>
<div style="display: flex; gap: 0.5rem; justify-content: flex-end; margin-top: 2rem;">
<button onclick="closeDeleteModal()" class="btn" style="background: #6c757d;">
Cancel
</button>
<form method="POST" action="{{ url_for('players.delete_player', player_id=player.id) }}" style="margin: 0;">
<button type="submit" class="btn" style="background: #dc3545;">
Yes, Delete Player
</button>
</form>
</div>
</div>
</div>
<style>
body.dark-mode #deleteModal > div {
background-color: #1a202c;
border-color: #4a5568;
color: #e2e8f0;
}
body.dark-mode #deleteModal h2 {
color: #f87171;
}
body.dark-mode #deleteModal p {
color: #e2e8f0;
}
body.dark-mode #deleteModal p strong {
color: #fbbf24;
}
</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;">
@@ -334,23 +405,43 @@
</select>
</div>
<div class="info-box neutral">
<h4 style="margin: 0 0 1rem 0; font-size: 0.95rem; color: #495057;">🔑 Player Credentials</h4>
<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="credential-item">
<span class="credential-label">Hostname</span>
<div class="credential-value">{{ player.hostname }}</div>
<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="credential-item">
<span class="credential-label">Auth Code</span>
<div class="credential-value">{{ player.auth_code }}</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="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 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 class="info-box neutral" style="margin-top: 1rem;">
<h4 style="margin: 0 0 0.5rem 0; font-size: 0.85rem;">📋 Current Credentials (Read-Only)</h4>
<div style="font-size: 0.85rem;">
<strong>Auth Code:</strong> <code style="font-size: 0.8rem;">{{ player.auth_code }}</code><br>
<strong>Quick Connect Hash:</strong> <code style="font-size: 0.7rem;">{{ player.quickconnect_code[:50] if player.quickconnect_code else 'Not set' }}...</code>
</div>
</div>
</div>
@@ -421,6 +512,10 @@
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>

49
check_fix_player.py Normal file
View File

@@ -0,0 +1,49 @@
#!/usr/bin/env python3
"""Check and fix player quickconnect code."""
from app import create_app
from app.models import Player
from app.extensions import db
app = create_app()
with app.app_context():
# Find player by hostname
player = Player.query.filter_by(hostname='tv-terasa').first()
if not player:
print("❌ Player 'tv-terasa' NOT FOUND in database!")
print("\nAll registered players:")
all_players = Player.query.all()
for p in all_players:
print(f" - ID={p.id}, Name='{p.name}', Hostname='{p.hostname}'")
else:
print(f"✅ Player found:")
print(f" ID: {player.id}")
print(f" Name: {player.name}")
print(f" Hostname: {player.hostname}")
print(f" Playlist ID: {player.playlist_id}")
print(f" Status: {player.status}")
print(f" QuickConnect Hash: {player.quickconnect_code[:60] if player.quickconnect_code else 'Not set'}...")
# Test the quickconnect code
test_code = "8887779"
print(f"\n🔐 Testing quickconnect code: '{test_code}'")
if player.check_quickconnect_code(test_code):
print(f"✅ Code '{test_code}' is VALID!")
else:
print(f"❌ Code '{test_code}' is INVALID - Hash doesn't match!")
# Update it
print(f"\n🔧 Updating quickconnect code to: '{test_code}'")
player.set_quickconnect_code(test_code)
db.session.commit()
print("✅ QuickConnect code updated successfully!")
print(f" New hash: {player.quickconnect_code[:60]}...")
# Verify the update
if player.check_quickconnect_code(test_code):
print(f"✅ Verification successful - code '{test_code}' now works!")
else:
print(f"❌ Verification failed - something went wrong!")