Files
Claude Project Manager 0d7d888502 Initial commit
2025-07-05 17:51:16 +02:00

301 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; }
</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>
</div>
</div>
<!-- Backup-Info -->
<div class="row mb-4">
<div class="col-md-6">
<div class="card">
<div class="card-body">
<h5 class="card-title">📅 Letztes erfolgreiches Backup</h5>
{% if last_backup %}
<p class="mb-1"><strong>Zeitpunkt:</strong> {{ last_backup.id.strftime('%d.%m.%Y %H:%M:%S') }}</p>
<p class="mb-1"><strong>Größe:</strong> {{ (last_backup.filename / 1024 / 1024)|round(2) }} MB</p>
<p class="mb-0"><strong>Dauer:</strong> {{ last_backup.filesize|round(1) }} Sekunden</p>
{% else %}
<p class="text-muted mb-0">Noch kein Backup vorhanden</p>
{% endif %}
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-body">
<h5 class="card-title">🔧 Backup-Aktionen</h5>
<button id="createBackupBtn" class="btn btn-primary" onclick="createBackup()">
💾 Backup jetzt erstellen
</button>
<p class="text-muted mt-2 mb-0">
<small>Automatische Backups: Täglich um 03:00 Uhr</small>
</p>
</div>
</div>
</div>
</div>
<!-- Backup-Historie -->
<div class="card">
<div class="card-header">
<h5 class="mb-0">📋 Backup-Historie</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover sortable-table">
<thead>
<tr>
<th class="sortable" data-type="date">Zeitstempel</th>
<th class="sortable">Dateiname</th>
<th class="sortable" data-type="numeric">Größe</th>
<th class="sortable">Typ</th>
<th class="sortable">Status</th>
<th class="sortable">Erstellt von</th>
<th>Details</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
{% for backup in backups %}
<tr>
<td>{{ backup.created_at.strftime('%d.%m.%Y %H:%M:%S') }}</td>
<td>
<small>{{ backup.filename }}</small>
{% if backup.is_encrypted %}
<span class="badge bg-info ms-1">🔒 Verschlüsselt</span>
{% endif %}
</td>
<td>
{% if backup.filesize %}
{{ (backup.filesize / 1024 / 1024)|round(2) }} MB
{% else %}
-
{% endif %}
</td>
<td>
{% if backup.backup_type == 'manual' %}
<span class="badge bg-primary">Manuell</span>
{% else %}
<span class="badge bg-secondary">Automatisch</span>
{% endif %}
</td>
<td>
{% if backup.status == 'success' %}
<span class="status-success">✅ Erfolgreich</span>
{% elif backup.status == 'failed' %}
<span class="status-failed" title="{{ backup.error_message }}">❌ Fehlgeschlagen</span>
{% else %}
<span class="status-in_progress">⏳ In Bearbeitung</span>
{% endif %}
</td>
<td>{{ backup.created_by }}</td>
<td>
{% if backup.tables_count and backup.records_count %}
<small>
{{ backup.tables_count }} Tabellen<br>
{{ backup.records_count }} Datensätze<br>
{% if backup.duration_seconds %}
{{ backup.duration_seconds|round(1) }}s
{% endif %}
</small>
{% else %}
-
{% endif %}
</td>
<td class="backup-actions">
{% if backup.status == 'success' %}
<div class="btn-group btn-group-sm" role="group">
<a href="{{ url_for('admin.download_backup', backup_id=backup.id) }}"
class="btn btn-outline-primary"
title="Backup herunterladen">
📥 Download
</a>
<button class="btn btn-outline-success"
onclick="restoreBackup({{ backup.id }}, '{{ backup.filename }}')"
title="Backup wiederherstellen">
🔄 Wiederherstellen
</button>
<button class="btn btn-outline-danger"
onclick="deleteBackup({{ backup.id }}, '{{ backup.filename }}')"
title="Backup löschen">
🗑️ Löschen
</button>
</div>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if not backups %}
<div class="text-center py-5">
<p class="text-muted">Noch keine Backups vorhanden.</p>
</div>
{% endif %}
</div>
</div>
</div>
</div>
<!-- Wiederherstellungs-Modal -->
<div class="modal fade" id="restoreModal" tabindex="-1" aria-hidden="true">
<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">
<div class="alert alert-warning">
<strong>⚠️ Warnung:</strong> Bei der Wiederherstellung werden alle aktuellen Daten überschrieben!
</div>
<p>Backup: <strong id="restoreFilename"></strong></p>
<form id="restoreForm">
<input type="hidden" id="restoreBackupId">
<div class="mb-3">
<label for="encryptionKey" class="form-label">Verschlüsselungs-Passwort (optional)</label>
<input type="password" class="form-control" id="encryptionKey"
placeholder="Leer lassen für Standard-Passwort">
<small class="text-muted">
Falls das Backup mit einem anderen Passwort verschlüsselt wurde
</small>
</div>
</form>
</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>
function createBackup() {
const btn = document.getElementById('createBackupBtn');
const originalText = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '⏳ Backup wird erstellt...';
fetch('{{ url_for('admin.create_backup_route') }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('✅ ' + data.message);
location.reload();
} else {
alert('❌ ' + data.message);
}
})
.catch(error => {
alert('❌ Fehler beim Erstellen des Backups: ' + error);
})
.finally(() => {
btn.disabled = false;
btn.innerHTML = originalText;
});
}
function restoreBackup(backupId, filename) {
document.getElementById('restoreBackupId').value = backupId;
document.getElementById('restoreFilename').textContent = filename;
document.getElementById('encryptionKey').value = '';
const modal = new bootstrap.Modal(document.getElementById('restoreModal'));
modal.show();
}
function confirmRestore() {
if (!confirm('Wirklich wiederherstellen? Alle aktuellen Daten werden überschrieben!')) {
return;
}
const backupId = document.getElementById('restoreBackupId').value;
const encryptionKey = document.getElementById('encryptionKey').value;
const formData = new FormData();
if (encryptionKey) {
formData.append('encryption_key', encryptionKey);
}
// Modal schließen
bootstrap.Modal.getInstance(document.getElementById('restoreModal')).hide();
// Loading anzeigen
const loadingDiv = document.createElement('div');
loadingDiv.className = 'position-fixed top-50 start-50 translate-middle';
loadingDiv.innerHTML = '<div class="spinner-border text-primary" role="status"><span class="visually-hidden">Loading...</span></div>';
document.body.appendChild(loadingDiv);
fetch(`/backup/restore/${backupId}`, {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('✅ ' + data.message + '\n\nDie Seite wird neu geladen...');
window.location.href = '{{ url_for('admin.dashboard') }}';
} else {
alert('❌ ' + data.message);
}
})
.catch(error => {
alert('❌ Fehler bei der Wiederherstellung: ' + error);
})
.finally(() => {
document.body.removeChild(loadingDiv);
});
}
function deleteBackup(backupId, filename) {
if (!confirm(`Soll das Backup "${filename}" wirklich gelöscht werden?\n\nDieser Vorgang kann nicht rückgängig gemacht werden!`)) {
return;
}
fetch(`/backup/delete/${backupId}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('✅ ' + data.message);
location.reload();
} else {
alert('❌ ' + data.message);
}
})
.catch(error => {
alert('❌ Fehler beim Löschen des Backups: ' + error);
});
}
</script>
{% endblock %}