updated to delete player and edit fields
This commit is contained in:
@@ -240,8 +240,8 @@ def get_playlist_by_quickconnect():
|
|||||||
'playlist_version': 0
|
'playlist_version': 0
|
||||||
}), 403
|
}), 403
|
||||||
|
|
||||||
# Check if quickconnect matches
|
# Check if quickconnect matches (using bcrypt verification)
|
||||||
if player.quickconnect_code != quickconnect_code:
|
if not player.check_quickconnect_code(quickconnect_code):
|
||||||
log_action('warning', f'Invalid quickconnect code for player: {hostname}')
|
log_action('warning', f'Invalid quickconnect code for player: {hostname}')
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'error': 'Invalid quickconnect code',
|
'error': 'Invalid quickconnect code',
|
||||||
@@ -430,21 +430,26 @@ def receive_player_feedback():
|
|||||||
return jsonify({'error': 'No data provided'}), 400
|
return jsonify({'error': 'No data provided'}), 400
|
||||||
|
|
||||||
player_name = data.get('player_name')
|
player_name = data.get('player_name')
|
||||||
|
hostname = data.get('hostname') # Also accept hostname
|
||||||
quickconnect_code = data.get('quickconnect_code')
|
quickconnect_code = data.get('quickconnect_code')
|
||||||
|
|
||||||
if not player_name or not quickconnect_code:
|
if (not player_name and not hostname) or not quickconnect_code:
|
||||||
return jsonify({'error': 'player_name and quickconnect_code required'}), 400
|
return jsonify({'error': 'player_name/hostname and quickconnect_code required'}), 400
|
||||||
|
|
||||||
# Find player by name and validate quickconnect
|
# Find player by hostname first (more reliable), then by name
|
||||||
player = Player.query.filter_by(name=player_name).first()
|
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:
|
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
|
return jsonify({'error': 'Player not found'}), 404
|
||||||
|
|
||||||
# Validate quickconnect code
|
# Validate quickconnect code (using bcrypt verification)
|
||||||
if player.quickconnect_code != quickconnect_code:
|
if not player.check_quickconnect_code(quickconnect_code):
|
||||||
log_action('warning', f'Invalid quickconnect in feedback from: {player_name}')
|
log_action('warning', f'Invalid quickconnect in feedback from: {player.name} ({player.hostname})')
|
||||||
return jsonify({'error': 'Invalid quickconnect code'}), 403
|
return jsonify({'error': 'Invalid quickconnect code'}), 403
|
||||||
|
|
||||||
# Create feedback record
|
# Create feedback record
|
||||||
@@ -466,7 +471,7 @@ def receive_player_feedback():
|
|||||||
|
|
||||||
db.session.commit()
|
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({
|
return jsonify({
|
||||||
'success': True,
|
'success': True,
|
||||||
|
|||||||
@@ -228,21 +228,48 @@ def manage_player(player_id: int):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
if action == 'update_credentials':
|
if action == 'update_credentials':
|
||||||
# Update player name, location, orientation
|
# Update player name, location, orientation, and authentication
|
||||||
name = request.form.get('name', '').strip()
|
name = request.form.get('name', '').strip()
|
||||||
location = request.form.get('location', '').strip()
|
location = request.form.get('location', '').strip()
|
||||||
orientation = request.form.get('orientation', 'Landscape')
|
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:
|
if not name or len(name) < 3:
|
||||||
flash('Player name must be at least 3 characters long.', 'warning')
|
flash('Player name must be at least 3 characters long.', 'warning')
|
||||||
return redirect(url_for('players.manage_player', player_id=player_id))
|
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.name = name
|
||||||
|
player.hostname = hostname
|
||||||
player.location = location or None
|
player.location = location or None
|
||||||
player.orientation = orientation
|
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()
|
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')
|
flash(f'Player "{name}" updated successfully.', 'success')
|
||||||
|
|
||||||
elif action == 'assign_playlist':
|
elif action == 'assign_playlist':
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ class ProductionConfig(Config):
|
|||||||
|
|
||||||
DEBUG = False
|
DEBUG = False
|
||||||
TESTING = False
|
TESTING = False
|
||||||
|
TEMPLATES_AUTO_RELOAD = True # Force template reload
|
||||||
|
|
||||||
# Database - construct absolute path
|
# Database - construct absolute path
|
||||||
_basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
|
_basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
|
||||||
|
|||||||
@@ -223,6 +223,77 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</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 -->
|
<!-- Player Status Overview -->
|
||||||
<div class="card status-card {% if player.status == 'online' %}online{% elif player.status == 'offline' %}offline{% else %}other{% 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;">
|
<h3 style="display: flex; align-items: center; gap: 0.5rem;">
|
||||||
@@ -334,23 +405,43 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="info-box neutral">
|
<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; color: #495057;">🔑 Player Credentials</h4>
|
<h4 style="margin: 0 0 1rem 0; font-size: 0.95rem;">🔑 Authentication Settings</h4>
|
||||||
|
|
||||||
<div class="credential-item">
|
<div class="form-group">
|
||||||
<span class="credential-label">Hostname</span>
|
<label for="hostname">Hostname *</label>
|
||||||
<div class="credential-value">{{ player.hostname }}</div>
|
<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>
|
||||||
|
|
||||||
<div class="credential-item">
|
<div class="form-group">
|
||||||
<span class="credential-label">Auth Code</span>
|
<label for="password">New Password (leave blank to keep current)</label>
|
||||||
<div class="credential-value">{{ player.auth_code }}</div>
|
<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>
|
||||||
|
|
||||||
<div class="credential-item">
|
<div class="form-group">
|
||||||
<span class="credential-label">Quick Connect Code (Hashed)</span>
|
<label for="quickconnect_code">Quick Connect Code</label>
|
||||||
<div class="credential-value" style="font-size: 0.75rem;">{{ player.quickconnect_code or 'Not set' }}</div>
|
<input type="text" id="quickconnect_code" name="quickconnect_code" class="form-control"
|
||||||
<small style="display: block; margin-top: 0.25rem; color: #6c757d;">⚠️ This is the hashed version for security</small>
|
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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -421,6 +512,10 @@
|
|||||||
Edit Current Playlist
|
Edit Current Playlist
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% 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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
49
check_fix_player.py
Normal file
49
check_fix_player.py
Normal 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!")
|
||||||
Reference in New Issue
Block a user