338 lines
16 KiB
HTML
338 lines
16 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Backup Management - {{ t.app_title }}{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid mt-4">
|
|
<!-- Header -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-8">
|
|
<nav aria-label="breadcrumb">
|
|
<ol class="breadcrumb">
|
|
<li class="breadcrumb-item">
|
|
<a href="{{ url_for('dashboard') }}">
|
|
<i class="bi bi-house"></i>
|
|
</a>
|
|
</li>
|
|
<li class="breadcrumb-item">
|
|
<a href="{{ url_for('admin_users') }}">Administration</a>
|
|
</li>
|
|
<li class="breadcrumb-item active">Backup Management</li>
|
|
</ol>
|
|
</nav>
|
|
<div class="d-flex align-items-center">
|
|
<h1 class="h3 mb-0">
|
|
<i class="bi bi-archive text-primary"></i>
|
|
Backup Management
|
|
</h1>
|
|
</div>
|
|
<p class="text-muted mb-0">Create and manage system data backups</p>
|
|
</div>
|
|
<div class="col-md-4 text-end">
|
|
<button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#createBackupModal">
|
|
<i class="bi bi-plus-circle"></i>
|
|
Create Backup
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Backup Status -->
|
|
<div class="row mb-4">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">
|
|
<i class="bi bi-speedometer2"></i>
|
|
Backup Status
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-3">
|
|
<div class="d-flex align-items-center">
|
|
<div class="stat-icon bg-primary text-white me-3">
|
|
<i class="bi bi-files"></i>
|
|
</div>
|
|
<div>
|
|
<h5 class="mb-0">{{ backup_status.total_backups or 0 }}</h5>
|
|
<small class="text-muted">Total Backups</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="d-flex align-items-center">
|
|
<div class="stat-icon bg-info text-white me-3">
|
|
<i class="bi bi-hdd"></i>
|
|
</div>
|
|
<div>
|
|
<h5 class="mb-0">{{ "%.1f"|format((backup_status.total_size or 0) / 1024 / 1024) }} MB</h5>
|
|
<small class="text-muted">Total Size</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="d-flex align-items-center">
|
|
<div class="stat-icon bg-success text-white me-3">
|
|
<i class="bi bi-calendar-check"></i>
|
|
</div>
|
|
<div>
|
|
<h5 class="mb-0">
|
|
{% if backup_status.last_backup %}
|
|
{{ backup_status.last_backup.backup_date }}
|
|
{% else %}
|
|
Never
|
|
{% endif %}
|
|
</h5>
|
|
<small class="text-muted">Last Backup</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="d-flex align-items-center">
|
|
<div class="stat-icon bg-warning text-white me-3">
|
|
<i class="bi bi-clock"></i>
|
|
</div>
|
|
<div>
|
|
<h5 class="mb-0">
|
|
{% if backup_status.next_scheduled_backup %}
|
|
{{ backup_status.next_scheduled_backup[:10] }}
|
|
{% else %}
|
|
Disabled
|
|
{% endif %}
|
|
</h5>
|
|
<small class="text-muted">Next Scheduled</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<hr>
|
|
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<h6><i class="bi bi-gear"></i> Configuration</h6>
|
|
<ul class="list-unstyled small">
|
|
<li><strong>Status:</strong>
|
|
{% if backup_status.enabled %}
|
|
<span class="badge bg-success">Enabled</span>
|
|
{% else %}
|
|
<span class="badge bg-danger">Disabled</span>
|
|
{% endif %}
|
|
</li>
|
|
<li><strong>Retention:</strong> {{ backup_status.retention_days or 30 }} days</li>
|
|
<li><strong>Data Path:</strong> <code>{{ backup_status.data_path }}</code></li>
|
|
</ul>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<h6><i class="bi bi-folder"></i> Storage</h6>
|
|
<ul class="list-unstyled small">
|
|
<li><strong>Backup Path:</strong> <code>{{ backup_status.backup_path }}</code></li>
|
|
<li><strong>Available Space:</strong> <span class="text-success">{{ "%.1f"|format((backup_status.total_size or 0) / 1024 / 1024) }} MB used</span></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Backup List -->
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">
|
|
<i class="bi bi-list-ul"></i>
|
|
Available Backups
|
|
{% if backups %}
|
|
<span class="badge bg-primary ms-2">{{ backups|length }}</span>
|
|
{% endif %}
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
{% if backups %}
|
|
<div class="table-responsive">
|
|
<table class="table table-striped table-hover">
|
|
<thead>
|
|
<tr>
|
|
<th>Date</th>
|
|
<th>Time</th>
|
|
<th>Size</th>
|
|
<th>Type</th>
|
|
<th>Status</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for backup in backups %}
|
|
<tr>
|
|
<td>
|
|
<strong>{{ backup.backup_date }}</strong>
|
|
</td>
|
|
<td>
|
|
<small class="text-muted">{{ backup.backup_time or 'Unknown' }}</small>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-light text-dark">
|
|
{{ "%.1f"|format((backup.backup_size or 0) / 1024 / 1024) }} MB
|
|
</span>
|
|
</td>
|
|
<td>
|
|
{% if backup.get('type') == 'manual' %}
|
|
<span class="badge bg-info">Manual</span>
|
|
{% else %}
|
|
<span class="badge bg-secondary">Scheduled</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
{% if backup.get('status') == 'success' %}
|
|
<span class="badge bg-success">
|
|
<i class="bi bi-check-circle"></i>
|
|
Success
|
|
</span>
|
|
{% elif backup.get('status') == 'failed' %}
|
|
<span class="badge bg-danger">
|
|
<i class="bi bi-x-circle"></i>
|
|
Failed
|
|
</span>
|
|
{% else %}
|
|
<span class="badge bg-secondary">Unknown</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<div class="btn-group btn-group-sm" role="group">
|
|
<button type="button" class="btn btn-outline-primary btn-sm"
|
|
data-bs-toggle="tooltip" title="View Details"
|
|
onclick="showBackupDetails('{{ backup.backup_date }}')">
|
|
<i class="bi bi-eye"></i>
|
|
</button>
|
|
<button type="button" class="btn btn-outline-danger btn-sm"
|
|
data-bs-toggle="tooltip" title="Delete Backup"
|
|
onclick="confirmDeleteBackup('{{ backup.backup_date }}')">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center py-4">
|
|
<i class="bi bi-inbox display-4 text-muted"></i>
|
|
<h5 class="mt-3 text-muted">No backups found</h5>
|
|
<p class="text-muted">Create your first backup to get started.</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Create Backup Modal -->
|
|
<div class="modal fade" id="createBackupModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">
|
|
<i class="bi bi-plus-circle"></i>
|
|
Create New Backup
|
|
</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<form method="POST" action="{{ url_for('admin_backup_create') }}">
|
|
<div class="modal-body">
|
|
<div class="mb-3">
|
|
<label for="description" class="form-label">Description</label>
|
|
<input type="text" class="form-control" id="description" name="description"
|
|
placeholder="Enter backup description (optional)">
|
|
<div class="form-text">Optional description for this backup</div>
|
|
</div>
|
|
<div class="alert alert-info">
|
|
<h6><i class="bi bi-info-circle"></i> Backup Information</h6>
|
|
<ul class="mb-0 small">
|
|
<li>This will create a compressed backup of all user data</li>
|
|
<li>The backup will be stored in the backup directory</li>
|
|
<li>Large datasets may take several minutes to backup</li>
|
|
<li>You can continue working during the backup process</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="submit" class="btn btn-success">
|
|
<i class="bi bi-download"></i>
|
|
Create Backup
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Delete Confirmation Modal -->
|
|
<div class="modal fade" id="deleteBackupModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title text-danger">
|
|
<i class="bi bi-exclamation-triangle"></i>
|
|
Confirm Deletion
|
|
</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p>Are you sure you want to delete the backup for <strong id="deleteBackupDate"></strong>?</p>
|
|
<p class="text-danger">
|
|
<i class="bi bi-exclamation-triangle"></i>
|
|
This action cannot be undone. The backup files will be permanently removed.
|
|
</p>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
<form method="POST" id="deleteBackupForm" style="display: inline;">
|
|
<button type="submit" class="btn btn-danger">
|
|
<i class="bi bi-trash"></i>
|
|
Delete Backup
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function showBackupDetails(backupDate) {
|
|
// Could implement a details modal here
|
|
alert('Backup details for ' + backupDate + ' - Feature coming soon!');
|
|
}
|
|
|
|
function confirmDeleteBackup(backupDate) {
|
|
document.getElementById('deleteBackupDate').textContent = backupDate;
|
|
document.getElementById('deleteBackupForm').action = '{{ url_for("admin_backup_delete", backup_date="") }}' + backupDate;
|
|
new bootstrap.Modal(document.getElementById('deleteBackupModal')).show();
|
|
}
|
|
|
|
// Initialize tooltips
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
var tooltips = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
|
tooltips.map(function(tooltipTriggerEl) {
|
|
return new bootstrap.Tooltip(tooltipTriggerEl);
|
|
});
|
|
});
|
|
</script>
|
|
|
|
<style>
|
|
.stat-icon {
|
|
width: 48px;
|
|
height: 48px;
|
|
border-radius: 8px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 1.2rem;
|
|
}
|
|
</style>
|
|
{% endblock %} |