Add in-app help page with user manual (Romanian)

Adds a /help page served from the dashboard navigation, containing the full
Romanian user manual for operators: dashboard, playlist management, adding
media, removing files from playlists, player management, and the advanced
edited-media view.

Implementation notes
- Manual is pre-rendered to HTML with pandoc and shipped as a static asset
  (app/static/help/_manual_body.html) rather than parsed at runtime. This
  keeps the container free of a Markdown dependency and makes the page render
  identically regardless of what is installed.
- /help is login-protected; the sidebar table of contents is derived from the
  level-2 headings at request time.
- Screenshots are served from app/static/help/screenshots/ (32 images).
- Regenerate after editing the manual:
    documentatie/convert_help_page.sh
    docker compose up -d --build

Also adds
- backup_players_playlists.sh / restore_database.sh for DB backup and restore.
- .gitignore rules so local database backups (real production data), generated
  .docx files and duplicate screenshot copies are never committed.
This commit is contained in:
2026-09-15 16:46:16 +03:00
parent b4c3f65636
commit 7177cdb9ab
45 changed files with 3082 additions and 0 deletions
+44
View File
@@ -89,6 +89,50 @@ def dashboard():
)
@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 (`<h2 id="...">`), which
# is exactly the printed table of contents of the manual. Nested <h3>
# entries would make the sidebar unwieldy, so they are left out.
for anchor, title in re.findall(
r'<h2 id="([^"]+)"[^>]*>(.*?)</h2>', manual_html, re.DOTALL
):
clean = re.sub(r'<[^>]+>', '', title).strip()
if clean:
toc.append({'anchor': anchor, 'title': clean})
except FileNotFoundError:
manual_html = (
'<h1>Manual indisponibil</h1>'
'<p>Fișierul manualului nu a fost găsit. '
'Rulați <code>documentatie/convert_help_page.sh</code> pentru a-l genera.</p>'
)
return render_template('help.html', manual_html=manual_html, toc=toc)
@main_bp.route('/health')
def health():
"""Health check endpoint"""