297 Zeilen
12 KiB
HTML
297 Zeilen
12 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Backup-Verwaltung{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
.status-success { color: #28a745; }
|
|
.status-failed { color: #dc3545; }
|
|
.status-in_progress { color: #ffc107; }
|
|
.backup-actions { white-space: nowrap; }
|
|
.github-indicator {
|
|
display: inline-block;
|
|
margin-left: 10px;
|
|
}
|
|
.github-indicator.uploaded { color: #28a745; }
|
|
.github-indicator.not-uploaded { color: #6c757d; }
|
|
.backup-type-badge {
|
|
margin-left: 5px;
|
|
}
|
|
.recent-backups {
|
|
background-color: #f8f9fa;
|
|
padding: 20px;
|
|
border-radius: 8px;
|
|
margin-bottom: 30px;
|
|
}
|
|
.backup-card {
|
|
transition: transform 0.2s;
|
|
}
|
|
.backup-card:hover {
|
|
transform: translateY(-2px);
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container py-5">
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h2>💾 Backup-Verwaltung</h2>
|
|
<div>
|
|
<span class="text-muted">Automatische Backups: Täglich um 03:00 Uhr</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Letzte 3 GitHub-Backups prominent anzeigen -->
|
|
<div class="recent-backups">
|
|
<h4 class="mb-3">🌟 Letzte GitHub-Backups</h4>
|
|
<div class="row">
|
|
{% set github_uploaded_backups = backups | selectattr('github_uploaded', 'eq', True) | selectattr('status', 'eq', 'success') | list %}
|
|
{% for backup in github_uploaded_backups[:3] %}
|
|
<div class="col-md-4 mb-3">
|
|
<div class="card backup-card h-100">
|
|
<div class="card-body">
|
|
<h6 class="card-title">
|
|
{% if backup.is_server_backup %}
|
|
🖥️ Server-Backup
|
|
{% else %}
|
|
💾 Datenbank-Backup
|
|
{% endif %}
|
|
</h6>
|
|
<p class="mb-1"><small>{{ backup.created_at.strftime('%d.%m.%Y %H:%M') }}</small></p>
|
|
<p class="mb-1"><small>{{ (backup.filesize / 1024 / 1024)|round(2) }} MB</small></p>
|
|
<div class="mt-2">
|
|
<button class="btn btn-sm btn-primary" onclick="downloadFromGitHub({{ backup.id }})">
|
|
<i class="fas fa-download"></i> Download
|
|
</button>
|
|
{% if backup.is_server_backup %}
|
|
<button class="btn btn-sm btn-warning" onclick="restoreServer({{ backup.id }})">
|
|
<i class="fas fa-undo"></i> Restore
|
|
</button>
|
|
{% else %}
|
|
<button class="btn btn-sm btn-warning" onclick="showRestoreModal({{ backup.id }})">
|
|
<i class="fas fa-undo"></i> Restore
|
|
</button>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<div class="col-12">
|
|
<p class="text-muted text-center">Noch keine GitHub-Backups vorhanden</p>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Backup-Info -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-4">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<h5 class="card-title">📊 Backup-Statistiken</h5>
|
|
{% set total_backups = backups | selectattr('github_uploaded', 'eq', True) | list | length %}
|
|
{% set db_backups = backups | selectattr('github_uploaded', 'eq', True) | selectattr('is_server_backup', 'eq', False) | list | length %}
|
|
{% set server_backups = backups | selectattr('github_uploaded', 'eq', True) | selectattr('is_server_backup', 'eq', True) | list | length %}
|
|
<p class="mb-1"><strong>Gesamt:</strong> {{ total_backups }} Backups</p>
|
|
<p class="mb-1"><strong>Datenbank:</strong> {{ db_backups }}</p>
|
|
<p class="mb-0"><strong>Server:</strong> {{ server_backups }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<h5 class="card-title">💿 Speicherplatz</h5>
|
|
{% set local_size = backups | selectattr('local_deleted', 'eq', False) | selectattr('file_exists', 'eq', True) | sum(attribute='filesize') %}
|
|
<p class="mb-1"><strong>Lokal:</strong> {{ (local_size / 1024 / 1024)|round(2) }} MB</p>
|
|
<p class="mb-0"><small class="text-muted">GitHub: Unbegrenzt</small></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<h5 class="card-title">🔧 Backup erstellen</h5>
|
|
<div class="d-grid gap-2">
|
|
<button class="btn btn-primary" onclick="createBackup('database')">
|
|
💾 Datenbank-Backup
|
|
</button>
|
|
<button class="btn btn-success" onclick="createBackup('server')">
|
|
🖥️ Server-Backup
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Alle GitHub Backups -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="mb-0"><i class="fab fa-github"></i> Alle GitHub-Backups</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover">
|
|
<thead>
|
|
<tr>
|
|
<th>Typ</th>
|
|
<th>Erstellt</th>
|
|
<th>Größe</th>
|
|
<th>Status</th>
|
|
<th>Erstellt von</th>
|
|
<th>Aktionen</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for backup in github_uploaded_backups %}
|
|
<tr>
|
|
<td>
|
|
{% if backup.is_server_backup %}
|
|
<span class="badge bg-success">🖥️ Server</span>
|
|
{% else %}
|
|
<span class="badge bg-primary">💾 DB</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>{{ backup.created_at.strftime('%d.%m.%Y %H:%M:%S') }}</td>
|
|
<td>{{ (backup.filesize / 1024 / 1024)|round(2) }} MB</td>
|
|
<td>
|
|
<span class="status-{{ backup.status }}">
|
|
{% if backup.status == 'success' %}✅{% else %}❌{% endif %}
|
|
{{ backup.status }}
|
|
</span>
|
|
</td>
|
|
<td>{{ backup.created_by or 'system' }}</td>
|
|
<td class="backup-actions">
|
|
<button class="btn btn-sm btn-primary" onclick="downloadFromGitHub({{ backup.id }})" title="Von GitHub herunterladen">
|
|
<i class="fas fa-download"></i>
|
|
</button>
|
|
{% if backup.is_server_backup %}
|
|
<button class="btn btn-sm btn-warning" onclick="restoreServer({{ backup.id }})" title="Server wiederherstellen">
|
|
<i class="fas fa-undo"></i>
|
|
</button>
|
|
{% else %}
|
|
<button class="btn btn-sm btn-warning" onclick="showRestoreModal({{ backup.id }})" title="Datenbank wiederherstellen">
|
|
<i class="fas fa-database"></i>
|
|
</button>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Restore Modal für Datenbank -->
|
|
<div class="modal fade" id="restoreModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Backup wiederherstellen</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p>⚠️ <strong>Warnung:</strong> Alle aktuellen Daten werden überschrieben!</p>
|
|
<div class="mb-3">
|
|
<label class="form-label">Verschlüsselungsschlüssel (falls benötigt):</label>
|
|
<input type="password" class="form-control" id="encryptionKey" placeholder="Leer lassen für Standard-Schlüssel">
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
|
|
<button type="button" class="btn btn-danger" onclick="confirmRestore()">Wiederherstellen</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script>
|
|
let selectedBackupId = null;
|
|
|
|
function createBackup(type) {
|
|
const btn = event.target;
|
|
btn.disabled = true;
|
|
btn.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Erstelle...';
|
|
|
|
fetch('/admin/backup/create', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
type: type,
|
|
push_to_github: true,
|
|
delete_local: true // Automatisch lokal löschen
|
|
})
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
alert('Backup wurde erfolgreich erstellt und zu GitHub hochgeladen!');
|
|
location.reload();
|
|
} else {
|
|
alert('Fehler: ' + data.error);
|
|
btn.disabled = false;
|
|
btn.innerHTML = type === 'database' ? '💾 Datenbank-Backup' : '🖥️ Server-Backup';
|
|
}
|
|
})
|
|
.catch(error => {
|
|
alert('Fehler beim Erstellen des Backups: ' + error);
|
|
btn.disabled = false;
|
|
btn.innerHTML = type === 'database' ? '💾 Datenbank-Backup' : '🖥️ Server-Backup';
|
|
});
|
|
}
|
|
|
|
function downloadFromGitHub(backupId) {
|
|
window.location.href = `/admin/backup/download/${backupId}?from_github=true`;
|
|
}
|
|
|
|
function showRestoreModal(backupId) {
|
|
selectedBackupId = backupId;
|
|
const modal = new bootstrap.Modal(document.getElementById('restoreModal'));
|
|
modal.show();
|
|
}
|
|
|
|
function confirmRestore() {
|
|
const encryptionKey = document.getElementById('encryptionKey').value;
|
|
|
|
fetch(`/admin/backup/restore/${selectedBackupId}`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
encryption_key: encryptionKey
|
|
})
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
alert('Backup wurde erfolgreich wiederhergestellt!');
|
|
location.reload();
|
|
} else {
|
|
alert('Fehler beim Wiederherstellen: ' + data.error);
|
|
}
|
|
});
|
|
|
|
bootstrap.Modal.getInstance(document.getElementById('restoreModal')).hide();
|
|
}
|
|
|
|
function restoreServer(backupId) {
|
|
if (!confirm('⚠️ WARNUNG: Dies wird den kompletten Server-Zustand wiederherstellen!\n\nAlle aktuellen Konfigurationen, Datenbanken und Docker-Volumes werden überschrieben.\n\nSind Sie sicher?')) {
|
|
return;
|
|
}
|
|
|
|
alert('Server-Restore muss derzeit manuell über SSH durchgeführt werden:\n\n' +
|
|
'1. SSH zum Server verbinden\n' +
|
|
'2. Backup von GitHub herunterladen\n' +
|
|
'3. ./restore_full_backup.sh <backup_name> ausführen');
|
|
}
|
|
</script>
|
|
{% endblock %} |