Files
radiosync/templates/index.html
T
karol 45bfb8fa5f Improve sync feedback and dashboard layout
Add request counters to Radio 357 backfill logs, clarify RNŚ warnings, and refine responsive dashboard metrics and configuration layout.
2026-09-03 12:38:56 +02:00

233 lines
14 KiB
HTML

{% extends "base.html" %}
{% block content %}
{% set is_running = (status.radio357.status == 'W trakcie') or (status.rns.status == 'W trakcie') or (status.jazz.status == 'W trakcie') %}
{% if is_running %}
<script>
setTimeout(function() { window.location.reload(); }, 5000);
</script>
{% endif %}
<div class="row">
{% if msg %}
<div class="col-12 mb-3">
<div class="alert alert-info alert-dismissible fade show m-0" role="alert">
<strong>Info:</strong> {{ msg }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
</div>
{% endif %}
<div class="col-12 col-xl-8 mb-4 mb-xl-0">
<div class="mb-4">
<div class="eyebrow mb-2">Panel operacyjny</div>
<h1 class="page-title">Status systemu</h1>
</div>
<div class="row mb-4">
{% for st, name in [('radio357', 'Radio 357'), ('rns', 'Radio Nowy Świat'), ('jazz', 'Radio Jazz')] %}
<div class="col-12 col-lg-4">
<div class="card station-card text-white mb-3 h-100">
<div class="card-header fw-bold">
{{ name }}
</div>
<div class="card-body">
<div class="mb-3">
<span class="metric-label d-block mb-1">Status</span>
{% if status[st].status == 'OK' %}<span class="badge bg-success">OK</span>
{% elif status[st].status == 'Błąd' %}<span class="badge bg-danger">Błąd</span>
{% elif status[st].status == 'W trakcie' %}
<span class="badge bg-primary">W trakcie <span class="spinner-border spinner-border-sm" style="width: 10px; height: 10px;" role="status" aria-hidden="true"></span></span>
{% if status[st].progress %}<br><small class="text-info mt-1 d-block">Zapytania: {{ status[st].progress }}</small>{% endif %}
{% else %}<span class="badge bg-warning text-dark">{{ status[st].status }}</span>{% endif %}
</div>
<div class="row g-3">
<div class="col-12"><span class="metric-label d-block">Ostatni sync</span><span class="metric-value">{% if status[st].last_run %}{{ status[st].last_run.strftime('%d.%m.%Y %H:%M') }}{% else %}Nigdy{% endif %}</span></div>
{% if st in ['radio357', 'rns'] %}
<div class="col-6"><span class="metric-label d-block">Programy</span><span class="metric-value">{{ stats['r357_progs' if st == 'radio357' else 'rns_progs'] }}</span></div>
<div class="col-6"><span class="metric-label d-block">Audio / odcinki</span><span class="metric-value">{{ stats['r357_audio_eps' if st == 'radio357' else 'rns_audio_eps'] }} / {{ stats['r357_eps' if st == 'radio357' else 'rns_eps'] }}</span></div>
{% endif %}
</div>
</div>
<div class="card-footer border-0 bg-transparent d-grid gap-2 pb-3">
{% if st != 'jazz' %}
<form method="POST" action="/test-auth" class="m-0">
<input type="hidden" name="csrf" value="{{ csrf_token }}">
<input type="hidden" name="station" value="{{ st }}">
<button class="btn btn-sm btn-outline-info w-100" title="Sprawdź czy sesja jest aktywna">Sprawdź status</button>
</form>
<form method="POST" action="/force-relogin" class="m-0">
<input type="hidden" name="csrf" value="{{ csrf_token }}">
<input type="hidden" name="station" value="{{ st }}">
<button class="btn btn-sm btn-outline-warning w-100" title="Wymuś nowe logowanie z API" onclick="return confirm('Czy na pewno chcesz wymusić nowe logowanie?')">Relogin</button>
</form>
{% endif %}
{% if status[st].status == 'W trakcie' %}
<form method="POST" action="/stop-sync" class="m-0">
<input type="hidden" name="csrf" value="{{ csrf_token }}">
<input type="hidden" name="station" value="{{ st }}">
<button class="btn btn-sm btn-outline-danger w-100" onclick="return confirm('Zatrzymać synchronizację?')">
<span aria-hidden="true">■</span> Stop
</button>
</form>
{% else %}
<form method="POST" action="/force-sync" class="m-0">
<input type="hidden" name="csrf" value="{{ csrf_token }}">
<input type="hidden" name="station" value="{{ st }}">
<button class="btn btn-sm btn-light w-100">Sync</button>
</form>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
<div class="d-flex justify-content-between align-items-center mt-5 mb-3">
<h3 class="m-0">Błędy wymagające uwagi</h3>
<span class="badge {% if pending_errors %}bg-danger{% else %}bg-success{% endif %}">
{{ pending_errors|length }}
</span>
</div>
{% if pending_errors %}
<div class="list-group mb-4">
{% for error in pending_errors %}
<div class="list-group-item list-group-item-dark bg-secondary text-white {% if error.severity == 'warning' %}border-warning{% else %}border-danger{% endif %} mb-2 rounded">
<div class="d-flex justify-content-between gap-3">
<strong>{% if error.severity == 'warning' %}Ostrzeżenie{% else %}Błąd{% endif %}: {{ error.station }}</strong>
<small>{{ error.last_seen.strftime('%Y-%m-%d %H:%M') }}</small>
</div>
<div class="mt-1">{{ error.message }}</div>
{% if error.source == 'missing_player' and error.message.startswith('Audycja ') %}
{% set program_slug = error.message.split(':', 1)[0].replace('Audycja ', '') %}
<a class="small text-info" href="/episodes/rns/{{ program_slug }}">Otwórz odcinki audycji</a>
{% endif %}
<div class="d-flex justify-content-between align-items-center mt-2">
<small class="text-warning">Powtórzenia: {{ error.occurrences }}</small>
<form method="POST" action="/errors/{{ error.id }}/acknowledge" class="m-0">
<input type="hidden" name="csrf" value="{{ csrf_token }}">
<button class="btn btn-sm btn-outline-light">Potwierdź odczytanie</button>
</form>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="alert alert-success mb-4">Brak błędów wymagających odczytania.</div>
{% endif %}
{% if acknowledged_errors %}
<details class="mb-4">
<summary class="text-muted">Ostatnio odczytane błędy ({{ acknowledged_errors|length }})</summary>
<div class="list-group mt-2">
{% for error in acknowledged_errors %}
<div class="list-group-item list-group-item-dark bg-secondary text-white border-secondary mb-1">
<div class="d-flex justify-content-between gap-3">
<strong>{{ error.station }}</strong>
<small>{{ error.last_seen.strftime('%Y-%m-%d %H:%M') }}</small>
</div>
<div>{{ error.message }}</div>
<small class="text-muted">Powtórzenia: {{ error.occurrences }}</small>
</div>
{% endfor %}
</div>
</details>
{% endif %}
<h3 class="mt-5 mb-3">Ostatnie logi (z pamięci podręcznej)</h3>
<div class="card bg-dark text-light border-secondary">
<div class="card-body">
<div id="log-container" class="log-box" style="max-height: 400px; overflow-y: auto; font-family: monospace; font-size: 0.85rem;">
{% for line in status.logs %}
<div>{{ line }}</div>
{% endfor %}
</div>
</div>
</div>
</div>
<div class="col-12 col-xl-4">
<div class="card config-surface text-white h-100">
<div class="card-header border-secondary">Konfiguracja Systemu</div>
<div class="card-body p-4">
<form method="POST" action="/config">
<input type="hidden" name="csrf" value="{{ csrf_token }}">
<h5 class="mb-3 text-info">Radio 357</h5>
<div class="mb-2">
<input type="email" name="r357_email" class="form-control form-control-sm" placeholder="Email (logowanie)" value="{{ config.r357_email or '' }}">
</div>
<div class="mb-2">
<input type="password" name="r357_password" class="form-control form-control-sm" placeholder="Hasło (zostaw puste aby nie zmieniać)">
</div>
<div class="config-grid mb-4">
<div>
<label class="form-label small text-muted m-0">Sync co (godzin)</label>
<input type="number" name="r357_interval" class="form-control form-control-sm" value="{{ config.r357_interval or '6' }}">
</div>
<div>
<label class="form-label small text-muted m-0">Max odcinków backfill</label>
<input type="number" name="r357_limit" class="form-control form-control-sm" value="{{ config.r357_limit or '600' }}">
</div>
<div>
<label class="form-label small text-muted m-0">Hard limit (all)</label>
<input type="number" name="r357_hard_limit" class="form-control form-control-sm" value="{{ config.r357_hard_limit or '1000' }}">
</div>
</div>
<h5 class="mb-3 text-info">Radio Nowy Świat</h5>
<div class="mb-2">
<input type="email" name="rns_email" class="form-control form-control-sm" placeholder="Email (logowanie)" value="{{ config.rns_email or '' }}">
</div>
<div class="mb-2">
<input type="password" name="rns_password" class="form-control form-control-sm" placeholder="Hasło (zostaw puste aby nie zmieniać)">
</div>
<div class="config-grid mb-4">
<div>
<label class="form-label small text-muted m-0">Sync co (godzin)</label>
<input type="number" name="rns_interval" class="form-control form-control-sm" value="{{ config.rns_interval or '6' }}">
</div>
<div>
<label class="form-label small text-muted m-0">Max stron backfill</label>
<input type="number" name="rns_limit" class="form-control form-control-sm" value="{{ config.rns_limit or '50' }}">
</div>
<div>
<label class="form-label small text-muted m-0">Hard limit (all)</label>
<input type="number" name="rns_hard_limit" class="form-control form-control-sm" value="{{ config.rns_hard_limit or '500' }}">
</div>
</div>
<h5 class="mb-3 text-info">Radio Jazz</h5>
<div class="row g-2 mb-4">
<div class="col-12">
<label class="form-label small text-muted m-0">Sync co (godzin)</label>
<input type="number" name="jazz_interval" class="form-control form-control-sm" value="{{ config.jazz_interval or '24' }}">
</div>
</div>
<h5 class="mb-3 mt-4 text-info">Powiadomienia ntfy.sh</h5>
<div class="mb-2">
<input type="text" name="ntfy_url" class="form-control form-control-sm" placeholder="Adres serwera (np. https://ntfy.sh)" value="{{ config.ntfy_url or '' }}">
</div>
<div class="mb-2">
<input type="text" name="ntfy_topic" class="form-control form-control-sm" placeholder="Temat (Topic)" value="{{ config.ntfy_topic or '' }}">
</div>
<div class="row g-2 mb-4">
<div class="col-6">
<input type="text" name="ntfy_user" class="form-control form-control-sm" placeholder="Użytkownik (opcj.)" value="{{ config.ntfy_user or '' }}">
</div>
<div class="col-6">
<input type="password" name="ntfy_password" class="form-control form-control-sm" placeholder="Hasło (opcj.)">
</div>
</div>
<div class="d-grid mt-4">
<button type="submit" class="btn btn-primary">Zapisz konfigurację</button>
</div>
</form>
<form method="POST" action="/test-ntfy" class="mt-2">
<input type="hidden" name="csrf" value="{{ csrf_token }}">
<button type="submit" class="btn btn-outline-info btn-sm w-100">Testuj powiadomienie ntfy</button>
</form>
</div>
</div>
</div>
</div>
{% endblock %}