hospital-management/templates/core/backup_restore.html
2025-08-12 13:33:25 +03:00

569 lines
25 KiB
HTML

{% extends 'base.html' %}
{% load static %}
{% block title %}Backup & Restore{% endblock %}
{% block css %}
<link href="{% static 'assets/plugins/datatables.net-bs5/css/dataTables.bootstrap5.min.css' %}" rel="stylesheet" />
{% endblock %}
{% block content %}
<div id="content" class="app-content">
<div class="container">
<ul class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Dashboard</a></li>
<li class="breadcrumb-item active">Backup & Restore</li>
</ul>
<div class="row align-items-center mb-3">
<div class="col">
<h1 class="page-header">Backup & Restore</h1>
<p class="text-muted">Manage system backups and data recovery operations</p>
</div>
<div class="col-auto">
<button type="button" class="btn btn-primary" onclick="createBackup()">
<i class="fa fa-plus me-2"></i>Create Backup
</button>
</div>
</div>
<!-- Backup Status -->
<div class="row mb-4">
<div class="col-md-3">
<div class="card text-center">
<div class="card-body">
<div class="w-60px h-60px bg-primary bg-opacity-20 d-flex align-items-center justify-content-center rounded-circle mx-auto mb-3">
<i class="fa fa-database fa-2x text-primary"></i>
</div>
<h5>Total Backups</h5>
<div class="fs-24px fw-600 text-primary">{{ total_backups }}</div>
<div class="text-muted small">All time</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center">
<div class="card-body">
<div class="w-60px h-60px bg-success bg-opacity-20 d-flex align-items-center justify-content-center rounded-circle mx-auto mb-3">
<i class="fa fa-check-circle fa-2x text-success"></i>
</div>
<h5>Last Backup</h5>
<div class="fs-16px fw-600 text-success">{{ last_backup_date|date:"M d, Y" }}</div>
<div class="text-muted small">{{ last_backup_time_ago }}</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center">
<div class="card-body">
<div class="w-60px h-60px bg-info bg-opacity-20 d-flex align-items-center justify-content-center rounded-circle mx-auto mb-3">
<i class="fa fa-hdd fa-2x text-info"></i>
</div>
<h5>Storage Used</h5>
<div class="fs-20px fw-600 text-info">{{ total_backup_size }}</div>
<div class="text-muted small">{{ available_space }} available</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center">
<div class="card-body">
<div class="w-60px h-60px bg-warning bg-opacity-20 d-flex align-items-center justify-content-center rounded-circle mx-auto mb-3">
<i class="fa fa-clock fa-2x text-warning"></i>
</div>
<h5>Next Scheduled</h5>
<div class="fs-16px fw-600 text-warning">{{ next_backup_date|date:"M d, Y" }}</div>
<div class="text-muted small">{{ next_backup_time|date:"g:i A" }}</div>
</div>
</div>
</div>
</div>
<!-- Backup Configuration -->
<div class="row mb-4">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h4 class="card-title">Backup Configuration</h4>
</div>
<div class="card-body">
<form id="backupConfigForm">
{% csrf_token %}
<div class="mb-3">
<label class="form-label">Automatic Backups</label>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" name="auto_backup_enabled"
{% if backup_config.auto_backup_enabled %}checked{% endif %}>
<label class="form-check-label">Enable automatic backups</label>
</div>
</div>
<div class="mb-3">
<label class="form-label">Backup Frequency</label>
<select name="backup_frequency" class="form-select">
<option value="daily" {% if backup_config.frequency == 'daily' %}selected{% endif %}>Daily</option>
<option value="weekly" {% if backup_config.frequency == 'weekly' %}selected{% endif %}>Weekly</option>
<option value="monthly" {% if backup_config.frequency == 'monthly' %}selected{% endif %}>Monthly</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Backup Time</label>
<input type="time" name="backup_time" class="form-control"
value="{{ backup_config.backup_time|default:'02:00' }}">
</div>
<div class="mb-3">
<label class="form-label">Retention Period</label>
<div class="input-group">
<input type="number" name="retention_days" class="form-control"
value="{{ backup_config.retention_days|default:30 }}" min="7" max="365">
<span class="input-group-text">days</span>
</div>
</div>
<div class="mb-3">
<label class="form-label">Backup Types</label>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="include_database"
{% if backup_config.include_database %}checked{% endif %}>
<label class="form-check-label">Database</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="include_media"
{% if backup_config.include_media %}checked{% endif %}>
<label class="form-check-label">Media files</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="include_logs"
{% if backup_config.include_logs %}checked{% endif %}>
<label class="form-check-label">System logs</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="include_config"
{% if backup_config.include_config %}checked{% endif %}>
<label class="form-check-label">Configuration files</label>
</div>
</div>
<button type="submit" class="btn btn-primary">
<i class="fa fa-save me-2"></i>Save Configuration
</button>
</form>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h4 class="card-title">Quick Actions</h4>
</div>
<div class="card-body">
<div class="d-grid gap-3">
<button type="button" class="btn btn-outline-primary" onclick="createFullBackup()">
<i class="fa fa-database me-2"></i>Create Full Backup
</button>
<button type="button" class="btn btn-outline-info" onclick="createDatabaseBackup()">
<i class="fa fa-table me-2"></i>Database Only Backup
</button>
<button type="button" class="btn btn-outline-success" onclick="testBackupSystem()">
<i class="fa fa-check-circle me-2"></i>Test Backup System
</button>
<button type="button" class="btn btn-outline-warning" onclick="cleanupOldBackups()">
<i class="fa fa-trash me-2"></i>Cleanup Old Backups
</button>
<hr>
<div class="mb-3">
<label class="form-label">Restore from File</label>
<input type="file" class="form-control" id="restoreFile" accept=".sql,.zip,.tar.gz">
</div>
<button type="button" class="btn btn-outline-danger" onclick="restoreFromFile()">
<i class="fa fa-upload me-2"></i>Restore from File
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Backup History -->
<div class="card">
<div class="card-header">
<h4 class="card-title">Backup History</h4>
<div class="card-toolbar">
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="refreshBackupList()">
<i class="fa fa-refresh"></i>
</button>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped" id="backupHistoryTable">
<thead>
<tr>
<th>Backup Name</th>
<th>Type</th>
<th>Size</th>
<th>Created</th>
<th>Status</th>
<th>Duration</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for backup in backups %}
<tr>
<td>
<div class="fw-bold">{{ backup.name }}</div>
<div class="text-muted small">{{ backup.description|default:"No description" }}</div>
</td>
<td>
<span class="badge bg-{{ backup.type_color }}">
<i class="fa fa-{{ backup.type_icon }} me-1"></i>{{ backup.get_type_display }}
</span>
</td>
<td>
<div class="fw-bold">{{ backup.file_size_human }}</div>
{% if backup.compressed_size %}
<div class="text-muted small">{{ backup.compressed_size_human }} compressed</div>
{% endif %}
</td>
<td>
<div>{{ backup.created_at|date:"M d, Y" }}</div>
<div class="text-muted small">{{ backup.created_at|date:"g:i A" }}</div>
</td>
<td>
{% if backup.status == 'completed' %}
<span class="badge bg-success">
<i class="fa fa-check me-1"></i>Completed
</span>
{% elif backup.status == 'in_progress' %}
<span class="badge bg-warning">
<i class="fa fa-spinner fa-spin me-1"></i>In Progress
</span>
{% elif backup.status == 'failed' %}
<span class="badge bg-danger">
<i class="fa fa-times me-1"></i>Failed
</span>
{% else %}
<span class="badge bg-secondary">{{ backup.get_status_display }}</span>
{% endif %}
</td>
<td>
{% if backup.duration %}
{{ backup.duration_human }}
{% else %}
<span class="text-muted">N/A</span>
{% endif %}
</td>
<td>
<div class="btn-group">
{% if backup.status == 'completed' %}
<button type="button" class="btn btn-outline-primary btn-sm" onclick="downloadBackup('{{ backup.id }}')">
<i class="fa fa-download"></i>
</button>
<button type="button" class="btn btn-outline-success btn-sm" onclick="restoreBackup('{{ backup.id }}')">
<i class="fa fa-undo"></i>
</button>
{% endif %}
<button type="button" class="btn btn-outline-info btn-sm" onclick="viewBackupDetails('{{ backup.id }}')">
<i class="fa fa-eye"></i>
</button>
<button type="button" class="btn btn-outline-danger btn-sm" onclick="deleteBackup('{{ backup.id }}')">
<i class="fa fa-trash"></i>
</button>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Backup Progress Modal -->
<div class="modal fade" id="backupProgressModal" tabindex="-1" data-bs-backdrop="static">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Backup in Progress</h5>
</div>
<div class="modal-body">
<div class="text-center mb-3">
<i class="fa fa-spinner fa-spin fa-3x text-primary"></i>
</div>
<div class="progress mb-3">
<div class="progress-bar" role="progressbar" style="width: 0%" id="backupProgress"></div>
</div>
<div class="text-center">
<div id="backupStatus">Initializing backup...</div>
<div class="text-muted small" id="backupDetails"></div>
</div>
</div>
</div>
</div>
</div>
<!-- Restore Confirmation Modal -->
<div class="modal fade" id="restoreConfirmModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Confirm Restore</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="alert alert-warning">
<i class="fa fa-exclamation-triangle me-2"></i>
<strong>Warning:</strong> This will overwrite current data with the backup data. This action cannot be undone.
</div>
<p>Are you sure you want to restore from this backup?</p>
<div id="restoreBackupInfo"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger" id="confirmRestoreBtn">Restore</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block js %}
<script src="{% static 'assets/plugins/datatables.net/js/jquery.dataTables.min.js' %}"></script>
<script src="{% static 'assets/plugins/datatables.net-bs5/js/dataTables.bootstrap5.min.js' %}"></script>
<script>
$(document).ready(function() {
// Initialize DataTable
$('#backupHistoryTable').DataTable({
responsive: true,
pageLength: 10,
order: [[3, 'desc']],
columnDefs: [
{ orderable: false, targets: [6] }
]
});
// Backup configuration form
$('#backupConfigForm').submit(function(e) {
e.preventDefault();
var formData = new FormData(this);
$.post('{% url "core:save_backup_config" %}', formData, function(response) {
if (response.success) {
toastr.success('Backup configuration saved');
} else {
toastr.error('Failed to save configuration: ' + response.error);
}
}).fail(function() {
toastr.error('Failed to save configuration');
});
});
});
function createBackup() {
createFullBackup();
}
function createFullBackup() {
if (confirm('Create a full system backup? This may take several minutes.')) {
startBackupProcess('full');
}
}
function createDatabaseBackup() {
if (confirm('Create a database backup?')) {
startBackupProcess('database');
}
}
function startBackupProcess(type) {
$('#backupProgressModal').modal('show');
$.post('{% url "core:create_backup" %}', {
'backup_type': type,
'csrfmiddlewaretoken': '{{ csrf_token }}'
}, function(response) {
if (response.success) {
monitorBackupProgress(response.backup_id);
} else {
$('#backupProgressModal').modal('hide');
toastr.error('Failed to start backup: ' + response.error);
}
}).fail(function() {
$('#backupProgressModal').modal('hide');
toastr.error('Failed to start backup');
});
}
function monitorBackupProgress(backupId) {
var progressInterval = setInterval(function() {
$.get('{% url "core:backup_progress" 0 %}'.replace('0', backupId), function(response) {
$('#backupProgress').css('width', response.progress + '%');
$('#backupStatus').text(response.status);
$('#backupDetails').text(response.details);
if (response.completed) {
clearInterval(progressInterval);
$('#backupProgressModal').modal('hide');
if (response.success) {
toastr.success('Backup completed successfully');
refreshBackupList();
} else {
toastr.error('Backup failed: ' + response.error);
}
}
});
}, 2000);
}
function downloadBackup(backupId) {
window.location.href = '{% url "core:download_backup" 0 %}'.replace('0', backupId);
}
function restoreBackup(backupId) {
$.get('{% url "core:backup_detail" 0 %}'.replace('0', backupId), function(backup) {
$('#restoreBackupInfo').html(`
<strong>Backup:</strong> ${backup.name}<br>
<strong>Created:</strong> ${backup.created_at}<br>
<strong>Size:</strong> ${backup.file_size_human}<br>
<strong>Type:</strong> ${backup.type}
`);
$('#confirmRestoreBtn').off('click').on('click', function() {
performRestore(backupId);
});
$('#restoreConfirmModal').modal('show');
});
}
function performRestore(backupId) {
$('#restoreConfirmModal').modal('hide');
$.post('{% url "core:restore_backup" %}', {
'backup_id': backupId,
'csrfmiddlewaretoken': '{{ csrf_token }}'
}, function(response) {
if (response.success) {
toastr.success('Restore completed successfully');
setTimeout(function() {
location.reload();
}, 2000);
} else {
toastr.error('Restore failed: ' + response.error);
}
}).fail(function() {
toastr.error('Failed to restore backup');
});
}
function restoreFromFile() {
var fileInput = document.getElementById('restoreFile');
if (!fileInput.files.length) {
toastr.warning('Please select a backup file first');
return;
}
if (confirm('Restore from uploaded file? This will overwrite current data.')) {
var formData = new FormData();
formData.append('backup_file', fileInput.files[0]);
formData.append('csrfmiddlewaretoken', '{{ csrf_token }}');
$.ajax({
url: '{% url "core:restore_from_file" %}',
type: 'POST',
data: formData,
processData: false,
contentType: false,
success: function(response) {
if (response.success) {
toastr.success('Restore completed successfully');
setTimeout(function() {
location.reload();
}, 2000);
} else {
toastr.error('Restore failed: ' + response.error);
}
},
error: function() {
toastr.error('Failed to restore from file');
}
});
}
}
function viewBackupDetails(backupId) {
window.open('{% url "core:backup_detail" 0 %}'.replace('0', backupId), '_blank');
}
function deleteBackup(backupId) {
if (confirm('Delete this backup? This action cannot be undone.')) {
$.post('{% url "core:delete_backup" %}', {
'backup_id': backupId,
'csrfmiddlewaretoken': '{{ csrf_token }}'
}, function(response) {
if (response.success) {
toastr.success('Backup deleted successfully');
refreshBackupList();
} else {
toastr.error('Failed to delete backup: ' + response.error);
}
}).fail(function() {
toastr.error('Failed to delete backup');
});
}
}
function testBackupSystem() {
$.post('{% url "core:test_backup_system" %}', {
'csrfmiddlewaretoken': '{{ csrf_token }}'
}, function(response) {
if (response.success) {
toastr.success('Backup system test passed');
} else {
toastr.error('Backup system test failed: ' + response.error);
}
}).fail(function() {
toastr.error('Failed to test backup system');
});
}
function cleanupOldBackups() {
if (confirm('Delete backups older than the retention period?')) {
$.post('{% url "core:cleanup_old_backups" %}', {
'csrfmiddlewaretoken': '{{ csrf_token }}'
}, function(response) {
if (response.success) {
toastr.success(`Cleaned up ${response.deleted_count} old backups`);
refreshBackupList();
} else {
toastr.error('Failed to cleanup backups: ' + response.error);
}
}).fail(function() {
toastr.error('Failed to cleanup backups');
});
}
}
function refreshBackupList() {
location.reload();
}
</script>
{% endblock %}