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

829 lines
33 KiB
HTML

{% extends 'base.html' %}
{% load static %}
{% block title %}Configuration Backup{% 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">Configuration Backup</li>
</ul>
<div class="row align-items-center mb-3">
<div class="col">
<h1 class="page-header">Configuration Backup</h1>
<p class="text-muted">Create and manage system configuration backups</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 Actions -->
<div class="row mb-4">
<div class="col-md-4">
<div class="card">
<div class="card-body text-center">
<i class="fa fa-download fa-3x text-primary mb-3"></i>
<h5>Create Backup</h5>
<p class="text-muted">Generate a complete configuration backup</p>
<button class="btn btn-primary" onclick="showCreateBackupModal()">
<i class="fa fa-plus me-2"></i>New Backup
</button>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-body text-center">
<i class="fa fa-upload fa-3x text-success mb-3"></i>
<h5>Restore Backup</h5>
<p class="text-muted">Restore from an existing backup file</p>
<button class="btn btn-success" onclick="showRestoreBackupModal()">
<i class="fa fa-upload me-2"></i>Restore
</button>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-body text-center">
<i class="fa fa-clock fa-3x text-info mb-3"></i>
<h5>Scheduled Backups</h5>
<p class="text-muted">Configure automatic backup schedules</p>
<button class="btn btn-info" onclick="showScheduleModal()">
<i class="fa fa-calendar me-2"></i>Schedule
</button>
</div>
</div>
</div>
</div>
<!-- Backup List -->
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h4 class="card-title">Backup History</h4>
<div class="card-tools">
<button type="button" class="btn btn-outline-primary btn-sm" onclick="refreshBackupList()">
<i class="fa fa-sync me-1"></i>Refresh
</button>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table id="backupTable" class="table table-striped">
<thead>
<tr>
<th>Backup Name</th>
<th>Type</th>
<th>Size</th>
<th>Created</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="backupTableBody">
<tr>
<td colspan="6" class="text-center text-muted">
<i class="fa fa-spinner fa-spin me-2"></i>Loading backups...
</td>
</tr>
</tbody>
</table>
</div>
</div>
</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">Create Configuration Backup</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form id="createBackupForm">
<div class="modal-body">
{% csrf_token %}
<div class="mb-3">
<label class="form-label">Backup Name</label>
<input type="text" name="backup_name" class="form-control" placeholder="e.g., Pre-upgrade backup" required>
<div class="form-text">A descriptive name for this backup</div>
</div>
<div class="mb-3">
<label class="form-label">Backup Type</label>
<select name="backup_type" class="form-select" required>
<option value="full">Full Configuration</option>
<option value="settings">Settings Only</option>
<option value="database">Database Schema</option>
<option value="files">Configuration Files</option>
<option value="custom">Custom Selection</option>
</select>
</div>
<div id="customBackupOptions" style="display: none;">
<label class="form-label">Include Components</label>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="include_settings" checked>
<label class="form-check-label">Django Settings</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="include_database" checked>
<label class="form-check-label">Database Configuration</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="include_media">
<label class="form-check-label">Media Files</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="include_static">
<label class="form-check-label">Static Files</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="include_logs">
<label class="form-check-label">Log Files</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="include_certificates">
<label class="form-check-label">SSL Certificates</label>
</div>
</div>
<div class="mb-3">
<label class="form-label">Compression</label>
<select name="compression" class="form-select">
<option value="gzip">GZip (Recommended)</option>
<option value="zip">ZIP</option>
<option value="none">No Compression</option>
</select>
</div>
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="encrypt_backup">
<label class="form-check-label">Encrypt Backup</label>
</div>
<div class="form-text">Recommended for sensitive configurations</div>
</div>
<div class="mb-3">
<label class="form-label">Description (Optional)</label>
<textarea name="description" class="form-control" rows="3" placeholder="Additional notes about this backup..."></textarea>
</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-primary">
<i class="fa fa-download me-2"></i>Create Backup
</button>
</div>
</form>
</div>
</div>
</div>
<!-- Restore Backup Modal -->
<div class="modal fade" id="restoreBackupModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Restore Configuration Backup</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form id="restoreBackupForm">
<div class="modal-body">
{% csrf_token %}
<div class="mb-3">
<label class="form-label">Restore Method</label>
<select id="restoreMethod" name="restore_method" class="form-select" required>
<option value="existing">From Existing Backup</option>
<option value="upload">Upload Backup File</option>
</select>
</div>
<div id="existingBackupSection">
<div class="mb-3">
<label class="form-label">Select Backup</label>
<select name="backup_id" class="form-select" id="existingBackupSelect">
<option value="">Loading backups...</option>
</select>
</div>
</div>
<div id="uploadBackupSection" style="display: none;">
<div class="mb-3">
<label class="form-label">Backup File</label>
<input type="file" name="backup_file" class="form-control" accept=".gz,.zip,.tar">
<div class="form-text">Supported formats: .gz, .zip, .tar</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Restore Options</label>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="create_backup_before_restore" checked>
<label class="form-check-label">Create backup before restore</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="restart_services">
<label class="form-check-label">Restart services after restore</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="validate_config" checked>
<label class="form-check-label">Validate configuration</label>
</div>
</div>
<div class="alert alert-warning">
<i class="fa fa-exclamation-triangle me-2"></i>
<strong>Warning:</strong> Restoring a backup will overwrite current configuration.
Make sure to create a backup of current settings first.
</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-warning">
<i class="fa fa-upload me-2"></i>Restore Backup
</button>
</div>
</form>
</div>
</div>
</div>
<!-- Schedule Backup Modal -->
<div class="modal fade" id="scheduleBackupModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Schedule Automatic Backups</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form id="scheduleBackupForm">
<div class="modal-body">
{% csrf_token %}
<div class="mb-3">
<label class="form-label">Schedule Name</label>
<input type="text" name="schedule_name" class="form-control" placeholder="e.g., Daily Configuration Backup" required>
</div>
<div class="mb-3">
<label class="form-label">Frequency</label>
<select name="frequency" class="form-select" required>
<option value="daily">Daily</option>
<option value="weekly">Weekly</option>
<option value="monthly">Monthly</option>
<option value="custom">Custom (Cron)</option>
</select>
</div>
<div id="customCronSection" style="display: none;">
<div class="mb-3">
<label class="form-label">Cron Expression</label>
<input type="text" name="cron_expression" class="form-control" placeholder="0 2 * * *">
<div class="form-text">Example: "0 2 * * *" for daily at 2 AM</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Backup Type</label>
<select name="scheduled_backup_type" class="form-select" required>
<option value="full">Full Configuration</option>
<option value="settings">Settings Only</option>
<option value="database">Database Schema</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Retention Policy</label>
<select name="retention_days" class="form-select">
<option value="7">Keep for 7 days</option>
<option value="30">Keep for 30 days</option>
<option value="90">Keep for 90 days</option>
<option value="365">Keep for 1 year</option>
<option value="0">Keep forever</option>
</select>
</div>
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="email_notification" checked>
<label class="form-check-label">Email notification on completion</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="email_on_failure" checked>
<label class="form-check-label">Email notification on failure</label>
</div>
</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-info">
<i class="fa fa-calendar me-2"></i>Create Schedule
</button>
</div>
</form>
</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 Progress</h5>
</div>
<div class="modal-body">
<div class="mb-3">
<div class="d-flex justify-content-between">
<span id="progressLabel">Initializing...</span>
<span id="progressPercent">0%</span>
</div>
<div class="progress">
<div id="progressBar" class="progress-bar" style="width: 0%"></div>
</div>
</div>
<div id="progressLog" class="bg-light p-3" style="height: 200px; overflow-y: auto; font-family: monospace; font-size: 0.875rem;">
<!-- Progress messages will appear here -->
</div>
</div>
<div class="modal-footer">
<button type="button" id="cancelBackupBtn" class="btn btn-secondary">Cancel</button>
<button type="button" id="closeProgressBtn" class="btn btn-primary" style="display: none;">Close</button>
</div>
</div>
</div>
</div>
<!-- Backup Details Modal -->
<div class="modal fade" id="backupDetailsModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Backup Details</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div id="backupDetailsContent">
<!-- Backup details will be loaded here -->
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" onclick="downloadBackup()">
<i class="fa fa-download me-2"></i>Download
</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block js %}
<script>
var currentBackupId = null;
$(document).ready(function() {
loadBackupList();
setupEventHandlers();
});
function setupEventHandlers() {
// Form submissions
$('#createBackupForm').submit(function(e) {
e.preventDefault();
createBackup();
});
$('#restoreBackupForm').submit(function(e) {
e.preventDefault();
restoreBackup();
});
$('#scheduleBackupForm').submit(function(e) {
e.preventDefault();
scheduleBackup();
});
// Backup type change
$('select[name="backup_type"]').change(function() {
if ($(this).val() === 'custom') {
$('#customBackupOptions').show();
} else {
$('#customBackupOptions').hide();
}
});
// Restore method change
$('#restoreMethod').change(function() {
if ($(this).val() === 'upload') {
$('#existingBackupSection').hide();
$('#uploadBackupSection').show();
} else {
$('#existingBackupSection').show();
$('#uploadBackupSection').hide();
}
});
// Schedule frequency change
$('select[name="frequency"]').change(function() {
if ($(this).val() === 'custom') {
$('#customCronSection').show();
} else {
$('#customCronSection').hide();
}
});
}
function loadBackupList() {
$.get('{% url "core:get_backup_list" %}', function(data) {
renderBackupList(data.backups);
populateBackupSelects(data.backups);
}).fail(function() {
$('#backupTableBody').html('<tr><td colspan="6" class="text-center text-danger">Failed to load backups</td></tr>');
});
}
function renderBackupList(backups) {
var html = '';
if (backups.length === 0) {
html = '<tr><td colspan="6" class="text-center text-muted">No backups found</td></tr>';
} else {
backups.forEach(function(backup) {
html += '<tr>' +
'<td>' +
'<strong>' + backup.name + '</strong><br>' +
'<small class="text-muted">' + (backup.description || 'No description') + '</small>' +
'</td>' +
'<td><span class="badge bg-' + getTypeColor(backup.type) + '">' + backup.type + '</span></td>' +
'<td>' + formatFileSize(backup.size) + '</td>' +
'<td>' + formatDate(backup.created_at) + '</td>' +
'<td><span class="badge bg-' + getStatusColor(backup.status) + '">' + backup.status + '</span></td>' +
'<td>' +
'<div class="btn-group btn-group-sm">' +
'<button class="btn btn-outline-primary" onclick="viewBackupDetails(\'' + backup.id + '\')" title="View Details">' +
'<i class="fa fa-eye"></i>' +
'</button>' +
'<button class="btn btn-outline-success" onclick="downloadBackup(\'' + backup.id + '\')" title="Download">' +
'<i class="fa fa-download"></i>' +
'</button>' +
'<button class="btn btn-outline-warning" onclick="restoreFromBackup(\'' + backup.id + '\')" title="Restore">' +
'<i class="fa fa-upload"></i>' +
'</button>' +
'<button class="btn btn-outline-danger" onclick="deleteBackup(\'' + backup.id + '\')" title="Delete">' +
'<i class="fa fa-trash"></i>' +
'</button>' +
'</div>' +
'</td>' +
'</tr>';
});
}
$('#backupTableBody').html(html);
}
function populateBackupSelects(backups) {
var options = '<option value="">Select a backup...</option>';
backups.forEach(function(backup) {
if (backup.status === 'completed') {
options += '<option value="' + backup.id + '">' + backup.name + ' (' + formatDate(backup.created_at) + ')</option>';
}
});
$('#existingBackupSelect').html(options);
}
function getTypeColor(type) {
switch (type) {
case 'full': return 'primary';
case 'settings': return 'info';
case 'database': return 'warning';
case 'files': return 'secondary';
default: return 'light';
}
}
function getStatusColor(status) {
switch (status) {
case 'completed': return 'success';
case 'failed': return 'danger';
case 'in_progress': return 'warning';
case 'pending': return 'secondary';
default: return 'light';
}
}
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
var k = 1024;
var sizes = ['Bytes', 'KB', 'MB', 'GB'];
var i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
function formatDate(dateString) {
return new Date(dateString).toLocaleString();
}
function showCreateBackupModal() {
$('#createBackupModal').modal('show');
}
function showRestoreBackupModal() {
$('#restoreBackupModal').modal('show');
}
function showScheduleModal() {
$('#scheduleBackupModal').modal('show');
}
function createBackup() {
var formData = new FormData($('#createBackupForm')[0]);
$('#createBackupModal').modal('hide');
showBackupProgress();
$.post('{% url "core:create_backup" %}', formData, function(data) {
if (data.success) {
currentBackupId = data.backup_id;
monitorBackupProgress(data.backup_id);
} else {
hideBackupProgress();
toastr.error('Failed to start backup: ' + data.error);
}
}).fail(function() {
hideBackupProgress();
toastr.error('Failed to start backup');
});
}
function restoreBackup() {
if (!confirm('Are you sure you want to restore this backup? This will overwrite current configuration.')) {
return;
}
var formData = new FormData($('#restoreBackupForm')[0]);
$('#restoreBackupModal').modal('hide');
showBackupProgress('Restoring backup...');
$.post('{% url "core:restore_backup" %}', formData, function(data) {
if (data.success) {
monitorRestoreProgress(data.restore_id);
} else {
hideBackupProgress();
toastr.error('Failed to start restore: ' + data.error);
}
}).fail(function() {
hideBackupProgress();
toastr.error('Failed to start restore');
});
}
function scheduleBackup() {
var formData = new FormData($('#scheduleBackupForm')[0]);
$.post('{% url "core:schedule_backup" %}', formData, function(data) {
if (data.success) {
$('#scheduleBackupModal').modal('hide');
toastr.success('Backup schedule created successfully');
} else {
toastr.error('Failed to create schedule: ' + data.error);
}
}).fail(function() {
toastr.error('Failed to create schedule');
});
}
function showBackupProgress(title = 'Creating backup...') {
$('#backupProgressModal .modal-title').text(title);
$('#progressLabel').text('Initializing...');
$('#progressPercent').text('0%');
$('#progressBar').css('width', '0%');
$('#progressLog').html('');
$('#cancelBackupBtn').show();
$('#closeProgressBtn').hide();
$('#backupProgressModal').modal('show');
}
function hideBackupProgress() {
$('#backupProgressModal').modal('hide');
}
function monitorBackupProgress(backupId) {
var checkProgress = function() {
$.get('{% url "core:get_backup_progress" %}', {backup_id: backupId}, function(data) {
updateProgress(data.progress, data.message, data.log);
if (data.status === 'completed') {
$('#progressLabel').text('Backup completed successfully');
$('#cancelBackupBtn').hide();
$('#closeProgressBtn').show();
toastr.success('Backup created successfully');
loadBackupList();
} else if (data.status === 'failed') {
$('#progressLabel').text('Backup failed');
$('#cancelBackupBtn').hide();
$('#closeProgressBtn').show();
toastr.error('Backup failed: ' + data.error);
} else {
setTimeout(checkProgress, 2000);
}
}).fail(function() {
$('#progressLabel').text('Failed to get progress');
$('#cancelBackupBtn').hide();
$('#closeProgressBtn').show();
});
};
checkProgress();
}
function monitorRestoreProgress(restoreId) {
var checkProgress = function() {
$.get('{% url "core:get_restore_progress" %}', {restore_id: restoreId}, function(data) {
updateProgress(data.progress, data.message, data.log);
if (data.status === 'completed') {
$('#progressLabel').text('Restore completed successfully');
$('#cancelBackupBtn').hide();
$('#closeProgressBtn').show();
toastr.success('Configuration restored successfully');
} else if (data.status === 'failed') {
$('#progressLabel').text('Restore failed');
$('#cancelBackupBtn').hide();
$('#closeProgressBtn').show();
toastr.error('Restore failed: ' + data.error);
} else {
setTimeout(checkProgress, 2000);
}
}).fail(function() {
$('#progressLabel').text('Failed to get progress');
$('#cancelBackupBtn').hide();
$('#closeProgressBtn').show();
});
};
checkProgress();
}
function updateProgress(progress, message, log) {
$('#progressPercent').text(progress + '%');
$('#progressBar').css('width', progress + '%');
$('#progressLabel').text(message);
if (log) {
var logHtml = $('#progressLog').html();
logHtml += '<div>' + new Date().toLocaleTimeString() + ': ' + log + '</div>';
$('#progressLog').html(logHtml);
$('#progressLog').scrollTop($('#progressLog')[0].scrollHeight);
}
}
function viewBackupDetails(backupId) {
$.get('{% url "core:get_backup_details" %}', {backup_id: backupId}, function(data) {
var detailsHtml = '<div class="row">' +
'<div class="col-md-6">' +
'<h6>Backup Information</h6>' +
'<table class="table table-sm">' +
'<tr><td><strong>Name:</strong></td><td>' + data.name + '</td></tr>' +
'<tr><td><strong>Type:</strong></td><td><span class="badge bg-' + getTypeColor(data.type) + '">' + data.type + '</span></td></tr>' +
'<tr><td><strong>Size:</strong></td><td>' + formatFileSize(data.size) + '</td></tr>' +
'<tr><td><strong>Created:</strong></td><td>' + formatDate(data.created_at) + '</td></tr>' +
'<tr><td><strong>Status:</strong></td><td><span class="badge bg-' + getStatusColor(data.status) + '">' + data.status + '</span></td></tr>' +
'</table>' +
'</div>' +
'<div class="col-md-6">' +
'<h6>Configuration</h6>' +
'<table class="table table-sm">' +
'<tr><td><strong>Compression:</strong></td><td>' + (data.compression || 'None') + '</td></tr>' +
'<tr><td><strong>Encrypted:</strong></td><td>' + (data.encrypted ? 'Yes' : 'No') + '</td></tr>' +
'<tr><td><strong>Components:</strong></td><td>' + (data.components || []).join(', ') + '</td></tr>' +
'</table>' +
'</div>' +
'</div>';
if (data.description) {
detailsHtml += '<hr><h6>Description</h6><p>' + data.description + '</p>';
}
if (data.log) {
detailsHtml += '<hr><h6>Backup Log</h6><pre class="bg-light p-3" style="max-height: 200px; overflow-y: auto;">' + data.log + '</pre>';
}
$('#backupDetailsContent').html(detailsHtml);
currentBackupId = backupId;
$('#backupDetailsModal').modal('show');
});
}
function downloadBackup(backupId = null) {
var id = backupId || currentBackupId;
if (id) {
window.open('{% url "core:download_backup" %}?backup_id=' + id, '_blank');
}
}
function restoreFromBackup(backupId) {
$('#existingBackupSelect').val(backupId);
$('#restoreMethod').val('existing');
$('#existingBackupSection').show();
$('#uploadBackupSection').hide();
showRestoreBackupModal();
}
function deleteBackup(backupId) {
if (confirm('Are you sure you want to delete this backup? This action cannot be undone.')) {
$.post('{% url "core:delete_backup" %}', {backup_id: backupId}, function(data) {
if (data.success) {
toastr.success('Backup deleted successfully');
loadBackupList();
} else {
toastr.error('Failed to delete backup: ' + data.error);
}
}).fail(function() {
toastr.error('Failed to delete backup');
});
}
}
function refreshBackupList() {
loadBackupList();
toastr.success('Backup list refreshed');
}
// Close progress modal handlers
$('#closeProgressBtn').click(function() {
hideBackupProgress();
});
$('#cancelBackupBtn').click(function() {
if (currentBackupId && confirm('Are you sure you want to cancel the backup?')) {
$.post('{% url "core:cancel_backup" %}', {backup_id: currentBackupId}, function(data) {
hideBackupProgress();
if (data.success) {
toastr.info('Backup cancelled');
}
});
}
});
</script>
<style>
.card-tools {
margin-left: auto;
}
.progress {
height: 20px;
}
.progress-bar {
transition: width 0.3s ease;
}
#progressLog {
font-size: 0.875rem;
line-height: 1.4;
}
#progressLog div {
margin-bottom: 2px;
}
.btn-group-sm .btn {
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
}
.table td {
vertical-align: middle;
}
.badge {
font-size: 0.75rem;
}
pre {
font-size: 0.875rem;
line-height: 1.4;
}
</style>
{% endblock %}