Files
digiserver-v2/app/models/player.py
Quality App Developer 48f1bfbcad Add HTTPS configuration management system
- Add HTTPSConfig model for managing HTTPS settings
- Add admin routes for HTTPS configuration management
- Add beautiful admin template for HTTPS configuration
- Add database migration for https_config table
- Add CLI utility for HTTPS management
- Add setup script for automated configuration
- Add Caddy configuration generator and manager
- Add comprehensive documentation (3 guides)
- Add HTTPS Configuration card to admin dashboard
- Implement input validation and security features
- Add admin-only access control with audit trail
- Add real-time configuration preview
- Integrate with existing Caddy reverse proxy

Features:
- Enable/disable HTTPS from web interface
- Configure domain, hostname, IP address, port
- Automatic SSL certificate management via Let's Encrypt
- Real-time Caddyfile generation and reload
- Full audit trail with admin username and timestamps
- Support for HTTPS and HTTP fallback access points
- Beautiful, mobile-responsive UI

Modified files:
- app/models/__init__.py (added HTTPSConfig import)
- app/blueprints/admin.py (added HTTPS routes)
- app/templates/admin/admin.html (added HTTPS card)
- docker-compose.yml (added Caddyfile mount and admin port)

New files:
- app/models/https_config.py
- app/blueprints/https_config.html
- app/utils/caddy_manager.py
- https_manager.py
- setup_https.sh
- migrations/add_https_config_table.py
- migrations/add_email_to_https_config.py
- HTTPS_STATUS.txt
- Documentation files (3 markdown guides)
2026-01-14 12:02:49 +02:00

139 lines
5.2 KiB
Python
Executable File

"""Player model for digital signage players."""
from datetime import datetime
from typing import Optional, List
from app.extensions import db
class Player(db.Model):
"""Player model representing a digital signage device.
Attributes:
id: Primary key
name: Display name for the player
hostname: Unique hostname/identifier for the player
location: Physical location description
auth_code: Authentication code for API access (legacy)
password_hash: Hashed password for player authentication
quickconnect_code: Hashed quick connect code for easy pairing
orientation: Display orientation (Landscape/Portrait)
status: Current player status (online, offline, error)
last_seen: Last activity timestamp
playlist_version: Version number for playlist synchronization
created_at: Player creation timestamp
"""
__tablename__ = 'player'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255), nullable=False)
hostname = db.Column(db.String(255), unique=True, nullable=False, index=True)
location = db.Column(db.String(255), nullable=True)
auth_code = db.Column(db.String(255), unique=True, nullable=False, index=True)
password_hash = db.Column(db.String(255), nullable=False)
quickconnect_code = db.Column(db.String(255), nullable=True)
orientation = db.Column(db.String(16), default='Landscape', nullable=False)
status = db.Column(db.String(50), default='offline', index=True)
last_seen = db.Column(db.DateTime, nullable=True, index=True)
last_heartbeat = db.Column(db.DateTime, nullable=True, index=True)
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
# Playlist assignment
playlist_id = db.Column(db.Integer, db.ForeignKey('playlist.id', ondelete='SET NULL'),
nullable=True, index=True)
# Relationships
playlist = db.relationship('Playlist', back_populates='players')
feedback = db.relationship('PlayerFeedback', back_populates='player',
cascade='all, delete-orphan', lazy='dynamic')
def __repr__(self) -> str:
"""String representation of Player."""
return f'<Player {self.name} (ID={self.id}, Status={self.status})>'
@property
def is_online(self) -> bool:
"""Check if player is online (seen in last 5 minutes)."""
if not self.last_seen:
return False
delta = datetime.utcnow() - self.last_seen
return delta.total_seconds() < 300 # 5 minutes
def update_status(self, status: str) -> None:
"""Update player status and last seen timestamp.
Args:
status: New status value
"""
self.status = status
self.last_seen = datetime.utcnow()
def set_password(self, password: str) -> None:
"""Set player password with bcrypt hashing.
Args:
password: Plain text password
"""
from app.extensions import bcrypt
self.password_hash = bcrypt.generate_password_hash(password).decode('utf-8')
def check_password(self, password: str) -> bool:
"""Verify player password.
Args:
password: Plain text password to check
Returns:
True if password matches, False otherwise
"""
from app.extensions import bcrypt
return bcrypt.check_password_hash(self.password_hash, password)
def set_quickconnect_code(self, code: str) -> None:
"""Set quick connect code with bcrypt hashing.
Args:
code: Plain text quick connect code
"""
from app.extensions import bcrypt
self.quickconnect_code = bcrypt.generate_password_hash(code).decode('utf-8')
def check_quickconnect_code(self, code: str) -> bool:
"""Verify quick connect code.
Args:
code: Plain text code to check
Returns:
True if code matches, False otherwise
"""
if not self.quickconnect_code:
return False
from app.extensions import bcrypt
return bcrypt.check_password_hash(self.quickconnect_code, code)
@staticmethod
def authenticate(hostname: str, password: str = None, quickconnect_code: str = None) -> Optional['Player']:
"""Authenticate a player by hostname and password or quickconnect code.
Args:
hostname: Player hostname
password: Player password (optional if using quickconnect)
quickconnect_code: Quick connect code (optional if using password)
Returns:
Player instance if authentication successful, None otherwise
"""
player = Player.query.filter_by(hostname=hostname).first()
if not player:
return None
# Try password authentication first
if password and player.check_password(password):
return player
# Try quickconnect code authentication
if quickconnect_code and player.check_quickconnect_code(quickconnect_code):
return player
return None