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.
@@ -76,6 +76,27 @@ build/
|
|||||||
#data
|
#data
|
||||||
data/
|
data/
|
||||||
|
|
||||||
|
# Local database backups / restore points.
|
||||||
|
# These contain REAL production data (users, password hashes, player feedback).
|
||||||
|
# They must never leave the host — keep them on disk only.
|
||||||
|
backups/
|
||||||
|
restore-points/
|
||||||
|
|
||||||
|
# Generated documents (regenerate with documentatie/convert_manual.sh)
|
||||||
|
documentatie/*.docx
|
||||||
|
|
||||||
|
# Screenshots are shipped with the app under app/static/help/screenshots/.
|
||||||
|
# The copy in documentatie/ is the editing source; ignore it to avoid committing
|
||||||
|
# ~4 MB twice.
|
||||||
|
documentatie/screenshots/
|
||||||
|
|
||||||
|
# Local notes / scratch files
|
||||||
|
inportant info .txt
|
||||||
|
important info.txt
|
||||||
|
|
||||||
|
# Legacy pre-sanitization archive (also covered as docs/old_code_documentation/)
|
||||||
|
old_code_documentation/
|
||||||
|
|
||||||
# Local archive / restore-point snapshots.
|
# Local archive / restore-point snapshots.
|
||||||
# Kept on disk for reference (see docs/SANITIZATION-REVIEW.md) but deliberately
|
# Kept on disk for reference (see docs/SANITIZATION-REVIEW.md) but deliberately
|
||||||
# NOT tracked: docs/legacy code/ is a full pre-sanitization repo snapshot and
|
# NOT tracked: docs/legacy code/ is a full pre-sanitization repo snapshot and
|
||||||
|
|||||||
@@ -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')
|
@main_bp.route('/health')
|
||||||
def health():
|
def health():
|
||||||
"""Health check endpoint"""
|
"""Health check endpoint"""
|
||||||
|
|||||||
|
After Width: | Height: | Size: 177 KiB |
|
After Width: | Height: | Size: 138 KiB |
|
After Width: | Height: | Size: 130 KiB |
|
After Width: | Height: | Size: 310 KiB |
|
After Width: | Height: | Size: 310 KiB |
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 111 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 134 KiB |
|
After Width: | Height: | Size: 148 KiB |
|
After Width: | Height: | Size: 191 KiB |
|
After Width: | Height: | Size: 161 KiB |
|
After Width: | Height: | Size: 191 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 117 KiB |
|
After Width: | Height: | Size: 151 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 146 KiB |
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 116 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 130 KiB |
|
After Width: | Height: | Size: 132 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 103 KiB |
|
After Width: | Height: | Size: 115 KiB |
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<circle cx="12" cy="12" r="10"></circle>
|
||||||
|
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path>
|
||||||
|
<line x1="12" y1="17" x2="12.01" y2="17"></line>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 340 B |
@@ -393,6 +393,10 @@
|
|||||||
<img src="{{ url_for('static', filename='icons/playlist.svg') }}" alt="">
|
<img src="{{ url_for('static', filename='icons/playlist.svg') }}" alt="">
|
||||||
Playlists
|
Playlists
|
||||||
</a>
|
</a>
|
||||||
|
<a href="{{ url_for('main.help_page') }}">
|
||||||
|
<img src="{{ url_for('static', filename='icons/help.svg') }}" alt="" onerror="this.style.display='none';">
|
||||||
|
Ajutor
|
||||||
|
</a>
|
||||||
<a href="{{ url_for('admin.admin_panel') }}">Admin</a>
|
<a href="{{ url_for('admin.admin_panel') }}">Admin</a>
|
||||||
<a href="{{ url_for('auth.logout') }}">Logout ({{ current_user.username }})</a>
|
<a href="{{ url_for('auth.logout') }}">Logout ({{ current_user.username }})</a>
|
||||||
<button class="dark-mode-toggle" onclick="toggleDarkMode()" title="Toggle Dark Mode">
|
<button class="dark-mode-toggle" onclick="toggleDarkMode()" title="Toggle Dark Mode">
|
||||||
|
|||||||
@@ -0,0 +1,276 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Ajutor – Manual de Utilizare{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<style>
|
||||||
|
/* ---- Help page: local layout & typography ---- */
|
||||||
|
.help-wrapper {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 280px 1fr;
|
||||||
|
gap: 2rem;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sidebar cu cuprins */
|
||||||
|
.help-toc {
|
||||||
|
position: sticky;
|
||||||
|
top: 20px;
|
||||||
|
max-height: calc(100vh - 40px);
|
||||||
|
overflow-y: auto;
|
||||||
|
background: var(--card-bg);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 1.25rem;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
.help-toc h3 {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
.help-toc ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.help-toc li { margin-bottom: 0.15rem; }
|
||||||
|
.help-toc a {
|
||||||
|
display: block;
|
||||||
|
padding: 0.4rem 0.6rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: var(--text-color);
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.35;
|
||||||
|
transition: all 0.15s;
|
||||||
|
}
|
||||||
|
.help-toc a:hover {
|
||||||
|
background: var(--bg-color);
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
.help-toc a.active {
|
||||||
|
background: var(--primary-color);
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Continutul manualului */
|
||||||
|
.help-content {
|
||||||
|
background: var(--card-bg);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 2rem 2.5rem;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
line-height: 1.7;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
.help-content h1 {
|
||||||
|
font-size: 1.85rem;
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
padding-bottom: 0.75rem;
|
||||||
|
border-bottom: 3px solid var(--primary-color);
|
||||||
|
}
|
||||||
|
.help-content h2 {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
margin: 2.25rem 0 0.85rem;
|
||||||
|
padding-top: 1rem;
|
||||||
|
color: var(--primary-color);
|
||||||
|
scroll-margin-top: 20px;
|
||||||
|
}
|
||||||
|
.help-content h3 {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
margin: 1.5rem 0 0.6rem;
|
||||||
|
scroll-margin-top: 20px;
|
||||||
|
}
|
||||||
|
.help-content h4 {
|
||||||
|
font-size: 1rem;
|
||||||
|
margin: 1.25rem 0 0.5rem;
|
||||||
|
}
|
||||||
|
.help-content p { margin: 0.7rem 0; }
|
||||||
|
.help-content ul, .help-content ol { margin: 0.7rem 0 0.7rem 1.5rem; }
|
||||||
|
.help-content li { margin-bottom: 0.35rem; }
|
||||||
|
.help-content hr {
|
||||||
|
border: none;
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
margin: 2rem 0;
|
||||||
|
}
|
||||||
|
.help-content a { color: var(--primary-color); }
|
||||||
|
.help-content code {
|
||||||
|
background: var(--bg-color);
|
||||||
|
padding: 0.15rem 0.4rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.875em;
|
||||||
|
font-family: 'SFMono-Regular', Consolas, monospace;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
.help-content pre {
|
||||||
|
background: var(--bg-color);
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow-x: auto;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
.help-content pre code {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.help-content img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
margin: 0.75rem 0;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.help-content table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 1rem 0;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
.help-content th, .help-content td {
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
padding: 0.6rem 0.75rem;
|
||||||
|
text-align: left;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
.help-content th {
|
||||||
|
background: var(--bg-color);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.help-content tr:nth-child(even) td {
|
||||||
|
background: color-mix(in srgb, var(--bg-color) 50%, transparent);
|
||||||
|
}
|
||||||
|
/* Citat / atentionari */
|
||||||
|
.help-content blockquote {
|
||||||
|
border-left: 4px solid var(--primary-color);
|
||||||
|
background: var(--bg-color);
|
||||||
|
margin: 1rem 0;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border-radius: 0 8px 8px 0;
|
||||||
|
}
|
||||||
|
.help-content blockquote p { margin: 0.3rem 0; }
|
||||||
|
/* Textul italics al figurilor */
|
||||||
|
.help-content em { color: var(--text-secondary); font-size: 0.9em; }
|
||||||
|
|
||||||
|
/* Back-to-top */
|
||||||
|
.help-top-btn {
|
||||||
|
position: fixed;
|
||||||
|
right: 24px;
|
||||||
|
bottom: 24px;
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--primary-color);
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: transform 0.2s;
|
||||||
|
z-index: 500;
|
||||||
|
}
|
||||||
|
.help-top-btn:hover { transform: translateY(-3px); }
|
||||||
|
.help-top-btn.visible { display: flex; }
|
||||||
|
|
||||||
|
/* Cauta */
|
||||||
|
.help-search {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--bg-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
.help-search:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.help-wrapper { grid-template-columns: 1fr; }
|
||||||
|
.help-toc { position: static; max-height: none; }
|
||||||
|
.help-content { padding: 1.25rem; }
|
||||||
|
.help-content h1 { font-size: 1.5rem; }
|
||||||
|
.help-content h2 { font-size: 1.2rem; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="help-wrapper">
|
||||||
|
|
||||||
|
<!-- Cuprins / navigare laterala -->
|
||||||
|
<aside class="help-toc">
|
||||||
|
<h3>Cuprins</h3>
|
||||||
|
<input type="text" id="tocSearch" class="help-search"
|
||||||
|
placeholder="Caută în cuprins..." autocomplete="off">
|
||||||
|
<ul id="tocList">
|
||||||
|
{% for item in toc %}
|
||||||
|
<li>
|
||||||
|
<a href="#{{ item.anchor }}" data-anchor="{{ item.anchor }}">
|
||||||
|
{{ item.title }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<!-- Continutul manualului -->
|
||||||
|
<main class="help-content" id="helpContent">
|
||||||
|
{{ manual_html | safe }}
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="help-top-btn" id="topBtn" title="Sus" onclick="window.scrollTo({top:0,behavior:'smooth'})">↑</button>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
// Cuprins: evidentiaza sectiunea curenta la scroll
|
||||||
|
var links = Array.prototype.slice.call(document.querySelectorAll('#tocList a'));
|
||||||
|
var sections = links.map(function (a) {
|
||||||
|
return document.getElementById(a.dataset.anchor);
|
||||||
|
}).filter(Boolean);
|
||||||
|
|
||||||
|
function onScroll() {
|
||||||
|
var btn = document.getElementById('topBtn');
|
||||||
|
if (window.scrollY > 400) { btn.classList.add('visible'); }
|
||||||
|
else { btn.classList.remove('visible'); }
|
||||||
|
|
||||||
|
var pos = window.scrollY + 120;
|
||||||
|
var current = null;
|
||||||
|
for (var i = 0; i < sections.length; i++) {
|
||||||
|
if (sections[i].offsetTop <= pos) { current = sections[i].id; }
|
||||||
|
}
|
||||||
|
links.forEach(function (a) {
|
||||||
|
a.classList.toggle('active', a.dataset.anchor === current);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
window.addEventListener('scroll', onScroll, { passive: true });
|
||||||
|
onScroll();
|
||||||
|
|
||||||
|
// Filtrare in cuprins
|
||||||
|
var search = document.getElementById('tocSearch');
|
||||||
|
search.addEventListener('input', function () {
|
||||||
|
var q = this.value.toLowerCase().trim();
|
||||||
|
links.forEach(function (a) {
|
||||||
|
var li = a.parentElement;
|
||||||
|
li.style.display = (!q || a.textContent.toLowerCase().indexOf(q) !== -1) ? '' : 'none';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,240 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ============================================================================
|
||||||
|
# DigiServer - Database Backup (restore-ready for Docker image / host upgrades)
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Produces a timestamped, self-contained bundle under ./backups/<timestamp>/:
|
||||||
|
#
|
||||||
|
# dashboard.db.gz - CONSISTENT full snapshot of the SQLite database
|
||||||
|
# (player, playlist, playlist_content,
|
||||||
|
# player_feedback and all other tables)
|
||||||
|
# players_playlists.sql.gz - logical dump of player/playlist tables
|
||||||
|
# (secondary safety net; human-readable)
|
||||||
|
# SHA256SUMS - integrity check
|
||||||
|
# MANIFEST.txt - row counts, sizes, checksums, app version
|
||||||
|
# RESTORE.md - copy-paste restore instructions for this bundle
|
||||||
|
#
|
||||||
|
# The bundle is designed so that after a `docker compose build` + host upgrade
|
||||||
|
# you can run ./restore_database.sh <timestamp> and be back exactly as before.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./backup_players_playlists.sh # full restore-ready backup
|
||||||
|
# ./backup_players_playlists.sh --logical-only # SQL dump only (no 2.2GB copy)
|
||||||
|
# ============================================================================
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
COMPOSE="docker compose -f $PROJECT_DIR/docker-compose.yml"
|
||||||
|
CONTAINER="digiserver-v2"
|
||||||
|
DB_HOST="$PROJECT_DIR/data/instance/dashboard.db"
|
||||||
|
DB_CTR="/app/instance/dashboard.db"
|
||||||
|
SNAP_CTR="/app/instance/__snapshot.db"
|
||||||
|
BACKUP_ROOT="$PROJECT_DIR/backups"
|
||||||
|
TS="$(date +%Y%m%d_%H%M%S)"
|
||||||
|
DEST="$BACKUP_ROOT/$TS"
|
||||||
|
|
||||||
|
ALL_DUMP_TABLES=(player playlist playlist_content player_user player_edit content group group_content)
|
||||||
|
|
||||||
|
LOGICAL_ONLY=0
|
||||||
|
[ "${1:-}" = "--logical-only" ] && LOGICAL_ONLY=1
|
||||||
|
|
||||||
|
mkdir -p "$DEST"
|
||||||
|
echo "============================================================"
|
||||||
|
echo " DigiServer restore-ready backup"
|
||||||
|
echo " Target: $DEST"
|
||||||
|
echo "============================================================"
|
||||||
|
|
||||||
|
if [ ! -f "$DB_HOST" ]; then
|
||||||
|
echo "ERROR: database not found at $DB_HOST"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 1. Consistent snapshot via SQLite online backup API (safe while app runs)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
if [ "$LOGICAL_ONLY" -eq 0 ]; then
|
||||||
|
echo
|
||||||
|
echo "[1/5] Creating consistent DB snapshot (online backup API)..."
|
||||||
|
$COMPOSE exec -T digiserver-app python - <<'PYEOF'
|
||||||
|
import sqlite3
|
||||||
|
src = sqlite3.connect('/app/instance/dashboard.db')
|
||||||
|
dst = sqlite3.connect('/app/instance/__snapshot.db')
|
||||||
|
with dst:
|
||||||
|
src.backup(dst)
|
||||||
|
dst.close(); src.close()
|
||||||
|
print(" snapshot created")
|
||||||
|
PYEOF
|
||||||
|
|
||||||
|
docker cp "$CONTAINER:$SNAP_CTR" "$DEST/dashboard.db"
|
||||||
|
$COMPOSE exec -T digiserver-app rm -f "$SNAP_CTR"
|
||||||
|
echo " -> size: $(du -h "$DEST/dashboard.db" | cut -f1)"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "[2/5] Compressing snapshot..."
|
||||||
|
gzip -f "$DEST/dashboard.db"
|
||||||
|
echo " -> $(ls -lh "$DEST/dashboard.db.gz" | awk '{print $5}')"
|
||||||
|
else
|
||||||
|
echo
|
||||||
|
echo "[1/5] Skipping full snapshot (--logical-only)"
|
||||||
|
echo "[2/5] Skipping compression"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 3. Logical SQL dump (secondary safety net)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo
|
||||||
|
echo "[3/5] Creating logical SQL dump of player/playlist tables..."
|
||||||
|
TABLE_LIST=$(printf '"%s",' "${ALL_DUMP_TABLES[@]}")
|
||||||
|
TABLE_LIST="[${TABLE_LIST%,}]"
|
||||||
|
|
||||||
|
$COMPOSE exec -T digiserver-app python - "$TABLE_LIST" > "$DEST/players_playlists.sql" <<'PYEOF'
|
||||||
|
import sqlite3, sys
|
||||||
|
tables = eval(sys.argv[1])
|
||||||
|
con = sqlite3.connect('/app/instance/dashboard.db')
|
||||||
|
o = sys.stdout
|
||||||
|
o.write("-- DigiServer logical backup: players & playlists\n")
|
||||||
|
o.write("-- Tables: %s\n\n" % ", ".join(tables))
|
||||||
|
o.write("PRAGMA foreign_keys=OFF;\nBEGIN TRANSACTION;\n\n")
|
||||||
|
for t in tables:
|
||||||
|
row = con.execute("SELECT sql FROM sqlite_master WHERE type='table' AND name=?", (t,)).fetchone()
|
||||||
|
if not row or not row[0]:
|
||||||
|
continue
|
||||||
|
o.write("DROP TABLE IF EXISTS \"%s\";\n" % t)
|
||||||
|
o.write(row[0].rstrip() + ";\n")
|
||||||
|
cur = con.execute('SELECT * FROM "%s"' % t)
|
||||||
|
cols = [d[0] for d in cur.description]
|
||||||
|
collist = ",".join('"%s"' % c for c in cols)
|
||||||
|
batch = []
|
||||||
|
def flush(b):
|
||||||
|
if b:
|
||||||
|
o.write("INSERT INTO \"%s\" (%s) VALUES\n%s;\n" % (t, collist, ",\n".join(b)))
|
||||||
|
for rec in cur:
|
||||||
|
vals = []
|
||||||
|
for v in rec:
|
||||||
|
if v is None: vals.append("NULL")
|
||||||
|
elif isinstance(v, (int, float)): vals.append(str(v))
|
||||||
|
elif isinstance(v, bytes): vals.append("X'%s'" % v.hex())
|
||||||
|
else: vals.append("'" + str(v).replace("'", "''") + "'")
|
||||||
|
batch.append("(%s)" % ",".join(vals))
|
||||||
|
if len(batch) >= 500:
|
||||||
|
flush(batch); batch = []
|
||||||
|
flush(batch)
|
||||||
|
o.write("\n")
|
||||||
|
o.write("COMMIT;\n")
|
||||||
|
PYEOF
|
||||||
|
|
||||||
|
gzip -f "$DEST/players_playlists.sql"
|
||||||
|
echo " -> $(ls -lh "$DEST/players_playlists.sql.gz" | awk '{print $5}')"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 4. Checksums
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo
|
||||||
|
echo "[4/5] Writing checksums..."
|
||||||
|
( cd "$DEST" && sha256sum *.gz > SHA256SUMS )
|
||||||
|
sed 's/^/ /' "$DEST/SHA256SUMS"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 5. Manifest + restore instructions
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo
|
||||||
|
echo "[5/5] Writing MANIFEST.txt and RESTORE.md..."
|
||||||
|
{
|
||||||
|
echo "DigiServer backup manifest"
|
||||||
|
echo "=========================="
|
||||||
|
echo "Created: $(date -Iseconds)"
|
||||||
|
echo "Host: $(hostname)"
|
||||||
|
echo "Backup ID: $TS"
|
||||||
|
echo "Source DB: $DB_HOST"
|
||||||
|
echo "Source DB size: $(du -h "$DB_HOST" | cut -f1)"
|
||||||
|
echo ""
|
||||||
|
echo "Row counts at backup time:"
|
||||||
|
$COMPOSE exec -T digiserver-app python - <<'PYEOF' 2>/dev/null || echo " (unavailable)"
|
||||||
|
import sqlite3
|
||||||
|
con = sqlite3.connect('/app/instance/dashboard.db')
|
||||||
|
for t in ['player','playlist','playlist_content','player_user','player_edit',
|
||||||
|
'player_feedback','content','group','group_content','user']:
|
||||||
|
try:
|
||||||
|
n = con.execute('SELECT COUNT(*) FROM "%s"' % t).fetchone()[0]
|
||||||
|
print(' %-20s %9d' % (t, n))
|
||||||
|
except Exception as e:
|
||||||
|
print(' %-20s ERROR %s' % (t, e))
|
||||||
|
PYEOF
|
||||||
|
echo ""
|
||||||
|
echo "Files:"
|
||||||
|
( cd "$DEST" && ls -lh | tail -n +2 | sed 's/^/ /' )
|
||||||
|
} > "$DEST/MANIFEST.txt"
|
||||||
|
|
||||||
|
cat > "$DEST/RESTORE.md" <<EOF
|
||||||
|
# Restore this DigiServer backup
|
||||||
|
|
||||||
|
Backup ID: **$TS**
|
||||||
|
Created: $(date -Iseconds)
|
||||||
|
|
||||||
|
## What's in this bundle
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| \`dashboard.db.gz\` | Full consistent snapshot (all tables) - primary restore source |
|
||||||
|
| \`players_playlists.sql.gz\` | Logical dump of player/playlist tables - secondary source |
|
||||||
|
| \`SHA256SUMS\` | Integrity check |
|
||||||
|
| \`MANIFEST.txt\` | Row counts + metadata |
|
||||||
|
|
||||||
|
## Restore procedure (after Docker image / host upgrade)
|
||||||
|
|
||||||
|
Run from the project root (\`$PROJECT_DIR\`):
|
||||||
|
|
||||||
|
\`\`\`bash
|
||||||
|
./restore_database.sh $TS
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
Or do it manually:
|
||||||
|
|
||||||
|
\`\`\`bash
|
||||||
|
# 1. Stop the app so nothing writes to the DB
|
||||||
|
docker compose stop digiserver-app
|
||||||
|
|
||||||
|
# 2. Verify the backup is intact
|
||||||
|
( cd backups/$TS && sha256sum -c SHA256SUMS )
|
||||||
|
|
||||||
|
# 3. Decompress and swap in the database
|
||||||
|
gunzip -c backups/$TS/dashboard.db.gz > data/instance/dashboard.db
|
||||||
|
|
||||||
|
# 4. Fix ownership (container runs as uid 1000)
|
||||||
|
sudo chown 1000:1000 data/instance/dashboard.db
|
||||||
|
chmod 644 data/instance/dashboard.db
|
||||||
|
|
||||||
|
# 5. Remove any stale journal file
|
||||||
|
rm -f data/instance/dashboard.db-journal
|
||||||
|
|
||||||
|
# 6. Rebuild/start the upgraded image
|
||||||
|
docker compose up -d --build
|
||||||
|
|
||||||
|
# 7. Re-run migrations (new schema may have been added)
|
||||||
|
docker compose exec -T digiserver-app python /app/migrations/add_https_config_table.py
|
||||||
|
docker compose exec -T digiserver-app python /app/migrations/add_player_user_table.py
|
||||||
|
docker compose exec -T digiserver-app python /app/migrations/add_email_to_https_config.py
|
||||||
|
docker compose exec -T digiserver-app python /app/migrations/migrate_player_user_global.py
|
||||||
|
|
||||||
|
# 8. Verify
|
||||||
|
docker compose exec -T digiserver-app python -c "
|
||||||
|
import sqlite3
|
||||||
|
c=sqlite3.connect('/app/instance/dashboard.db')
|
||||||
|
print('players: ', c.execute('SELECT COUNT(*) FROM player').fetchone()[0])
|
||||||
|
print('playlists:', c.execute('SELECT COUNT(*) FROM playlist').fetchone()[0])
|
||||||
|
print('items: ', c.execute('SELECT COUNT(*) FROM playlist_content').fetchone()[0])
|
||||||
|
"
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
## Important notes
|
||||||
|
|
||||||
|
- The container's **entrypoint only creates a DB if one is missing** - dropping in this
|
||||||
|
file bypasses re-initialisation, which is what we want.
|
||||||
|
- Ownership **must be uid/gid 1000** (host \`pi:pi\` == container \`appuser\`).
|
||||||
|
- If the new app version changed the schema, the migration step (7) is mandatory.
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "============================================================"
|
||||||
|
echo " Backup complete and restore-ready: $DEST"
|
||||||
|
echo " To restore later: ./restore_database.sh $TS"
|
||||||
|
echo "============================================================"
|
||||||
@@ -0,0 +1,225 @@
|
|||||||
|
# Cum convertesc acest manual în format .DOCX
|
||||||
|
|
||||||
|
Acest fișier explică pașii pentru a transforma `Manual-Utilizare-DigiServer.md` într-un document Word (`.docx`) gata de printat sau distribuit.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Metoda 1 – Pandoc (recomandată)
|
||||||
|
|
||||||
|
Pandoc este cea mai bună soluție: convertește Markdown în DOCX păstrând titluri, tabele, liste și imagini.
|
||||||
|
|
||||||
|
### 1.1. Instalare Pandoc
|
||||||
|
|
||||||
|
Pe Debian/Ubuntu (inclusiv pe acest server):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y pandoc
|
||||||
|
```
|
||||||
|
|
||||||
|
Verificare:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pandoc --version
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2. Conversie simplă
|
||||||
|
|
||||||
|
Din folderul `documentatie/`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /srv/digiserver-v2/documentatie
|
||||||
|
pandoc Manual-Utilizare-DigiServer.md -o Manual-Utilizare-DigiServer.docx
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3. Conversie cu cuprins automat (Table of Contents)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pandoc Manual-Utilizare-DigiServer.md \
|
||||||
|
--toc \
|
||||||
|
--toc-depth=2 \
|
||||||
|
--number-sections \
|
||||||
|
-o Manual-Utilizare-DigiServer.docx
|
||||||
|
```
|
||||||
|
|
||||||
|
| Opțiune | Efect |
|
||||||
|
|---------|-------|
|
||||||
|
| `--toc` | Adaugă cuprins automat |
|
||||||
|
| `--toc-depth=2` | Cuprinsul include titluri de nivel 1 și 2 |
|
||||||
|
| `--number-sections` | Numerotează secțiunile (1, 1.1, 1.2...) |
|
||||||
|
|
||||||
|
### 1.4. Conversie cu șablon personalizat (logo, fonturi corporate)
|
||||||
|
|
||||||
|
Dacă doriți un aspect personalizat:
|
||||||
|
|
||||||
|
1. Generați un șablon de referință:
|
||||||
|
```bash
|
||||||
|
pandoc -o template-referinta.docx --print-default-data-file reference.docx
|
||||||
|
```
|
||||||
|
2. Deschideți `template-referinta.docx` în Word
|
||||||
|
3. Modificați stilurile (fonturi, culori, antet, logo)
|
||||||
|
4. Salvați și folosiți-l la conversie:
|
||||||
|
```bash
|
||||||
|
pandoc Manual-Utilizare-DigiServer.md \
|
||||||
|
--toc --number-sections \
|
||||||
|
--reference-doc=template-referinta.docx \
|
||||||
|
-o Manual-Utilizare-DigiServer.docx
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.5. Conversie cu imagini redimensionate (pentru capturile de ecran)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pandoc Manual-Utilizare-DigiServer.md \
|
||||||
|
--toc --number-sections \
|
||||||
|
--resource-path=screenshots \
|
||||||
|
-o Manual-Utilizare-DigiServer.docx
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Notă:** Pentru ca imaginile să apară în document, ele trebuie să existe în folderul
|
||||||
|
> `screenshots/` cu numele exacte din Anexa B și trebuie referite în Markdown.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Metoda 2 – VS Code (fără instalări suplimentare)
|
||||||
|
|
||||||
|
Dacă editați în VS Code:
|
||||||
|
|
||||||
|
1. Instalați extensia **„Markdown PDF"** sau **„Markdown Preview Enhanced"**
|
||||||
|
2. Deschideți `Manual-Utilizare-DigiServer.md`
|
||||||
|
3. Click dreapta → **„Markdown: Export to DOCX"** (sau PDF)
|
||||||
|
4. Salvați documentul rezultat
|
||||||
|
|
||||||
|
Alternativ, extensia **„Markdown All in One"** + **„Pandoc"** permite export direct DOCX din Command Palette.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Metoda 3 – Online (fără instalare)
|
||||||
|
|
||||||
|
Pentru conversie rapidă fără a instala nimic:
|
||||||
|
|
||||||
|
1. Accesați un convertor online (ex: `https://www.markdowntodocx.com`)
|
||||||
|
2. Încărcați sau lipiți conținutul din `Manual-Utilizare-DigiServer.md`
|
||||||
|
3. Descărcați fișierul `.docx` rezultat
|
||||||
|
|
||||||
|
> **Atenție:** Nu folosiți convertizoare online pentru documente cu informații sensibile (adrese IP interne, credențiale). Preferați Metoda 1 sau 2.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pașii compleți pentru un document final (cu capturi de ecran)
|
||||||
|
|
||||||
|
### Pasul 1 – Faceți capturile de ecran
|
||||||
|
|
||||||
|
Deschideți aplicația în browser și faceți capturi pentru fiecare punct din **Anexa B** a manualului.
|
||||||
|
|
||||||
|
**Sfaturi pentru capturi bune:**
|
||||||
|
- Folosiți rezoluția completă a ferestrei (nu tăiați meniurile)
|
||||||
|
- Evitați să apară date sensibile (parole, IP-uri reale dacă nu e necesar)
|
||||||
|
- Salvați în format PNG pentru claritate
|
||||||
|
- Denumiți exact ca în Anexa B (ex: `01-login.png`)
|
||||||
|
|
||||||
|
### Pasul 2 – Salvați capturile
|
||||||
|
|
||||||
|
Puneți toate fișierele în:
|
||||||
|
|
||||||
|
```
|
||||||
|
/srv/digiserver-v2/documentatie/screenshots/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pasul 3 – Referiți imaginile în Markdown
|
||||||
|
|
||||||
|
Înlocuiți liniile de tip:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
**[SCREENSHOT: 03-dashboard.png – Dashboard după autentificare]**
|
||||||
|
```
|
||||||
|
|
||||||
|
cu:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|

|
||||||
|
|
||||||
|
*Figura 1 – Dashboard-ul principal al aplicației*
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pasul 4 – Convertiți în DOCX
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /srv/digiserver-v2/documentatie
|
||||||
|
pandoc Manual-Utilizare-DigiServer.md \
|
||||||
|
--toc \
|
||||||
|
--toc-depth=2 \
|
||||||
|
--number-sections \
|
||||||
|
--resource-path=screenshots \
|
||||||
|
-o Manual-Utilizare-DigiServer.docx
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pasul 5 – Verificați documentul
|
||||||
|
|
||||||
|
Deschideți `Manual-Utilizare-DigiServer.docx` în Word / LibreOffice și verificați:
|
||||||
|
|
||||||
|
- [ ] Cuprinsul este corect și complet
|
||||||
|
- [ ] Toate capturile de ecran apar
|
||||||
|
- [ ] Tabelele sunt formatate corect
|
||||||
|
- [ ] Numerotarea secțiunilor este consecventă
|
||||||
|
- [ ] Textul diacriticilor românești (ă, â, î, ș, ț) se afișează corect
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Script automat de conversie
|
||||||
|
|
||||||
|
Un script care face totul automat:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# convert_manual.sh - Converteste manualul in DOCX cu cuprins si imagini
|
||||||
|
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
echo "Verificare Pandoc..."
|
||||||
|
if ! command -v pandoc &> /dev/null; then
|
||||||
|
echo "Pandoc nu este instalat. Rulati: sudo apt-get install -y pandoc"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Numar capturi de ecran disponibile:"
|
||||||
|
ls -1 screenshots/*.png 2>/dev/null | wc -l
|
||||||
|
|
||||||
|
echo "Conversie in DOCX..."
|
||||||
|
pandoc Manual-Utilizare-DigiServer.md \
|
||||||
|
--toc \
|
||||||
|
--toc-depth=2 \
|
||||||
|
--number-sections \
|
||||||
|
--resource-path=screenshots \
|
||||||
|
-o Manual-Utilizare-DigiServer.docx
|
||||||
|
|
||||||
|
if [ -f Manual-Utilizare-DigiServer.docx ]; then
|
||||||
|
echo "✓ Document creat: Manual-Utilizare-DigiServer.docx"
|
||||||
|
ls -lh Manual-Utilizare-DigiServer.docx
|
||||||
|
else
|
||||||
|
echo "✗ Eroare la conversie"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
Salvați ca `convert_manual.sh`, apoi:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
chmod +x convert_manual.sh
|
||||||
|
./convert_manual.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rezumat rapid
|
||||||
|
|
||||||
|
| Metodă | Necesită | Calitate | Recomandat pentru |
|
||||||
|
|--------|----------|----------|-------------------|
|
||||||
|
| **Pandoc** | instalare | ★★★★★ | Document final profesional |
|
||||||
|
| **VS Code** | extensie | ★★★★ | Utilizatori VS Code |
|
||||||
|
| **Online** | browser | ★★★ | Conversie rapidă, documente nesensibile |
|
||||||
|
|
||||||
|
**Recomandare:** Folosiți **Pandoc cu `--toc` și `--number-sections`** și un șablon de referință personalizat pentru un document final profesionist.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Pentru întrebări despre conversie, consultați documentația Pandoc: https://pandoc.org/MANUAL.html*
|
||||||
@@ -0,0 +1,711 @@
|
|||||||
|
# Manual de Utilizare – DigiServer v2
|
||||||
|
|
||||||
|
**Versiune document:** 1.0
|
||||||
|
**Data:** Septembrie 2026
|
||||||
|
**Aplicație:** DigiServer v2 – Sistem de management al conținutului pentru ecrane digitale (Digital Signage)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cuprins
|
||||||
|
|
||||||
|
1. [Introducere](#1-introducere)
|
||||||
|
2. [Autentificare în aplicație](#2-autentificare-în-aplicație)
|
||||||
|
3. [Dashboard (Panoul principal)](#3-dashboard-panoul-principal)
|
||||||
|
4. [Biblioteca de Media](#4-biblioteca-de-media)
|
||||||
|
5. [Adăugarea de Media în sistem](#5-adăugarea-de-media-în-sistem)
|
||||||
|
6. [Managementul Playlist-urilor](#6-managementul-playlist-urilor)
|
||||||
|
7. [Adăugarea de conținut într-un Playlist](#7-adăugarea-de-conținut-într-un-playlist)
|
||||||
|
8. [Ștergerea unui fișier din Playlist](#8-ștergerea-unui-fișier-din-playlist)
|
||||||
|
9. [Reordonarea conținutului în Playlist](#9-reordonarea-conținutului-în-playlist)
|
||||||
|
10. [Managementul Playerelelor](#10-managementul-playerelelor)
|
||||||
|
11. [Vizualizarea fișierelor editate pe Player (funcție avansată)](#11-vizualizarea-fișierelor-editate-pe-player-funcție-avansată)
|
||||||
|
12. [Administrare](#12-administrare)
|
||||||
|
13. [Întrebări frecvente și depanare](#13-întrebări-frecvente-și-depanare)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Introducere
|
||||||
|
|
||||||
|
**DigiServer v2** este o aplicație web folosită pentru a gestiona conținutul afișat pe ecranele digitale (playere). Cu ajutorul ei puteți:
|
||||||
|
|
||||||
|
- Încărca fișiere media (imagini, videoclipuri, documente PDF, prezentări PowerPoint)
|
||||||
|
- Crea **playlist-uri** – liste ordonate de conținut care se afișează pe ecrane
|
||||||
|
- Atribui playlist-uri către playere specifice
|
||||||
|
- Urmări automatizarea și starea playerelor
|
||||||
|
- Vizualiza și gestiona versiunile editate ale fișierelor de pe playere
|
||||||
|
|
||||||
|
### Structura generală a aplicației
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ UTILIZATOR (browser) │
|
||||||
|
│ │ │
|
||||||
|
│ ▼ │
|
||||||
|
│ DigiServer (interfață web) │
|
||||||
|
│ │ │
|
||||||
|
│ ├──► Biblioteca de Media (fișiere) │
|
||||||
|
│ ├──► Playlist-uri (liste de redare) │
|
||||||
|
│ └──► Playere (ecrane fizice) │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Roluri de utilizator
|
||||||
|
|
||||||
|
| Rol | Drepturi |
|
||||||
|
|-----|----------|
|
||||||
|
| **Admin** | Acces complet: utilizatori, setări sistem, HTTPS, playere, conținut |
|
||||||
|
| **Editor** | Poate încărca conținut, crea playlist-uri și edita playere |
|
||||||
|
| **Utilizator** | Poate vizualiza conținut și playlists |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Autentificare în aplicație
|
||||||
|
|
||||||
|
### 2.1. Deschiderea aplicației
|
||||||
|
|
||||||
|
Deschideți un browser (Chrome, Firefox sau Edge) și accesați una dintre adresele:
|
||||||
|
|
||||||
|
- **Prin adresă IP:** `https://10.76.152.164`
|
||||||
|
- **Prin nume gazdă:** `https://digiserver`
|
||||||
|
- **Prin domeniu:** `https://digiserver.sibiusb.harting.intra`
|
||||||
|
|
||||||
|
> **Notă:** Dacă browserul afișează un avertisment de certificat („Conexiunea nu este privată"), acest lucru este normal pentru rețeaua internă. Apăsați **„Avansat" → „Continuă către..."**. Administratorul de rețea poate instala certificatul intern pentru a elimina acest avertisment.
|
||||||
|
|
||||||
|
### 2.2. Ecranul de autentificare
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*Figura 1 – Ecranul de login*
|
||||||
|
|
||||||
|
Introduceți:
|
||||||
|
|
||||||
|
1. **Nume utilizator** (username)
|
||||||
|
2. **Parolă**
|
||||||
|
3. Apăsați butonul **„Autentificare"** / **„Login"**
|
||||||
|
|
||||||
|
> **Important:** Dacă ați uitat parola, contactați administratorul sistemului. Parola poate fi schimbată ulterior din meniul de cont.
|
||||||
|
|
||||||
|
### 2.3. Schimbarea parolei
|
||||||
|
|
||||||
|
După prima autentificare, se recomandă schimbarea parolei:
|
||||||
|
|
||||||
|
1. Accesați **meniul de utilizator** (colț dreapta-sus)
|
||||||
|
2. Selectați **„Schimbă parola"** / **„Change Password"**
|
||||||
|
3. Introduceți parola veche, apoi parola nouă (de două ori)
|
||||||
|
4. Confirmați
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*Figura 2 – Schimbarea parolei*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Dashboard (Panoul principal)
|
||||||
|
|
||||||
|
După autentificare, sunteți redirecționat către **Dashboard** – pagina principală a aplicației.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*Figura 3 – Dashboard-ul principal*
|
||||||
|
|
||||||
|
### 3.1. Elementele Dashboard-ului
|
||||||
|
|
||||||
|
Dashboard-ul oferă o imagine de ansamblu asupra sistemului:
|
||||||
|
|
||||||
|
| Element | Descriere |
|
||||||
|
|---------|-----------|
|
||||||
|
| **Statistici** | Număr de playere, playlist-uri, fișiere media |
|
||||||
|
| **Stare playere** | Care playere sunt online / offline |
|
||||||
|
| **Acțiuni rapide** | Butoane pentru adăugare rapidă de conținut |
|
||||||
|
| **Meniu de navigare** | Acces către toate secțiunile aplicației |
|
||||||
|
|
||||||
|
### 3.2. Meniul principal de navigare
|
||||||
|
|
||||||
|
Meniul din partea stângă (sau de sus) conține:
|
||||||
|
|
||||||
|
| Secțiune | Funcție |
|
||||||
|
|----------|---------|
|
||||||
|
| **Dashboard** | Pagina principală cu statistici |
|
||||||
|
| **Media Library** (Biblioteca de Media) | Toate fișierele încărcate |
|
||||||
|
| **Playlists** | Gestionarea listelor de redare |
|
||||||
|
| **Players** (Playere) | Gestionarea ecranelor |
|
||||||
|
| **Admin** | Setări de sistem (doar pentru administratori) |
|
||||||
|
|
||||||
|
### 3.3. Cum citiți statisticile
|
||||||
|
|
||||||
|
1. **Playere (Players):** Numărul total de ecrane configurate
|
||||||
|
2. **Playlist-uri:** Numărul de liste de redare existente
|
||||||
|
3. **Media:** Numărul total de fișiere încărcate
|
||||||
|
|
||||||
|
Apăsați pe orice statistică pentru a accesa secțiunea corespunzătoare.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Biblioteca de Media
|
||||||
|
|
||||||
|
Biblioteca de Media conține **toate fișierele** încărcate în sistem: imagini, videoclipuri, documente PDF și prezentări.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*Figura 4 – Bibliotecac de Media*
|
||||||
|
|
||||||
|
### 4.1. Accesarea bibliotecii
|
||||||
|
|
||||||
|
Din meniul principal, selectați **„Media Library"** sau accesați direct `/media-library`.
|
||||||
|
|
||||||
|
### 4.2. Informații afișate pentru fiecare fișier
|
||||||
|
|
||||||
|
| Coloană / Element | Semnificație |
|
||||||
|
|-------------------|--------------|
|
||||||
|
| **Nume fișier** | Numele original al fișierului încărcat |
|
||||||
|
| **Tip** | Imagine / Video / PDF / Prezentare |
|
||||||
|
| **Dimensiune** | Mărimea fișierului |
|
||||||
|
| **Data încărcării** | Când a fost adăugat în sistem |
|
||||||
|
| **Previzualizare** | Miniatură pentru imagini și videoclipuri |
|
||||||
|
|
||||||
|
### 4.3. Filtrarea și căutarea
|
||||||
|
|
||||||
|
- Folosiți **bara de căutare** pentru a găsi un fișier după nume
|
||||||
|
- Folosiți **filtrele** pentru a afișa doar un anumit tip (imagini, videoclipuri etc.)
|
||||||
|
|
||||||
|
### 4.4. Ștergerea unui fișier din bibliotecă
|
||||||
|
|
||||||
|
> **Atenție:** Ștergerea din bibliotecă este permanentă. Dacă fișierul este folosit într-un playlist, acesta va apărea ca lipsă.
|
||||||
|
|
||||||
|
1. Găsiți fișierul în listă
|
||||||
|
2. Apăsați butonul **Ștergere** (iconiță coș de gunoi / X)
|
||||||
|
3. Confirmați ștergerea
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*Figura 5 – Accesarea butonului de ștergere*
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*Figura 6 – Confirmarea ștergerii*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Adăugarea de Media în sistem
|
||||||
|
|
||||||
|
### 5.1. Deschiderea formularului de încărcare
|
||||||
|
|
||||||
|
Din meniul principal selectați **„Upload Media"** sau accesați pagina de încărcare.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*Figura 7 – Pagina de încărcare media*
|
||||||
|
|
||||||
|
### 5.2. Tipuri de fișiere acceptate
|
||||||
|
|
||||||
|
| Categorie | Formate acceptate |
|
||||||
|
|-----------|-------------------|
|
||||||
|
| **Imagini** | PNG, JPG, JPEG, GIF, BMP |
|
||||||
|
| **Video** | MP4, AVI, MKV, MOV, WEBM |
|
||||||
|
| **Documente** | PDF |
|
||||||
|
| **Prezentări** | PPT, PPTX |
|
||||||
|
|
||||||
|
### 5.3. Pașii pentru încărcare
|
||||||
|
|
||||||
|
1. **Apăsați zona de selecție** sau butonul **„Alege fișier"**
|
||||||
|
2. Selectați fișierul (sau mai multe fișiere) din calculator
|
||||||
|
3. Așteptați finalizarea încărcării
|
||||||
|
4. Veți primi confirmarea că fișierul a fost adăugat
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*Figura 8 – Selectarea fișierului*
|
||||||
|
|
||||||
|
### 5.4. Confirmarea încărcării
|
||||||
|
|
||||||
|
După finalizarea încărcării, aplicația afișează un banner verde de confirmare cu mesajul **„Successfully uploaded"**, care indică playlist-ul în care a fost adăugat fișierul.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*Figura 9 – Bannerul „Successfully uploaded"*
|
||||||
|
|
||||||
|
### 5.5. Adăugarea unui link web (Web Link)
|
||||||
|
|
||||||
|
Pe lângă fișiere, puteți adăuga și **adrese web** care să fie afișate pe ecrane.
|
||||||
|
|
||||||
|
1. Selectați opțiunea **„Add Web Link"** / **„Adaugă link web"**
|
||||||
|
2. Introduceți **URL-ul complet** (ex: `https://www.exemplu.ro`)
|
||||||
|
3. Dați un **titlu** pentru link
|
||||||
|
4. Salvați
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*Figura 10 – Adăugarea unui link web*
|
||||||
|
|
||||||
|
> **Sfat:** Limita maximă pentru un fișier este de 2 GB. Pentru fișiere foarte mari, împărțiți-le sau contactați administratorul.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Managementul Playlist-urilor
|
||||||
|
|
||||||
|
Un **playlist** este o listă ordonată de conținut care se redă pe un player. Puteți avea mai multe playlist-uri pentru diferite ecrane sau situații.
|
||||||
|
|
||||||
|
### 6.1. Vizualizarea playlist-urilor
|
||||||
|
|
||||||
|
Din meniul principal selectați **„Playlists"**.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*Figura 9 – Lista playlist-urilor*
|
||||||
|
|
||||||
|
Aici vedeți toate playlist-urile existente, cu:
|
||||||
|
|
||||||
|
| Element | Descriere |
|
||||||
|
|---------|-----------|
|
||||||
|
| **Nume** | Denumirea playlist-ului |
|
||||||
|
| **Nr. elemente** | Câte fișiere conține |
|
||||||
|
| **Player atribuit** | Pe ce ecran se redă |
|
||||||
|
| **Acțiuni** | Editare, gestionare conținut, ștergere |
|
||||||
|
|
||||||
|
### 6.2. Crearea unui playlist nou
|
||||||
|
|
||||||
|
1. Apăsați butonul **„Creează Playlist"** / **„Create Playlist"**
|
||||||
|
2. Introduceți **numele playlist-ului** (ex: „Ecran Recepție")
|
||||||
|
3. Opțional: adăugați o **descriere**
|
||||||
|
4. Apăsați **„Salvează"**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*Figura 10 – Crearea unui playlist*
|
||||||
|
|
||||||
|
### 6.3. Gestionarea conținutului unui playlist
|
||||||
|
|
||||||
|
Pentru a vedea și edita conținutul unui playlist:
|
||||||
|
|
||||||
|
1. Găsiți playlist-ul în listă
|
||||||
|
2. Apăsați butonul **„Gestionare"** / **„Manage"** (sau numele playlist-ului)
|
||||||
|
3. Se deschide pagina de management a conținutului
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*Figura 12 – Accesarea managementului*
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*Figura 13 – Conținutul playlist-ului*
|
||||||
|
|
||||||
|
### 6.4. Ștergerea unui playlist
|
||||||
|
|
||||||
|
1. Găsiți playlist-ul în listă
|
||||||
|
2. Apăsați butonul **Ștergere**
|
||||||
|
3. Confirmați
|
||||||
|
|
||||||
|
> **Atenție:** Ștergerea unui playlist nu șterge fișierele media. Doar lista de redare este eliminată.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Adăugarea de conținut într-un Playlist
|
||||||
|
|
||||||
|
### 7.1. Pași pentru adăugare
|
||||||
|
|
||||||
|
1. Deschideți playlist-ul (secțiunea **Management**)
|
||||||
|
2. Apăsați **„Adaugă conținut"** / **„Add Content"**
|
||||||
|
3. Se afișează lista cu toate fișierele disponibile
|
||||||
|
4. **Bifați** fișierele dorite (puteți selecta mai multe)
|
||||||
|
5. Apăsați **„Adaugă"**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*Figura 12 – Adăugarea conținutului*
|
||||||
|
|
||||||
|
### 7.2. Opțiuni per element
|
||||||
|
|
||||||
|
După adăugare, pentru fiecare element puteți configura:
|
||||||
|
|
||||||
|
| Opțiune | Descriere |
|
||||||
|
|---------|-----------|
|
||||||
|
| **Durată afișare** | Cât timp se afișează elementul (secunde) |
|
||||||
|
| **Fără sunet (Muted)** | Dezactivează sunetul pentru acel element |
|
||||||
|
| **Editare permisă** | Permite player-ului să editeze fișierul local |
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*Figura 13 – Opțiunile elementelor*
|
||||||
|
|
||||||
|
### 7.3. Adăugarea unui link web direct în playlist
|
||||||
|
|
||||||
|
1. În pagina de management a playlist-ului, apăsați **„Adaugă link web"**
|
||||||
|
2. Introduceți URL-ul și titlul
|
||||||
|
3. Salvați – link-ul apare în listă ca orice alt element
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Ștergerea unui fișier din Playlist
|
||||||
|
|
||||||
|
### 8.1. Ștergerea unui singur element
|
||||||
|
|
||||||
|
1. Deschideți playlist-ul în modul **Management**
|
||||||
|
2. Găsiți elementul dorit în listă
|
||||||
|
3. Apăsați iconița **Ștergere** (X / coș de gunoi) de pe rândul respectiv
|
||||||
|
4. Confirmați dacă vi se cere
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*Figura 14 – Ștergerea unui element*
|
||||||
|
|
||||||
|
> **Important:** Ștergerea din playlist **nu șterge fișierul** din Biblioteca de Media. Fișierul rămâne disponibil pentru alte playlist-uri.
|
||||||
|
|
||||||
|
### 8.2. Ștergerea mai multor elemente simultan (Bulk Remove)
|
||||||
|
|
||||||
|
Pentru a șterge mai multe fișiere dintr-un playlist:
|
||||||
|
|
||||||
|
1. În pagina de management, **bifați** elementele dorite
|
||||||
|
2. Apăsați butonul **„Șterge selectate"** / **„Bulk Remove"**
|
||||||
|
3. Confirmați operațiunea
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*Figura 15 – Ștergerea în masă*
|
||||||
|
|
||||||
|
### 8.3. Diferența: ștergere playlist vs. ștergere fișier
|
||||||
|
|
||||||
|
| Acțiune | Efect |
|
||||||
|
|---------|-------|
|
||||||
|
| **Ștergere element din playlist** | Elementul dispare din listă; fișierul rămâne în bibliotecă |
|
||||||
|
| **Ștergere fișier din bibliotecă** | Fișierul dispare complet; apare ca lipsă în toate playlist-urile |
|
||||||
|
| **Ștergere playlist** | Lista dispare; toate fișierele rămân în bibliotecă |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Reordonarea conținutului în Playlist
|
||||||
|
|
||||||
|
Ordinea elementelor determină ordinea de afișare pe ecran.
|
||||||
|
|
||||||
|
### 9.1. Reordonare prin tragere (drag & drop)
|
||||||
|
|
||||||
|
1. Deschideți playlist-ul în modul **Management**
|
||||||
|
2. Apăsați și țineți apăsat pe iconița de **mâner** (⋮⋮) din dreptul unui element
|
||||||
|
3. Trageți elementul în poziția dorită
|
||||||
|
4. Ordinea se salvează automat
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*Figura 16 – Reordonarea elementelor*
|
||||||
|
|
||||||
|
### 9.2. Verificarea ordinii
|
||||||
|
|
||||||
|
După reordonare, verificați că numerele de ordine (1, 2, 3...) corespund intenției dvs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Managementul Playerelelor
|
||||||
|
|
||||||
|
**Playerele** sunt ecranele fizice care afișează conținutul.
|
||||||
|
|
||||||
|
### 10.1. Lista playerelor
|
||||||
|
|
||||||
|
Din meniul principal selectați **„Players"**.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*Figura 17 – Lista playerelor*
|
||||||
|
|
||||||
|
| Coloană | Semnificație |
|
||||||
|
|---------|--------------|
|
||||||
|
| **Nume** | Denumirea player-ului |
|
||||||
|
| **Locație** | Unde este amplasat ecranul |
|
||||||
|
| **Adresă IP** | Adresa de rețea a player-ului |
|
||||||
|
| **Playlist atribuit** | Ce conținut redă |
|
||||||
|
| **Stare** | Online / Offline / Stare deploy |
|
||||||
|
|
||||||
|
### 10.2. Adăugarea unui player nou
|
||||||
|
|
||||||
|
1. Apăsați **„Adaugă Player"** / **„Add Player"**
|
||||||
|
2. Completați:
|
||||||
|
- **Nume** (ex: „Ecran Hol Principal")
|
||||||
|
- **Adresă IP**
|
||||||
|
- **Locație** (opțional)
|
||||||
|
3. Salvați
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*Figura 20 – Formularul de adăugare player*
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*Figura 21 – Formularul completat*
|
||||||
|
|
||||||
|
### 10.3. Editarea unui player
|
||||||
|
|
||||||
|
1. Apăsați **„Editare"** / **„Edit"** pe rândul player-ului
|
||||||
|
2. Modificați câmpurile dorite
|
||||||
|
3. Salvați
|
||||||
|
|
||||||
|
### 10.4. Atribuirea unui playlist unui player
|
||||||
|
|
||||||
|
1. În lista de playere, găsiți player-ul dorit
|
||||||
|
2. Apăsați **„Atribuie Playlist"** / **„Assign Playlist"**
|
||||||
|
3. Selectați playlist-ul din listă
|
||||||
|
4. Confirmați
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*Figura 22 – Selectarea playlist-ului*
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*Figura 23 – Confirmarea atribuirii*
|
||||||
|
|
||||||
|
> **Notă:** După atribuire, player-ul va prelua noul conținut la următoarea sincronizare.
|
||||||
|
|
||||||
|
### 10.5. Starea de deployment
|
||||||
|
|
||||||
|
Fiecare player are o **stare de deployment** care arată dacă conținutul a fost trimis cu succes:
|
||||||
|
|
||||||
|
| Stare | Semnificație |
|
||||||
|
|-------|--------------|
|
||||||
|
| ✓ **Succes** | Conținutul a fost trimis cu succes |
|
||||||
|
| ⏳ **În așteptare** | Se așteaptă sincronizarea |
|
||||||
|
| ✗ **Eroare** | Trimiterea a eșuat – verificați mesajul |
|
||||||
|
|
||||||
|
### 10.6. Regenerarea cheii de autentificare
|
||||||
|
|
||||||
|
Dacă un player nu se mai poate conecta:
|
||||||
|
|
||||||
|
1. Găsiți player-ul în listă
|
||||||
|
2. Apăsați **„Regenerează autentificare"** / **„Regenerate Auth"**
|
||||||
|
3. Confirmați – se va genera o cheie nouă
|
||||||
|
|
||||||
|
> **Atenție:** Va trebui să actualizați cheia și pe player-ul fizic.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Vizualizarea fișierelor editate pe Player (funcție avansată)
|
||||||
|
|
||||||
|
Această funcție vă permite să vedeți **ce versiuni editate** ale fișierelor există pe fiecare player. Este utilă când un player modifică local fișierele (de exemplu, suprapune informații, ajustează rezoluția etc.).
|
||||||
|
|
||||||
|
### 11.1. Accesarea raportului de fișiere editate
|
||||||
|
|
||||||
|
1. Accesați secțiunea **Players**
|
||||||
|
2. Găsiți player-ul dorit
|
||||||
|
3. Apăsați **„Fișiere editate"** / **„Edited Media"**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*Figura 20 – Fișierele editate*
|
||||||
|
|
||||||
|
### 11.2. Ce afișează această pagină
|
||||||
|
|
||||||
|
| Element | Descriere |
|
||||||
|
|---------|-----------|
|
||||||
|
| **Fișier original** | Fișierul din biblioteca centrală |
|
||||||
|
| **Versiune editată** | Fișierul modificat pe player |
|
||||||
|
| **Data editării** | Când a fost modificat |
|
||||||
|
| **Status** | Sincronizat / Diferit / Lipsă |
|
||||||
|
|
||||||
|
### 11.3. Raportul detaliat
|
||||||
|
|
||||||
|
Pentru o vedere completă:
|
||||||
|
|
||||||
|
1. Apăsați **„Raport fișiere editate"** / **„Edited Media Report"**
|
||||||
|
2. Se generează un raport cu toate diferențele
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*Figura 26 – Raportul fișierelor editate*
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*Figura 27 – Detaliu din raport*
|
||||||
|
|
||||||
|
### 11.4. Interpretarea statusurilor
|
||||||
|
|
||||||
|
| Status | Ce înseamnă | Ce faceți |
|
||||||
|
|--------|-------------|-----------|
|
||||||
|
| **Sincronizat** | Fișierul de pe player corespunde cu cel central | Nimic – este în regulă |
|
||||||
|
| **Diferit** | Fișierul a fost editat pe player | Decideți dacă păstrați sau resincronizați |
|
||||||
|
| **Lipsă** | Fișierul lipsește de pe player | Resincronizați player-ul |
|
||||||
|
|
||||||
|
### 11.5. Vizualizare în mod ecran complet (Fullscreen)
|
||||||
|
|
||||||
|
Pentru a vedea cum arată conținutul pe player:
|
||||||
|
|
||||||
|
1. În lista de playere, apăsați **„Ecran complet"** / **„Fullscreen"**
|
||||||
|
2. Se deschide o previzualizare la scară
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*Figura 22 – Previzualizarea fullscreen*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Administrare
|
||||||
|
|
||||||
|
> Această secțiune este disponibilă **doar pentru administratori**.
|
||||||
|
|
||||||
|
### 12.1. Managementul utilizatorilor
|
||||||
|
|
||||||
|
**Admin → Users**
|
||||||
|
|
||||||
|
- Creați utilizatori noi
|
||||||
|
- Modificați roluri (Admin / Editor / Utilizator)
|
||||||
|
- Resetați parole
|
||||||
|
- Ștergeți utilizatori
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*Figura 28 – Lista utilizatorilor*
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*Figura 29 – Editarea unui utilizator*
|
||||||
|
|
||||||
|
### 12.2. Setări HTTPS
|
||||||
|
|
||||||
|
**Admin → HTTPS Configuration**
|
||||||
|
|
||||||
|
Aici configurați certificatul SSL/TLS pentru acces securizat:
|
||||||
|
|
||||||
|
1. **Hostname** – numele serverului
|
||||||
|
2. **Adresă IP** – IP-ul serverului
|
||||||
|
3. **Domeniu** – domeniul (lăsați gol pentru rețea internă)
|
||||||
|
4. **Email** – pentru notificări certificat
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*Figura 24 – Configurarea HTTPS*
|
||||||
|
|
||||||
|
> **Important:** Când mutați serverul în rețeaua de producție, setați IP-ul corect (`10.76.152.164`).
|
||||||
|
|
||||||
|
### 12.3. Personalizare logo
|
||||||
|
|
||||||
|
**Admin → Customize Logos**
|
||||||
|
|
||||||
|
- Încărcați logo pentru antet (header)
|
||||||
|
- Încărcați logo pentru ecranul de login
|
||||||
|
|
||||||
|
### 12.4. Dependențe și unelte
|
||||||
|
|
||||||
|
**Admin → Dependencies**
|
||||||
|
|
||||||
|
Permite instalarea de unelte opționale:
|
||||||
|
|
||||||
|
| Unealtă | Rol |
|
||||||
|
|---------|-----|
|
||||||
|
| **LibreOffice** | Conversie automată PPT/PPTX în imagini |
|
||||||
|
| **Emoji Fonts** | Afișarea corectă a emoji-urilor |
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*Figura 25 – Instalarea dependențelor*
|
||||||
|
|
||||||
|
### 12.5. Media rămasă fără utilizare
|
||||||
|
|
||||||
|
**Admin → Leftover Media**
|
||||||
|
|
||||||
|
Afișează fișierele care nu mai sunt folosite în niciun playlist. Le puteți șterge pentru a elibera spațiu.
|
||||||
|
|
||||||
|
### 12.6. Jurnale de sistem (Logs)
|
||||||
|
|
||||||
|
**Admin → System Info / Logs**
|
||||||
|
|
||||||
|
Vedeți activitatea sistemului și eventualele erori.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. Întrebări frecvente și depanare
|
||||||
|
|
||||||
|
### 13.1. Nu mă pot autentifica
|
||||||
|
|
||||||
|
**Simptom:** Parola este respinsă.
|
||||||
|
**Soluție:**
|
||||||
|
1. Verificați că scrieți corect numele de utilizator și parola (atenție la majuscule)
|
||||||
|
2. Dacă ați uitat parola, contactați administratorul
|
||||||
|
3. Verificați că accesați adresa corectă (cu `https://`)
|
||||||
|
|
||||||
|
### 13.2. Browserul afișează avertisment de certificat
|
||||||
|
|
||||||
|
**Simptom:** „Conexiunea nu este privată" / „Nu este sigur".
|
||||||
|
**Soluție:** Acest lucru este normal pentru rețeaua internă. Apăsați **„Avansat" → „Continuă către..."**. Administratorul de rețea poate furniza un certificat intern pentru a elimina avertismentul.
|
||||||
|
|
||||||
|
### 13.3. Fișierul nu se încarcă
|
||||||
|
|
||||||
|
**Simptom:** Încărcarea eșuează sau se blochează.
|
||||||
|
**Soluție:**
|
||||||
|
1. Verificați că fișierul este sub limita de **2 GB**
|
||||||
|
2. Verificați că formatul este acceptat (PNG, JPG, MP4, PDF, PPT etc.)
|
||||||
|
3. Verificați conexiunea la rețea
|
||||||
|
4. Încercați din nou; dacă persistă, contactați administratorul
|
||||||
|
|
||||||
|
### 13.4. Player-ul nu afișează conținutul nou
|
||||||
|
|
||||||
|
**Simptom:** Ați schimbat playlist-ul, dar ecranul afișează vechiul conținut.
|
||||||
|
**Soluție:**
|
||||||
|
1. Verificați că playlist-ul este **atribuit** player-ului
|
||||||
|
2. Verificați **starea de deployment** a player-ului
|
||||||
|
3. Așteptați următoarea sincronizare (poate dura câteva minute)
|
||||||
|
4. Dacă persistă, verificați că player-ul este **online** și accesibil în rețea
|
||||||
|
|
||||||
|
### 13.5. Am șters un fișier din bibliotecă din greșeală
|
||||||
|
|
||||||
|
**Simptom:** Fișierul apare ca „lipsă" în playlist-uri.
|
||||||
|
**Soluție:** Reîncărcați fișierul în bibliotecă și adăugați-l din nou în playlist. Dacă aveți un backup, contactați administratorul.
|
||||||
|
|
||||||
|
### 13.6. Playlist-ul se redă în ordine greșită
|
||||||
|
|
||||||
|
**Simptom:** Elementele apar în altă ordine decât ați stabilit.
|
||||||
|
**Soluție:** Verificați ordinea în pagina de **Management** a playlist-ului și reordonați folosind drag & drop. Confirmați că ordinea s-a salvat.
|
||||||
|
|
||||||
|
### 13.7. Fișierele editate nu corespund
|
||||||
|
|
||||||
|
**Simptom:** Raportul de fișiere editate arată diferențe neașteptate.
|
||||||
|
**Soluție:** Consultați secțiunea [11.4](#114-interpretarea-statusurilor) pentru interpretarea statusurilor. Decideți dacă resincronizați player-ul.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Anexă A – Glosar de termeni
|
||||||
|
|
||||||
|
| Termen | Explicație |
|
||||||
|
|--------|------------|
|
||||||
|
| **Media** | Fișier (imagine, video, document) încărcat în sistem |
|
||||||
|
| **Playlist** | Listă ordonată de conținut care se redă pe un ecran |
|
||||||
|
| **Player** | Dispozitivul/ecranul fizic care afișează conținutul |
|
||||||
|
| **Deployment** | Procesul de trimitere a conținutului către player |
|
||||||
|
| **Dashboard** | Pagina principală cu statistici |
|
||||||
|
| **Bibliotecă de Media** | Colecția tuturor fișierelor încărcate |
|
||||||
|
| **Bulk Remove** | Ștergerea mai multor elemente simultan |
|
||||||
|
| **Edited Media** | Versiunile modificate local ale fișierelor, de pe player |
|
||||||
|
| **Web Link** | Adresă web afișată ca element în playlist |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Anexă B – Lista capturilor de ecran necesare
|
||||||
|
|
||||||
|
Salvați capturile în folderul `documentatie/screenshots/` cu numele exacte de mai jos:
|
||||||
|
|
||||||
|
| Nr. | Nume fișier | Secțiune | Ce trebuie să conțină |
|
||||||
|
|-----|-------------|----------|------------------------|
|
||||||
|
| 1 | `01-login.png` | 2.2 | Ecranul de autentificare |
|
||||||
|
| 2 | `02-change-password.png` | 2.3 | Formularul de schimbare parolă |
|
||||||
|
| 3 | `03-dashboard.png` | 3 | Dashboard-ul complet |
|
||||||
|
| 4 | `04-media-library.png` | 4 | Lista fișierelor media |
|
||||||
|
| 5 | `05-delete-file.png` | 4.4 | Confirmarea ștergerii |
|
||||||
|
| 6 | `06-upload-media.png` | 5.1 | Formularul de încărcare |
|
||||||
|
| 7 | `07-upload-progress.png` | 5.3 | Progresul încărcării |
|
||||||
|
| 8 | `08-add-weblink.png` | 5.4 | Formularul de link web |
|
||||||
|
| 9 | `09-playlists-list.png` | 6.1 | Lista playlist-urilor |
|
||||||
|
| 10 | `10-create-playlist.png` | 6.2 | Crearea unui playlist |
|
||||||
|
| 11 | `11-manage-playlist.png` | 6.3 | Managementul conținutului |
|
||||||
|
| 12 | `12-add-content-to-playlist.png` | 7.1 | Selectarea conținutului |
|
||||||
|
| 13 | `13-content-options.png` | 7.2 | Opțiunile unui element |
|
||||||
|
| 14 | `14-remove-from-playlist.png` | 8.1 | Ștergerea unui element |
|
||||||
|
| 15 | `15-bulk-remove.png` | 8.2 | Ștergerea în masă |
|
||||||
|
| 16 | `16-reorder-playlist.png` | 9.1 | Reordonarea elementelor |
|
||||||
|
| 17 | `17-players-list.png` | 10.1 | Lista playerelor |
|
||||||
|
| 18 | `18-add-player.png` | 10.2 | Adăugarea unui player |
|
||||||
|
| 19 | `19-assign-playlist.png` | 10.4 | Atribuirea unui playlist |
|
||||||
|
| 20 | `20-edited-media.png` | 11.1 | Fișierele editate |
|
||||||
|
| 21 | `21-edited-media-report.png` | 11.3 | Raportul detaliat |
|
||||||
|
| 22 | `22-player-fullscreen.png` | 11.5 | Previzualizarea fullscreen |
|
||||||
|
| 23 | `23-user-management.png` | 12.1 | Managementul utilizatorilor |
|
||||||
|
| 24 | `24-https-config.png` | 12.2 | Configurarea HTTPS |
|
||||||
|
| 25 | `25-dependencies.png` | 12.4 | Instalarea dependențelor |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Sfârșitul documentului*
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ============================================================================
|
||||||
|
# capture_screenshots.sh - Helper pentru capturile de ecran ale manualului
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Ghideaza pas-cu-pas captura celor 25 de ecrane necesare pentru manual.
|
||||||
|
# Foloseste Flameshot (recomandat) sau scrot.
|
||||||
|
#
|
||||||
|
# Utilizare:
|
||||||
|
# ./capture_screenshots.sh # mod ghidat, pas cu pas
|
||||||
|
# ./capture_screenshots.sh --list # doar listeaza ce trebuie capturat
|
||||||
|
# ./capture_screenshots.sh --check # verifica ce capturi exista deja
|
||||||
|
# ============================================================================
|
||||||
|
set -u
|
||||||
|
|
||||||
|
SCREENSHOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/screenshots"
|
||||||
|
mkdir -p "$SCREENSHOT_DIR"
|
||||||
|
|
||||||
|
# Cele 25 de capturi necesare: nume|descriere|secțiune manual
|
||||||
|
CAPTURES=(
|
||||||
|
"01-login|Ecranul de autentificare (login)|2.2"
|
||||||
|
"02-change-password|Formularul de schimbare parola|2.3"
|
||||||
|
"03-dashboard|Dashboard-ul complet cu statistici|3"
|
||||||
|
"04-media-library|Lista fisierelor din Biblioteca de Media|4"
|
||||||
|
"05-delete-file|Confirmarea stergerii unui fisier|4.4"
|
||||||
|
"06-upload-media|Formularul de incarcare media|5.1"
|
||||||
|
"07-upload-progress|Progresul incarcarii unui fisier|5.3"
|
||||||
|
"08-add-weblink|Formularul de adaugare link web|5.4"
|
||||||
|
"09-playlists-list|Lista tuturor playlist-urilor|6.1"
|
||||||
|
"10-create-playlist|Crearea unui playlist nou|6.2"
|
||||||
|
"11-manage-playlist|Pagina de management a continutului|6.3"
|
||||||
|
"12-add-content-to-playlist|Selectarea continutului de adaugat|7.1"
|
||||||
|
"13-content-options|Optiunile per element (durata, muted)|7.2"
|
||||||
|
"14-remove-from-playlist|Stergerea unui element din playlist|8.1"
|
||||||
|
"15-bulk-remove|Stergerea in masa a elementelor|8.2"
|
||||||
|
"16-reorder-playlist|Reordonarea elementelor (drag & drop)|9.1"
|
||||||
|
"17-players-list|Lista playerelor|10.1"
|
||||||
|
"18-add-player|Adaugarea unui player nou|10.2"
|
||||||
|
"19-assign-playlist|Atribuirea unui playlist unui player|10.4"
|
||||||
|
"20-edited-media|Lista fisierelor editate pe player|11.1"
|
||||||
|
"21-edited-media-report|Raportul detaliat al fisierelor editate|11.3"
|
||||||
|
"22-player-fullscreen|Previzualizarea in mod ecran complet|11.5"
|
||||||
|
"23-user-management|Managementul utilizatorilor|12.1"
|
||||||
|
"24-https-config|Configurarea HTTPS|12.2"
|
||||||
|
"25-dependencies|Instalarea dependentelor|12.4"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Mod --list
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
if [ "${1:-}" = "--list" ]; then
|
||||||
|
echo "============================================================"
|
||||||
|
echo " Capturi de ecran necesare pentru manual (25 total)"
|
||||||
|
echo "============================================================"
|
||||||
|
echo
|
||||||
|
printf "%-6s %-34s %-36s %s\n" "NR" "FISIER" "CE SA CONTINA" "SECTIUNE"
|
||||||
|
echo "--------------------------------------------------------------------------"
|
||||||
|
i=1
|
||||||
|
for entry in "${CAPTURES[@]}"; do
|
||||||
|
IFS='|' read -r name desc section <<< "$entry"
|
||||||
|
printf "%-6s %-34s %-36s %s\n" "$i." "${name}.png" "$desc" "$section"
|
||||||
|
i=$((i+1))
|
||||||
|
done
|
||||||
|
echo
|
||||||
|
echo "Salveaza toate capturile in:"
|
||||||
|
echo " $SCREENSHOT_DIR"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Mod --check
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
if [ "${1:-}" = "--check" ]; then
|
||||||
|
echo "============================================================"
|
||||||
|
echo " Verificare capturi de ecran existente"
|
||||||
|
echo "============================================================"
|
||||||
|
echo
|
||||||
|
found=0; missing=0
|
||||||
|
for entry in "${CAPTURES[@]}"; do
|
||||||
|
IFS='|' read -r name desc section <<< "$entry"
|
||||||
|
if [ -f "$SCREENSHOT_DIR/${name}.png" ]; then
|
||||||
|
size=$(du -h "$SCREENSHOT_DIR/${name}.png" | cut -f1)
|
||||||
|
printf " \033[0;32m✓\033[0m %-32s %s\n" "${name}.png" "$size"
|
||||||
|
found=$((found+1))
|
||||||
|
else
|
||||||
|
printf " \033[0;31m✗\033[0m %-32s LIPSA\n" "${name}.png"
|
||||||
|
missing=$((missing+1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo
|
||||||
|
echo "-------------------------------------------------------------"
|
||||||
|
echo " Gasite: $found / 25"
|
||||||
|
echo " Lipsa: $missing / 25"
|
||||||
|
echo "-------------------------------------------------------------"
|
||||||
|
if [ "$missing" -eq 0 ]; then
|
||||||
|
echo
|
||||||
|
echo " Toate capturile sunt prezente! Puteti rula:"
|
||||||
|
echo " ./convert_manual.sh"
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Mod ghidat
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo "============================================================"
|
||||||
|
echo " Ghid capturi de ecran - Manual DigiServer"
|
||||||
|
echo "============================================================"
|
||||||
|
echo
|
||||||
|
|
||||||
|
if ! command -v scrot &> /dev/null && ! command -v flameshot &> /dev/null; then
|
||||||
|
echo "✗ Nu exista tool de capturi. Instalati cu:"
|
||||||
|
echo " sudo apt-get install -y flameshot scrot"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Deschideti aplicatia in browser: https://10.76.152.164"
|
||||||
|
echo "Autentificati-va ca admin, apoi urmati pasii."
|
||||||
|
echo
|
||||||
|
echo "Pentru fiecare ecran vi se va cere sa apasati Enter,"
|
||||||
|
echo "apoi se face captura. Daca ati facut-o deja, scrieti 's' (skip)."
|
||||||
|
echo
|
||||||
|
|
||||||
|
completed=0
|
||||||
|
for entry in "${CAPTURES[@]}"; do
|
||||||
|
IFS='|' read -r name desc section <<< "$entry"
|
||||||
|
target="$SCREENSHOT_DIR/${name}.png"
|
||||||
|
|
||||||
|
echo "-------------------------------------------------------------"
|
||||||
|
echo " ${name}.png"
|
||||||
|
echo " Ce: $desc"
|
||||||
|
echo " Sectiune manual: $section"
|
||||||
|
|
||||||
|
if [ -f "$target" ]; then
|
||||||
|
echo " (exista deja - scrieti 'r' pentru a reface, Enter pentru skip)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
read -r -p " Enter = captura | s = skip | q = iesire: " action
|
||||||
|
case "$action" in
|
||||||
|
q|Q) echo; echo "Iesire. Capturi realizate: $completed"; exit 0 ;;
|
||||||
|
r|R) : ;;
|
||||||
|
s|S) echo " skipped"; continue ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo " Captura in 3 secunde... (treceti la fereastra dorita)"
|
||||||
|
sleep 3
|
||||||
|
|
||||||
|
if command -v scrot &> /dev/null; then
|
||||||
|
scrot "$target"
|
||||||
|
else
|
||||||
|
flameshot full -p "$target"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "$target" ]; then
|
||||||
|
echo " ✓ Salvat: $target"
|
||||||
|
completed=$((completed+1))
|
||||||
|
else
|
||||||
|
echo " ✗ Captura a esuat"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "============================================================"
|
||||||
|
echo " Ghid terminat. Capturi realizate: $completed"
|
||||||
|
echo "============================================================"
|
||||||
|
echo
|
||||||
|
echo "Verificati cu: ./capture_screenshots.sh --check"
|
||||||
|
echo "Apoi convertiti: ./convert_manual.sh"
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ============================================================================
|
||||||
|
# convert_help_page.sh - Regenerates the in-app help page content
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Converts documentatie/Manual-Utilizare-DigiServer.md into the HTML fragment
|
||||||
|
# that the Flask app serves at /help, and refreshes the screenshot copies.
|
||||||
|
#
|
||||||
|
# Run this whenever the manual is edited, then rebuild the image:
|
||||||
|
# ./documentatie/convert_help_page.sh
|
||||||
|
# cd .. && docker compose up -d --build
|
||||||
|
# ============================================================================
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
SRC_MD="$PROJECT_DIR/documentatie/Manual-Utilizare-DigiServer.md"
|
||||||
|
SRC_IMG_DIR="$PROJECT_DIR/documentatie/screenshots"
|
||||||
|
DEST_DIR="$PROJECT_DIR/app/static/help"
|
||||||
|
DEST_IMG_DIR="$DEST_DIR/screenshots"
|
||||||
|
DEST_HTML="$DEST_DIR/_manual_body.html"
|
||||||
|
|
||||||
|
echo "============================================================"
|
||||||
|
echo " Regenerare pagina Ajutor (/help)"
|
||||||
|
echo "============================================================"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Checks
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
if ! command -v pandoc &> /dev/null; then
|
||||||
|
echo "✗ Pandoc nu este instalat."
|
||||||
|
echo " Instalati cu: sudo apt-get install -y pandoc"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ ! -f "$SRC_MD" ]; then
|
||||||
|
echo "✗ Nu am gasit manualul: $SRC_MD"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$DEST_IMG_DIR"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 1. Convert Markdown -> HTML fragment
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo "[1/3] Conversie Markdown -> HTML..."
|
||||||
|
pandoc "$SRC_MD" \
|
||||||
|
-t html5 \
|
||||||
|
--no-highlight \
|
||||||
|
--wrap=none \
|
||||||
|
-o "$DEST_HTML"
|
||||||
|
echo " ✓ $(basename "$DEST_HTML")"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 2. Copy screenshots and rewrite their paths for Flask's static route
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo
|
||||||
|
echo "[2/3] Copiere capturi de ecran si rescriere cai..."
|
||||||
|
cp "$SRC_IMG_DIR"/*.png "$DEST_IMG_DIR"/ 2>/dev/null || {
|
||||||
|
echo " ⚠ Nicio captura gasita in $SRC_IMG_DIR"
|
||||||
|
}
|
||||||
|
COUNT=$(ls -1 "$DEST_IMG_DIR"/*.png 2>/dev/null | wc -l)
|
||||||
|
echo " ✓ $COUNT capturi copiate"
|
||||||
|
|
||||||
|
# Markdown references images as "screenshots/x.png"; the app serves them from
|
||||||
|
# /static/help/screenshots/, so the prefix has to be made explicit.
|
||||||
|
sed -i 's|src="screenshots/|src="/static/help/screenshots/|g' "$DEST_HTML"
|
||||||
|
REFS=$(grep -c '/static/help/screenshots/' "$DEST_HTML" || true)
|
||||||
|
echo " ✓ $REFS referinte de imagini rescrise"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 3. Sanity check: every referenced image must exist
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo
|
||||||
|
echo "[3/3] Verificare consistenta..."
|
||||||
|
MISSING=0
|
||||||
|
while IFS= read -r ref; do
|
||||||
|
[ -z "$ref" ] && continue
|
||||||
|
if [ ! -f "$DEST_IMG_DIR/$(basename "$ref")" ]; then
|
||||||
|
echo " ✗ lipsa: $ref"
|
||||||
|
MISSING=$((MISSING + 1))
|
||||||
|
fi
|
||||||
|
done < <(grep -oE '/static/help/screenshots/[^"]+' "$DEST_HTML" | sort -u || true)
|
||||||
|
|
||||||
|
if [ "$MISSING" -eq 0 ]; then
|
||||||
|
echo " ✓ toate imaginile referite exista"
|
||||||
|
else
|
||||||
|
echo " ⚠ $MISSING imagini lipsa"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "============================================================"
|
||||||
|
echo " Gata. Rebuild imaginea pentru a aplica modificarile:"
|
||||||
|
echo " cd $PROJECT_DIR && docker compose up -d --build"
|
||||||
|
echo "============================================================"
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ============================================================================
|
||||||
|
# convert_manual.sh - Converteste manualul DigiServer in format DOCX
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Genereaza un document Word cu:
|
||||||
|
# - Cuprins automat
|
||||||
|
# - Secțiuni numerotate
|
||||||
|
# - Capturi de ecran incluse (din folderul screenshots/)
|
||||||
|
#
|
||||||
|
# Utilizare:
|
||||||
|
# ./convert_manual.sh
|
||||||
|
# ============================================================================
|
||||||
|
set -e
|
||||||
|
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
echo "============================================================"
|
||||||
|
echo " Conversie Manual DigiServer -> DOCX"
|
||||||
|
echo "============================================================"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Verificare Pandoc
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
if ! command -v pandoc &> /dev/null; then
|
||||||
|
echo "✗ Pandoc nu este instalat."
|
||||||
|
echo
|
||||||
|
echo " Instalati cu:"
|
||||||
|
echo " sudo apt-get update && sudo apt-get install -y pandoc"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✓ Pandoc detectat: $(pandoc --version | head -1)"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Verificare fisiere sursa
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
if [ ! -f "Manual-Utilizare-DigiServer.md" ]; then
|
||||||
|
echo "✗ Fisierul Manual-Utilizare-DigiServer.md nu a fost gasit."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "✓ Manual gasit"
|
||||||
|
|
||||||
|
mkdir -p screenshots
|
||||||
|
SCREENSHOT_COUNT=$(ls -1 screenshots/*.png screenshots/*.jpg 2>/dev/null | wc -l)
|
||||||
|
echo "✓ Capturi de ecran disponibile: $SCREENSHOT_COUNT"
|
||||||
|
if [ "$SCREENSHOT_COUNT" -eq 0 ]; then
|
||||||
|
echo " (Nu exista capturi - documentul va fi generat doar cu text)"
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Conversie
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo "Conversie in curs..."
|
||||||
|
echo
|
||||||
|
|
||||||
|
pandoc Manual-Utilizare-DigiServer.md \
|
||||||
|
--toc \
|
||||||
|
--toc-depth=2 \
|
||||||
|
--number-sections \
|
||||||
|
--resource-path=screenshots \
|
||||||
|
--metadata title="Manual de Utilizare – DigiServer v2" \
|
||||||
|
--metadata lang="ro-RO" \
|
||||||
|
-o Manual-Utilizare-DigiServer.docx
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Verificare rezultat
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo
|
||||||
|
if [ -f "Manual-Utilizare-DigiServer.docx" ]; then
|
||||||
|
echo "============================================================"
|
||||||
|
echo " ✓ Document creat cu succes!"
|
||||||
|
echo "============================================================"
|
||||||
|
ls -lh Manual-Utilizare-DigiServer.docx
|
||||||
|
echo
|
||||||
|
echo "Deschideti cu: libreoffice Manual-Utilizare-DigiServer.docx"
|
||||||
|
echo " sau deschideti in Microsoft Word."
|
||||||
|
else
|
||||||
|
echo "✗ Eroare: documentul nu a fost creat."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ============================================================================
|
||||||
|
# DigiServer - Database Restore
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Restores a database snapshot created by ./backup_players_playlists.sh.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./restore_database.sh # restore the latest backup
|
||||||
|
# ./restore_database.sh 20260915_143000 # restore a specific backup ID
|
||||||
|
# ./restore_database.sh --list # list available backups
|
||||||
|
# ./restore_database.sh <id> --skip-migrations
|
||||||
|
#
|
||||||
|
# Typical use: run AFTER upgrading the Docker image / docker host.
|
||||||
|
# ============================================================================
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
COMPOSE="docker compose -f $PROJECT_DIR/docker-compose.yml"
|
||||||
|
BACKUP_ROOT="$PROJECT_DIR/backups"
|
||||||
|
DB_HOST="$PROJECT_DIR/data/instance/dashboard.db"
|
||||||
|
|
||||||
|
SKIP_MIGRATIONS=0
|
||||||
|
BACKUP_ID=""
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--list) ls -1 "$BACKUP_ROOT" 2>/dev/null | sort || echo "(no backups found)"; exit 0 ;;
|
||||||
|
--skip-migrations) SKIP_MIGRATIONS=1 ;;
|
||||||
|
-*) echo "Unknown option: $arg"; exit 1 ;;
|
||||||
|
*) BACKUP_ID="$arg" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "$BACKUP_ID" ]; then
|
||||||
|
BACKUP_ID="$(ls -1 "$BACKUP_ROOT" 2>/dev/null | sort | tail -1 || true)"
|
||||||
|
fi
|
||||||
|
if [ -z "$BACKUP_ID" ]; then
|
||||||
|
echo "ERROR: no backups found in $BACKUP_ROOT"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
DEST="$BACKUP_ROOT/$BACKUP_ID"
|
||||||
|
if [ ! -d "$DEST" ]; then
|
||||||
|
echo "ERROR: backup '$BACKUP_ID' not found in $BACKUP_ROOT"
|
||||||
|
echo "Available:"
|
||||||
|
ls -1 "$BACKUP_ROOT" 2>/dev/null | sed 's/^/ /'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "============================================================"
|
||||||
|
echo " DigiServer database restore"
|
||||||
|
echo " Backup: $BACKUP_ID"
|
||||||
|
echo "============================================================"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 1. Verify integrity
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
if [ -f "$DEST/SHA256SUMS" ]; then
|
||||||
|
echo
|
||||||
|
echo "[1/6] Verifying checksums..."
|
||||||
|
( cd "$DEST" && sha256sum -c SHA256SUMS )
|
||||||
|
else
|
||||||
|
echo
|
||||||
|
echo "[1/6] No SHA256SUMS found, skipping verification"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 2. Stop the app (so nothing writes during restore)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo
|
||||||
|
echo "[2/6] Stopping application container..."
|
||||||
|
$COMPOSE stop digiserver-app
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 3. Back up the CURRENT database before overwriting it
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
if [ -f "$DB_HOST" ]; then
|
||||||
|
SAFETY="$BACKUP_ROOT/pre_restore_$(date +%Y%m%d_%H%M%S)_dashboard.db"
|
||||||
|
echo
|
||||||
|
echo "[3/6] Saving current DB as a safety copy -> $(basename "$SAFETY")"
|
||||||
|
cp "$DB_HOST" "$SAFETY"
|
||||||
|
else
|
||||||
|
echo
|
||||||
|
echo "[3/6] No existing DB to preserve"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 4. Restore the snapshot
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo
|
||||||
|
echo "[4/6] Restoring database snapshot..."
|
||||||
|
if [ -f "$DEST/dashboard.db.gz" ]; then
|
||||||
|
gunzip -c "$DEST/dashboard.db.gz" > "$DB_HOST"
|
||||||
|
elif [ -f "$DEST/dashboard.db" ]; then
|
||||||
|
cp "$DEST/dashboard.db" "$DB_HOST"
|
||||||
|
else
|
||||||
|
echo "ERROR: no dashboard.db(.gz) in backup $BACKUP_ID"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Remove any stale journal left over from the old DB
|
||||||
|
rm -f "$DB_HOST-journal" "$DB_HOST-wal" "$DB_HOST-shm"
|
||||||
|
|
||||||
|
# Ownership: container runs as uid 1000; host files are pi:pi (1000)
|
||||||
|
if [ "$(id -u)" -eq 0 ]; then
|
||||||
|
chown 1000:1000 "$DB_HOST"
|
||||||
|
else
|
||||||
|
sudo chown 1000:1000 "$DB_HOST"
|
||||||
|
fi
|
||||||
|
chmod 644 "$DB_HOST"
|
||||||
|
echo " -> restored $(du -h "$DB_HOST" | cut -f1)"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 5. Start the (upgraded) app
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo
|
||||||
|
echo "[5/6] Starting application..."
|
||||||
|
$COMPOSE up -d --build
|
||||||
|
sleep 8
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 6. Re-run migrations so any new schema is applied to the restored data
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
if [ "$SKIP_MIGRATIONS" -eq 0 ]; then
|
||||||
|
echo
|
||||||
|
echo "[6/6] Re-running migrations..."
|
||||||
|
for m in add_https_config_table add_player_user_table add_email_to_https_config migrate_player_user_global; do
|
||||||
|
if $COMPOSE exec -T digiserver-app test -f "/app/migrations/$m.py"; then
|
||||||
|
echo " - $m"
|
||||||
|
$COMPOSE exec -T digiserver-app python "/app/migrations/$m.py" || \
|
||||||
|
echo " (warning: $m reported an issue - often means it was already applied)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
else
|
||||||
|
echo
|
||||||
|
echo "[6/6] Migrations skipped (--skip-migrations)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Verify
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo
|
||||||
|
echo "Verification:"
|
||||||
|
$COMPOSE exec -T digiserver-app python -c "
|
||||||
|
import sqlite3
|
||||||
|
c = sqlite3.connect('/app/instance/dashboard.db')
|
||||||
|
for t in ['player','playlist','playlist_content']:
|
||||||
|
try:
|
||||||
|
print(' %-18s %d' % (t, c.execute('SELECT COUNT(*) FROM \"%s\"' % t).fetchone()[0]))
|
||||||
|
except Exception as e:
|
||||||
|
print(' %-18s ERROR %s' % (t, e))
|
||||||
|
"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "============================================================"
|
||||||
|
echo " Restore complete: $BACKUP_ID"
|
||||||
|
echo "============================================================"
|
||||||