Files
digiserver-v2/app/utils/group_player_management.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

210 lines
5.9 KiB
Python
Executable File

"""Group and player management utilities."""
from typing import Dict, List, Optional
from datetime import datetime, timedelta
from app.extensions import db
from app.models import Player, Group, PlayerFeedback
from app.utils.logger import log_action
def get_player_status_info(player_id: int) -> Dict:
"""Get comprehensive status information for a player.
Args:
player_id: Player ID to query
Returns:
Dictionary with status information
"""
player = Player.query.get(player_id)
if not player:
return {
'online': False,
'status': 'unknown',
'last_seen': None,
'latest_feedback': None
}
# Check if player is online (seen in last 5 minutes)
is_online = False
if player.last_seen:
delta = datetime.utcnow() - player.last_seen
is_online = delta.total_seconds() < 300
# Get latest feedback
latest_feedback = PlayerFeedback.query.filter_by(player_id=player_id)\
.order_by(PlayerFeedback.timestamp.desc())\
.first()
return {
'online': is_online,
'status': player.status,
'last_seen': player.last_seen.isoformat() if player.last_seen else None,
'last_seen_ago': _format_time_ago(player.last_seen) if player.last_seen else 'Never',
'latest_feedback': {
'status': latest_feedback.status,
'message': latest_feedback.message,
'error': latest_feedback.error,
'timestamp': latest_feedback.timestamp.isoformat()
} if latest_feedback else None
}
def get_group_statistics(group_id: int) -> Dict:
"""Get statistics for a group.
Args:
group_id: Group ID to query
Returns:
Dictionary with group statistics
"""
group = Group.query.get(group_id)
if not group:
return {
'total_players': 0,
'online_players': 0,
'total_content': 0,
'error_count': 0
}
total_players = group.player_count
total_content = group.content_count
# Count online players
online_players = 0
error_count = 0
five_min_ago = datetime.utcnow() - timedelta(minutes=5)
for player in group.players:
if player.last_seen and player.last_seen >= five_min_ago:
online_players += 1
if player.status == 'error':
error_count += 1
return {
'group_id': group_id,
'group_name': group.name,
'total_players': total_players,
'online_players': online_players,
'offline_players': total_players - online_players,
'total_content': total_content,
'error_count': error_count
}
def assign_player_to_group(player_id: int, group_id: Optional[int]) -> bool:
"""Assign a player to a group or unassign if group_id is None.
Args:
player_id: Player ID to assign
group_id: Group ID to assign to, or None to unassign
Returns:
True if successful, False otherwise
"""
try:
player = Player.query.get(player_id)
if not player:
log_action('error', f'Player {player_id} not found')
return False
old_group_id = player.group_id
player.group_id = group_id
db.session.commit()
if group_id:
group = Group.query.get(group_id)
log_action('info', f'Player "{player.name}" assigned to group "{group.name}"')
else:
log_action('info', f'Player "{player.name}" unassigned from group')
return True
except Exception as e:
db.session.rollback()
log_action('error', f'Error assigning player to group: {str(e)}')
return False
def bulk_assign_players_to_group(player_ids: List[int], group_id: Optional[int]) -> int:
"""Assign multiple players to a group.
Args:
player_ids: List of player IDs to assign
group_id: Group ID to assign to, or None to unassign
Returns:
Number of players successfully assigned
"""
count = 0
try:
for player_id in player_ids:
player = Player.query.get(player_id)
if player:
player.group_id = group_id
count += 1
db.session.commit()
if group_id:
group = Group.query.get(group_id)
log_action('info', f'Bulk assigned {count} players to group "{group.name}"')
else:
log_action('info', f'Bulk unassigned {count} players from groups')
return count
except Exception as e:
db.session.rollback()
log_action('error', f'Error bulk assigning players: {str(e)}')
return 0
def get_online_players_count() -> int:
"""Get count of online players (seen in last 5 minutes).
Returns:
Number of online players
"""
five_min_ago = datetime.utcnow() - timedelta(minutes=5)
return Player.query.filter(Player.last_seen >= five_min_ago).count()
def get_players_by_status(status: str) -> List[Player]:
"""Get all players with a specific status.
Args:
status: Status to filter by
Returns:
List of Player instances
"""
return Player.query.filter_by(status=status).all()
def _format_time_ago(dt: datetime) -> str:
"""Format datetime as 'time ago' string.
Args:
dt: Datetime to format
Returns:
Formatted string like '5 minutes ago'
"""
delta = datetime.utcnow() - dt
seconds = delta.total_seconds()
if seconds < 60:
return f'{int(seconds)} seconds ago'
elif seconds < 3600:
return f'{int(seconds / 60)} minutes ago'
elif seconds < 86400:
return f'{int(seconds / 3600)} hours ago'
else:
return f'{int(seconds / 86400)} days ago'