45bfb8fa5f
Add request counters to Radio 357 backfill logs, clarify RNŚ warnings, and refine responsive dashboard metrics and configuration layout.
233 lines
14 KiB
HTML
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 %}
|