- Auto WLED config backup daily at 02:05 (keeps last 14 per device) - Backup page: WLED configs grouped by hostname in accordion - Each device shows backup count and last backup date - Collapsible sections to keep page compact - Readable date format (dd.mm.yyyy HH:MM:SS) for all backups - Scrollable containers for long lists Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
233 lines
13 KiB
HTML
233 lines
13 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Backup - Busylight{% endblock %}
|
|
|
|
{% block content %}
|
|
<h4 class="mb-4">Backup</h4>
|
|
|
|
{% if request.query_params.get('db_created') %}
|
|
<div class="alert alert-success alert-dismissible fade show">
|
|
<i class="bi bi-check-circle"></i> Portal-Backup erstellt.
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
</div>
|
|
{% endif %}
|
|
{% if request.query_params.get('wled_backed_up') %}
|
|
<div class="alert alert-success alert-dismissible fade show">
|
|
<i class="bi bi-check-circle"></i> {{ request.query_params.get('wled_backed_up') }} Tuerschild-Config(s) gesichert.
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
</div>
|
|
{% endif %}
|
|
{% if request.query_params.get('restored') == '1' %}
|
|
<div class="alert alert-success alert-dismissible fade show">
|
|
<i class="bi bi-check-circle"></i> WLED-Config erfolgreich wiederhergestellt.
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
</div>
|
|
{% elif request.query_params.get('restored') == '0' %}
|
|
<div class="alert alert-danger alert-dismissible fade show">
|
|
<i class="bi bi-x-circle"></i> WLED-Config konnte nicht wiederhergestellt werden.
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="row g-4">
|
|
<!-- Portal DB Backup -->
|
|
<div class="col-lg-6">
|
|
<div class="card">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h6 class="mb-0"><i class="bi bi-database"></i> Portal-Datenbank</h6>
|
|
<div>
|
|
<a href="/api/backup/db/download" class="btn btn-sm btn-outline-primary">
|
|
<i class="bi bi-download"></i> Aktuelle DB
|
|
</a>
|
|
<form method="post" action="/api/backup/db/create" class="d-inline">
|
|
<button type="submit" class="btn btn-sm btn-primary">
|
|
<i class="bi bi-plus-circle"></i> Backup erstellen
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<div class="table-responsive" style="max-height: 400px; overflow-y: auto;">
|
|
<table class="table table-hover table-sm mb-0">
|
|
<thead class="sticky-top bg-white">
|
|
<tr>
|
|
<th>Datum</th>
|
|
<th>Groesse</th>
|
|
<th>Aktionen</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for b in db_backups %}
|
|
<tr>
|
|
<td><small>{{ b.date }}</small></td>
|
|
<td><small>{{ b.size }}</small></td>
|
|
<td>
|
|
<a href="/api/backup/db/{{ b.name }}" class="btn btn-sm btn-outline-primary py-0" title="Download">
|
|
<i class="bi bi-download"></i>
|
|
</a>
|
|
<form method="post" action="/api/backup/db/{{ b.name }}/delete" class="d-inline"
|
|
onsubmit="return confirm('Backup loeschen?')">
|
|
<button type="submit" class="btn btn-sm btn-outline-danger py-0" title="Loeschen">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</form>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
{% if not db_backups %}
|
|
<tr>
|
|
<td colspan="3" class="text-center text-muted py-3">Noch keine Backups vorhanden</td>
|
|
</tr>
|
|
{% endif %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<div class="card-footer text-muted">
|
|
<small><i class="bi bi-clock"></i> Auto-Backup taeglich um 02:00 Uhr (max. 14 Backups)</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- WLED Config Backup -->
|
|
<div class="col-lg-6">
|
|
<div class="card">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h6 class="mb-0"><i class="bi bi-router"></i> Tuerschild-Konfigurationen</h6>
|
|
<div>
|
|
<form method="post" action="/api/backup/wled/all" class="d-inline">
|
|
<button type="submit" class="btn btn-sm btn-primary">
|
|
<i class="bi bi-download"></i> Alle sichern
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<!-- Einzelnes Tuerschild sichern -->
|
|
<div class="p-3 border-bottom bg-light">
|
|
<form class="d-flex gap-2 align-items-end">
|
|
<div class="flex-grow-1">
|
|
<label class="form-label small text-muted mb-1">Einzelnes Tuerschild sichern</label>
|
|
<select class="form-select form-select-sm" id="backup-device-select">
|
|
{% for d in devices %}
|
|
<option value="{{ d.id }}">{{ d.name or d.hostname }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<button type="button" class="btn btn-sm btn-outline-primary" onclick="backupSingle()">
|
|
<i class="bi bi-download"></i> Sichern
|
|
</button>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Backups per Device, collapsible -->
|
|
<div class="accordion accordion-flush" id="wled-backups" style="max-height: 500px; overflow-y: auto;">
|
|
{% for hostname, backups in wled_by_device.items() %}
|
|
<div class="accordion-item">
|
|
<h2 class="accordion-header">
|
|
<button class="accordion-button collapsed py-2" type="button"
|
|
data-bs-toggle="collapse" data-bs-target="#wled-{{ loop.index }}">
|
|
<div class="d-flex justify-content-between align-items-center w-100 me-2">
|
|
<span>
|
|
<code class="me-2">{{ hostname }}</code>
|
|
<span class="badge bg-secondary">{{ backups|length }}</span>
|
|
</span>
|
|
<small class="text-muted">Letztes: {{ backups[0].date }}</small>
|
|
</div>
|
|
</button>
|
|
</h2>
|
|
<div id="wled-{{ loop.index }}" class="accordion-collapse collapse" data-bs-parent="#wled-backups">
|
|
<div class="accordion-body p-0">
|
|
<table class="table table-sm table-hover mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th>Datum</th>
|
|
<th>Groesse</th>
|
|
<th>Aktionen</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for b in backups %}
|
|
<tr>
|
|
<td><small>{{ b.date }}</small></td>
|
|
<td><small>{{ b.size }}</small></td>
|
|
<td>
|
|
<a href="/api/backup/wled/download/{{ b.name }}" class="btn btn-sm btn-outline-primary py-0" title="Download">
|
|
<i class="bi bi-download"></i>
|
|
</a>
|
|
<button class="btn btn-sm btn-outline-warning py-0" title="Wiederherstellen"
|
|
data-bs-toggle="modal" data-bs-target="#restore-{{ loop.parent.loop.index }}-{{ loop.index }}">
|
|
<i class="bi bi-arrow-counterclockwise"></i>
|
|
</button>
|
|
<form method="post" action="/api/backup/wled/delete/{{ b.name }}" class="d-inline"
|
|
onsubmit="return confirm('Backup loeschen?')">
|
|
<button type="submit" class="btn btn-sm btn-outline-danger py-0" title="Loeschen">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</form>
|
|
</td>
|
|
</tr>
|
|
<!-- Restore Modal -->
|
|
<div class="modal fade" id="restore-{{ loop.parent.loop.index }}-{{ loop.index }}" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<form method="post" action="/api/backup/wled/restore/{{ b.name }}">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Config wiederherstellen</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p><strong>{{ hostname }}</strong> vom <strong>{{ b.date }}</strong></p>
|
|
<p>Auf welches Tuerschild uebertragen?</p>
|
|
<select name="device_id" class="form-select">
|
|
{% for d in devices %}
|
|
<option value="{{ d.id }}">{{ d.name or d.hostname }} ({{ d.hostname }})</option>
|
|
{% endfor %}
|
|
</select>
|
|
<div class="alert alert-warning mt-3 mb-0">
|
|
<small><i class="bi bi-exclamation-triangle"></i> Das Tuerschild startet nach dem Restore neu!</small>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
|
|
<button type="submit" class="btn btn-warning">Wiederherstellen</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
|
|
{% if not wled_by_device %}
|
|
<div class="p-4 text-center text-muted">
|
|
Noch keine Config-Backups vorhanden
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<div class="card-footer text-muted">
|
|
<small><i class="bi bi-clock"></i> Auto-Backup taeglich um 02:05 Uhr (max. 14 pro Tuerschild)</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
function backupSingle() {
|
|
const id = document.getElementById('backup-device-select').value;
|
|
const form = document.createElement('form');
|
|
form.method = 'POST';
|
|
form.action = '/api/backup/wled/' + id;
|
|
document.body.appendChild(form);
|
|
form.submit();
|
|
}
|
|
</script>
|
|
{% endblock %}
|