feat: add comprehensive GitHub workflow and development tools
This commit is contained in:
262
app/templates/stiftung/backup_management.html
Normal file
262
app/templates/stiftung/backup_management.html
Normal file
@@ -0,0 +1,262 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load humanize %}
|
||||
|
||||
{% block title %}Backup & Restore - Administration - van Hees-Theyssen-Vogel'sche Stiftung{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="d-sm-flex align-items-center justify-content-between mb-4">
|
||||
<h1 class="h3 mb-0 text-gray-800">
|
||||
<i class="fas fa-database me-2"></i>Backup & Restore
|
||||
</h1>
|
||||
<a href="{% url 'stiftung:administration' %}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-arrow-left me-1"></i>Zurück zur Administration
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Backup Erstellung -->
|
||||
<div class="col-lg-6">
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-success">
|
||||
<i class="fas fa-plus me-2"></i>Neues Backup erstellen
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Backup-Typ</label>
|
||||
<select class="form-select" name="backup_type" required>
|
||||
{% for value, display in backup_types %}
|
||||
<option value="{{ value }}">{{ display }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<div class="form-text">
|
||||
<strong>Vollständiges Backup:</strong> Datenbank + Dateien + Konfiguration<br>
|
||||
<strong>Nur Datenbank:</strong> PostgreSQL Dump<br>
|
||||
<strong>Nur Dateien:</strong> Uploads und Konfigurationsdateien
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="fas fa-play me-1"></i>Backup starten
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Restore -->
|
||||
<div class="col-lg-6">
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-warning">
|
||||
<i class="fas fa-upload me-2"></i>Wiederherstellung
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="alert alert-warning">
|
||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||
<strong>Warnung:</strong> Die Wiederherstellung überschreibt alle aktuellen Daten!
|
||||
</div>
|
||||
<form method="post" action="{% url 'stiftung:backup_restore' %}" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Backup-Datei auswählen</label>
|
||||
<input type="file" class="form-control" name="backup_file" accept=".tar.gz" required>
|
||||
<div class="form-text">
|
||||
Nur .tar.gz Dateien von diesem System sind erlaubt.
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-warning" onclick="return confirm('Sind Sie sicher? Diese Aktion überschreibt alle aktuellen Daten!')">
|
||||
<i class="fas fa-undo me-1"></i>Wiederherstellung starten
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Backup Historie -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card shadow">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">
|
||||
<i class="fas fa-history me-2"></i>Backup Historie
|
||||
{% if page_obj %}
|
||||
<span class="badge bg-primary ms-2">{{ page_obj.paginator.count }}</span>
|
||||
{% endif %}
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if page_obj %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Erstellt</th>
|
||||
<th>Typ</th>
|
||||
<th>Status</th>
|
||||
<th>Größe</th>
|
||||
<th>Dauer</th>
|
||||
<th>Erstellt von</th>
|
||||
<th>Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for backup in page_obj %}
|
||||
<tr>
|
||||
<td>
|
||||
<div>{{ backup.created_at|date:"d.m.Y" }}</div>
|
||||
<small class="text-muted">{{ backup.created_at|date:"H:i:s" }}</small>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-info">{{ backup.get_backup_type_display }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-{% if backup.status == 'completed' %}success{% elif backup.status == 'failed' %}danger{% elif backup.status == 'running' %}primary{% else %}secondary{% endif %}">
|
||||
{{ backup.get_status_display }}
|
||||
</span>
|
||||
{% if backup.status == 'running' %}
|
||||
<br><small class="text-muted">
|
||||
{% if backup.get_duration %}
|
||||
Läuft seit {{ backup.get_duration.total_seconds|floatformat:0 }}s
|
||||
{% endif %}
|
||||
</small>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if backup.backup_size %}
|
||||
{{ backup.get_size_display }}
|
||||
{% else %}
|
||||
<span class="text-muted">-</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if backup.get_duration %}
|
||||
{{ backup.get_duration.total_seconds|floatformat:1 }}s
|
||||
{% else %}
|
||||
<span class="text-muted">-</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if backup.created_by %}
|
||||
<span class="badge bg-secondary">{{ backup.created_by.username }}</span>
|
||||
{% else %}
|
||||
<span class="text-muted">System</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if backup.status == 'completed' %}
|
||||
<a href="{% url 'stiftung:backup_download' backup.id %}" class="btn btn-outline-primary btn-sm" title="Herunterladen">
|
||||
<i class="fas fa-download"></i>
|
||||
</a>
|
||||
{% elif backup.status == 'failed' %}
|
||||
<button class="btn btn-outline-danger btn-sm" onclick="showError('{{ backup.error_message|escapejs }}')" title="Fehler anzeigen">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
</button>
|
||||
{% elif backup.status == 'running' %}
|
||||
<button class="btn btn-outline-primary btn-sm" disabled title="Läuft...">
|
||||
<i class="fas fa-spinner fa-spin"></i>
|
||||
</button>
|
||||
{% else %}
|
||||
<span class="text-muted">-</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
{% if page_obj.has_other_pages %}
|
||||
<nav aria-label="Backup Pagination">
|
||||
<ul class="pagination justify-content-center">
|
||||
{% if page_obj.has_previous %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.previous_page_number }}">Vorherige</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% for num in page_obj.paginator.page_range %}
|
||||
{% if page_obj.number == num %}
|
||||
<li class="page-item active">
|
||||
<span class="page-link">{{ num }}</span>
|
||||
</li>
|
||||
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ num }}">{{ num }}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if page_obj.has_next %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.next_page_number }}">Nächste</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="text-center py-5">
|
||||
<i class="fas fa-database fa-3x text-muted mb-3"></i>
|
||||
<h5 class="text-muted">Keine Backups vorhanden</h5>
|
||||
<p class="text-muted">Erstellen Sie Ihr erstes Backup über das Formular oben.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Error Modal -->
|
||||
<div class="modal fade" id="errorModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Backup Fehler</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div id="errorContent"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Schließen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
function showError(errorMessage) {
|
||||
const modal = new bootstrap.Modal(document.getElementById('errorModal'));
|
||||
document.getElementById('errorContent').innerHTML = `
|
||||
<div class="alert alert-danger">
|
||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||
${errorMessage}
|
||||
</div>
|
||||
`;
|
||||
modal.show();
|
||||
}
|
||||
|
||||
// Auto-refresh for running backups
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const runningBackups = document.querySelectorAll('.fa-spinner');
|
||||
if (runningBackups.length > 0) {
|
||||
// Refresh page every 10 seconds if there are running backups
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 10000);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user