286 Zeilen
12 KiB
HTML
286 Zeilen
12 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>Backup-Verwaltung - Admin Panel</title>
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<style>
|
|
.status-success { color: #28a745; }
|
|
.status-failed { color: #dc3545; }
|
|
.status-in_progress { color: #ffc107; }
|
|
.backup-actions { white-space: nowrap; }
|
|
</style>
|
|
</head>
|
|
<body class="bg-light">
|
|
<nav class="navbar navbar-dark bg-dark">
|
|
<div class="container">
|
|
<span class="navbar-brand">🎛️ Lizenzverwaltung</span>
|
|
<div class="d-flex align-items-center">
|
|
<span class="text-white me-3">Angemeldet als: {{ username }}</span>
|
|
<a href="/logout" class="btn btn-outline-light btn-sm">Abmelden</a>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
|
|
<div class="container py-5">
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h2>💾 Backup-Verwaltung</h2>
|
|
<div>
|
|
<a href="/" class="btn btn-secondary">📊 Dashboard</a>
|
|
<a href="/licenses" class="btn btn-secondary">📋 Lizenzen</a>
|
|
<a href="/customers" class="btn btn-secondary">👥 Kunden</a>
|
|
<a href="/sessions" class="btn btn-secondary">🟢 Sessions</a>
|
|
<a href="/audit" class="btn btn-secondary">📋 Audit</a>
|
|
</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[0].strftime('%d.%m.%Y %H:%M:%S') }}</p>
|
|
<p class="mb-1"><strong>Größe:</strong> {{ (last_backup[1] / 1024 / 1024)|round(2) }} MB</p>
|
|
<p class="mb-0"><strong>Dauer:</strong> {{ last_backup[2]|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">
|
|
<thead>
|
|
<tr>
|
|
<th>Zeitstempel</th>
|
|
<th>Dateiname</th>
|
|
<th>Größe</th>
|
|
<th>Typ</th>
|
|
<th>Status</th>
|
|
<th>Erstellt von</th>
|
|
<th>Details</th>
|
|
<th>Aktionen</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for backup in backups %}
|
|
<tr>
|
|
<td>{{ backup[6].strftime('%d.%m.%Y %H:%M:%S') }}</td>
|
|
<td>
|
|
<small>{{ backup[1] }}</small>
|
|
{% if backup[11] %}
|
|
<span class="badge bg-info ms-1">🔒 Verschlüsselt</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
{% if backup[2] %}
|
|
{{ (backup[2] / 1024 / 1024)|round(2) }} MB
|
|
{% else %}
|
|
-
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
{% if backup[3] == 'manual' %}
|
|
<span class="badge bg-primary">Manuell</span>
|
|
{% else %}
|
|
<span class="badge bg-secondary">Automatisch</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
{% if backup[4] == 'success' %}
|
|
<span class="status-success">✅ Erfolgreich</span>
|
|
{% elif backup[4] == 'failed' %}
|
|
<span class="status-failed" title="{{ backup[5] }}">❌ Fehlgeschlagen</span>
|
|
{% else %}
|
|
<span class="status-in_progress">⏳ In Bearbeitung</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>{{ backup[7] }}</td>
|
|
<td>
|
|
{% if backup[8] and backup[9] %}
|
|
<small>
|
|
{{ backup[8] }} Tabellen<br>
|
|
{{ backup[9] }} Datensätze<br>
|
|
{% if backup[10] %}
|
|
{{ backup[10]|round(1) }}s
|
|
{% endif %}
|
|
</small>
|
|
{% else %}
|
|
-
|
|
{% endif %}
|
|
</td>
|
|
<td class="backup-actions">
|
|
{% if backup[4] == 'success' %}
|
|
<div class="btn-group btn-group-sm" role="group">
|
|
<a href="/backup/download/{{ backup[0] }}"
|
|
class="btn btn-outline-primary"
|
|
title="Backup herunterladen">
|
|
📥 Download
|
|
</a>
|
|
<button class="btn btn-outline-success"
|
|
onclick="restoreBackup({{ backup[0] }}, '{{ backup[1] }}')"
|
|
title="Backup wiederherstellen">
|
|
🔄 Wiederherstellen
|
|
</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>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
|
<script>
|
|
function createBackup() {
|
|
const btn = document.getElementById('createBackupBtn');
|
|
const originalText = btn.innerHTML;
|
|
btn.disabled = true;
|
|
btn.innerHTML = '⏳ Backup wird erstellt...';
|
|
|
|
fetch('/backup/create', {
|
|
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 = '/';
|
|
} else {
|
|
alert('❌ ' + data.message);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
alert('❌ Fehler bei der Wiederherstellung: ' + error);
|
|
})
|
|
.finally(() => {
|
|
document.body.removeChild(loadingDiv);
|
|
});
|
|
}
|
|
</script>
|
|
</body>
|
|
</html> |