""" Main Blueprint - Dashboard and Home Routes """ from flask import Blueprint, render_template, redirect, url_for from flask_login import login_required, current_user from app.extensions import db, cache from app.models.player import Player from app.models.playlist import Playlist from app.models.content import Content from app.utils.logger import get_recent_logs import os main_bp = Blueprint('main', __name__) @main_bp.route('/') @login_required @cache.cached(timeout=60, unless=lambda: current_user.role != 'viewer') def dashboard(): """Main dashboard page""" # Get statistics total_players = Player.query.count() total_playlists = Playlist.query.count() total_content = Content.query.count() # Calculate storage usage upload_folder = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'static', 'uploads') storage_mb = 0 if os.path.exists(upload_folder): for filename in os.listdir(upload_folder): filepath = os.path.join(upload_folder, filename) if os.path.isfile(filepath): storage_mb += os.path.getsize(filepath) storage_mb = round(storage_mb / (1024 * 1024), 2) # Convert to MB # Recent Activity. # # Player feedback ("Feedback received from ...") is a ~15s heartbeat: it is # ~84% of all log rows, and 18 of the newest 20. Showing it unfiltered # buries the events an operator actually cares about (uploads, logins, # HTTPS changes, deployments), so the two kinds are queried separately and # the card defaults to real activity, with heartbeats available on demand. # # The split is done in SQL rather than by filtering one wide window in # Python: with a fleet of players the heartbeat rate is high enough that a # fixed window would return mostly noise and only a handful of real events. HEARTBEAT_PREFIX = 'Feedback received from' activity_logs = get_recent_logs(25, exclude_prefix=HEARTBEAT_PREFIX) heartbeat_logs = get_recent_logs(25, include_prefix=HEARTBEAT_PREFIX) # Per-playlist overview for the dashboard: which playlist holds what, and # which players consume it. This answers "where should I add media / which # playlist needs editing?" without opening each playlist. # # Players are fetched in a single query and grouped in Python rather than # relying on Playlist.players per row, to avoid one query per playlist. playlists = Playlist.query.order_by(Playlist.name).all() players_by_playlist = {} for player in Player.query.order_by(Player.name).all(): if player.playlist_id is not None: players_by_playlist.setdefault(player.playlist_id, []).append(player) playlist_overview = [] for playlist in playlists: assigned = players_by_playlist.get(playlist.id, []) playlist_overview.append({ 'playlist': playlist, 'content_count': playlist.contents.count(), 'total_duration': playlist.total_duration, 'players': assigned, 'player_count': len(assigned), 'online_count': sum(1 for p in assigned if p.is_online), }) # Players with no playlist produce no content on screen, so surface them. unassigned_players = Player.query.filter(Player.playlist_id.is_(None))\ .order_by(Player.name).all() return render_template( 'dashboard.html', total_players=total_players, total_playlists=total_playlists, total_content=total_content, storage_mb=storage_mb, recent_logs=activity_logs, heartbeat_logs=heartbeat_logs, playlist_overview=playlist_overview, unassigned_players=unassigned_players ) @main_bp.route('/help') @login_required def help_page(): """User manual / help page. The manual lives at ``app/static/help/_manual_body.html`` — a pre-rendered HTML fragment generated from ``documentatie/Manual-Utilizare-DigiServer.md`` with ``pandoc`` (see ``documentatie/convert_help_page.sh``). It is shipped as a static asset rather than parsed at runtime on purpose: it keeps the runtime free of a Markdown dependency and means the help page renders identically regardless of what is installed in the container. """ import os import re base_dir = os.path.dirname(os.path.dirname(__file__)) # -> app/ manual_path = os.path.join(base_dir, 'static', 'help', '_manual_body.html') manual_html = '' toc = [] try: with open(manual_path, 'r', encoding='utf-8') as f: manual_html = f.read() # Build the sidebar from the level-2 headings (`
Fișierul manualului nu a fost găsit. '
'Rulați documentatie/convert_help_page.sh pentru a-l genera.