859 lines
34 KiB
HTML
859 lines
34 KiB
HTML
{% extends 'base.html' %}
|
|
{% load static %}
|
|
|
|
{% block title %}Restore Configuration{% 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"><a href="{% url 'core:configuration_backup' %}">Configuration Backup</a></li>
|
|
<li class="breadcrumb-item active">Restore Configuration</li>
|
|
</ul>
|
|
|
|
<div class="row align-items-center mb-3">
|
|
<div class="col">
|
|
<h1 class="page-header">Restore Configuration</h1>
|
|
<p class="text-muted">Restore system configuration from backup files</p>
|
|
</div>
|
|
<div class="col-auto">
|
|
<a href="{% url 'core:configuration_backup' %}" class="btn btn-outline-secondary">
|
|
<i class="fa fa-arrow-left me-2"></i>Back to Backups
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Restore Warning -->
|
|
<div class="row mb-4">
|
|
<div class="col-12">
|
|
<div class="alert alert-warning">
|
|
<div class="d-flex">
|
|
<div class="me-3">
|
|
<i class="fa fa-exclamation-triangle fa-2x"></i>
|
|
</div>
|
|
<div>
|
|
<h5 class="alert-heading">Important Warning</h5>
|
|
<p class="mb-2">Restoring a configuration backup will overwrite your current system settings. This action cannot be undone.</p>
|
|
<ul class="mb-0">
|
|
<li>All current configuration will be replaced</li>
|
|
<li>Services may need to be restarted</li>
|
|
<li>Users may experience temporary downtime</li>
|
|
<li>It's recommended to create a backup before proceeding</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Restore Options -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h4 class="card-title">
|
|
<i class="fa fa-database me-2"></i>
|
|
Restore from Existing Backup
|
|
</h4>
|
|
</div>
|
|
<div class="card-body">
|
|
<p class="text-muted">Select from previously created backups stored on the server.</p>
|
|
|
|
<form id="restoreExistingForm">
|
|
{% csrf_token %}
|
|
<div class="mb-3">
|
|
<label class="form-label">Available Backups</label>
|
|
<select id="existingBackupSelect" name="backup_id" class="form-select" required>
|
|
<option value="">Loading backups...</option>
|
|
</select>
|
|
<div class="form-text">Only completed backups are shown</div>
|
|
</div>
|
|
|
|
<div id="backupPreview" class="mb-3" style="display: none;">
|
|
<div class="card bg-light">
|
|
<div class="card-body">
|
|
<h6>Backup Preview</h6>
|
|
<div id="backupPreviewContent">
|
|
<!-- Backup details will be shown here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<button type="submit" class="btn btn-warning">
|
|
<i class="fa fa-upload me-2"></i>Restore Selected Backup
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h4 class="card-title">
|
|
<i class="fa fa-file-upload me-2"></i>
|
|
Upload Backup File
|
|
</h4>
|
|
</div>
|
|
<div class="card-body">
|
|
<p class="text-muted">Upload a backup file from your computer to restore.</p>
|
|
|
|
<form id="restoreUploadForm" enctype="multipart/form-data">
|
|
{% csrf_token %}
|
|
<div class="mb-3">
|
|
<label class="form-label">Backup File</label>
|
|
<input type="file" id="backupFileInput" name="backup_file" class="form-control" accept=".gz,.zip,.tar,.bak" required>
|
|
<div class="form-text">Supported formats: .gz, .zip, .tar, .bak</div>
|
|
</div>
|
|
|
|
<div id="uploadPreview" class="mb-3" style="display: none;">
|
|
<div class="card bg-light">
|
|
<div class="card-body">
|
|
<h6>File Information</h6>
|
|
<div id="uploadPreviewContent">
|
|
<!-- File details will be shown here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<button type="submit" class="btn btn-warning">
|
|
<i class="fa fa-upload me-2"></i>Upload and Restore
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Restore Options -->
|
|
<div class="row mb-4">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h4 class="card-title">Restore Options</h4>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<h6>Pre-Restore Actions</h6>
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="createBackupBefore" checked>
|
|
<label class="form-check-label" for="createBackupBefore">
|
|
Create backup of current configuration
|
|
</label>
|
|
</div>
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="validateBackup" checked>
|
|
<label class="form-check-label" for="validateBackup">
|
|
Validate backup file before restore
|
|
</label>
|
|
</div>
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="checkDependencies">
|
|
<label class="form-check-label" for="checkDependencies">
|
|
Check system dependencies
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<h6>Post-Restore Actions</h6>
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="restartServices" checked>
|
|
<label class="form-check-label" for="restartServices">
|
|
Restart affected services
|
|
</label>
|
|
</div>
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="clearCache" checked>
|
|
<label class="form-check-label" for="clearCache">
|
|
Clear application cache
|
|
</label>
|
|
</div>
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="runMigrations">
|
|
<label class="form-check-label" for="runMigrations">
|
|
Run database migrations if needed
|
|
</label>
|
|
</div>
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="sendNotification" checked>
|
|
<label class="form-check-label" for="sendNotification">
|
|
Send completion notification
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent Restore History -->
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h4 class="card-title">Recent Restore History</h4>
|
|
<div class="card-tools">
|
|
<button type="button" class="btn btn-outline-primary btn-sm" onclick="refreshRestoreHistory()">
|
|
<i class="fa fa-sync me-1"></i>Refresh
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="table-responsive">
|
|
<table class="table table-striped">
|
|
<thead>
|
|
<tr>
|
|
<th>Restore Date</th>
|
|
<th>Backup Source</th>
|
|
<th>Status</th>
|
|
<th>Duration</th>
|
|
<th>Restored By</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="restoreHistoryBody">
|
|
<tr>
|
|
<td colspan="6" class="text-center text-muted">
|
|
<i class="fa fa-spinner fa-spin me-2"></i>Loading restore history...
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</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 Configuration Restore</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="alert alert-danger">
|
|
<i class="fa fa-exclamation-triangle me-2"></i>
|
|
<strong>Final Warning:</strong> This action will overwrite your current configuration and cannot be undone.
|
|
</div>
|
|
|
|
<div id="restoreConfirmDetails">
|
|
<!-- Restore details will be shown here -->
|
|
</div>
|
|
|
|
<div class="form-check mt-3">
|
|
<input class="form-check-input" type="checkbox" id="confirmRestore" required>
|
|
<label class="form-check-label" for="confirmRestore">
|
|
I understand the risks and want to proceed with the restore
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="button" id="confirmRestoreBtn" class="btn btn-danger" disabled>
|
|
<i class="fa fa-upload me-2"></i>Proceed with Restore
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Restore Progress Modal -->
|
|
<div class="modal fade" id="restoreProgressModal" tabindex="-1" data-bs-backdrop="static">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Configuration Restore Progress</h5>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="mb-3">
|
|
<div class="d-flex justify-content-between">
|
|
<span id="restoreProgressLabel">Initializing restore...</span>
|
|
<span id="restoreProgressPercent">0%</span>
|
|
</div>
|
|
<div class="progress">
|
|
<div id="restoreProgressBar" class="progress-bar" style="width: 0%"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mb-3">
|
|
<div class="col-md-4">
|
|
<div class="text-center">
|
|
<div id="currentStepIcon" class="mb-2">
|
|
<i class="fa fa-clock fa-2x text-muted"></i>
|
|
</div>
|
|
<div id="currentStepText" class="text-muted">Waiting...</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-8">
|
|
<div id="restoreSteps">
|
|
<div class="restore-step" data-step="validate">
|
|
<i class="fa fa-check-circle text-muted me-2"></i>
|
|
<span>Validate backup file</span>
|
|
</div>
|
|
<div class="restore-step" data-step="backup">
|
|
<i class="fa fa-check-circle text-muted me-2"></i>
|
|
<span>Create current configuration backup</span>
|
|
</div>
|
|
<div class="restore-step" data-step="extract">
|
|
<i class="fa fa-check-circle text-muted me-2"></i>
|
|
<span>Extract backup contents</span>
|
|
</div>
|
|
<div class="restore-step" data-step="restore">
|
|
<i class="fa fa-check-circle text-muted me-2"></i>
|
|
<span>Restore configuration files</span>
|
|
</div>
|
|
<div class="restore-step" data-step="migrate">
|
|
<i class="fa fa-check-circle text-muted me-2"></i>
|
|
<span>Run database migrations</span>
|
|
</div>
|
|
<div class="restore-step" data-step="restart">
|
|
<i class="fa fa-check-circle text-muted me-2"></i>
|
|
<span>Restart services</span>
|
|
</div>
|
|
<div class="restore-step" data-step="verify">
|
|
<i class="fa fa-check-circle text-muted me-2"></i>
|
|
<span>Verify system health</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="restoreLog" class="bg-dark text-light p-3" style="height: 300px; overflow-y: auto; font-family: monospace; font-size: 0.875rem;">
|
|
<!-- Restore log messages will appear here -->
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" id="cancelRestoreBtn" class="btn btn-secondary">Cancel Restore</button>
|
|
<button type="button" id="closeRestoreProgressBtn" class="btn btn-primary" style="display: none;">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Restore Details Modal -->
|
|
<div class="modal fade" id="restoreDetailsModal" tabindex="-1">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Restore Details</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div id="restoreDetailsContent">
|
|
<!-- Restore details will be loaded here -->
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block js %}
|
|
<script>
|
|
var currentRestoreId = null;
|
|
var selectedBackupData = null;
|
|
|
|
$(document).ready(function() {
|
|
loadAvailableBackups();
|
|
loadRestoreHistory();
|
|
setupEventHandlers();
|
|
});
|
|
|
|
function setupEventHandlers() {
|
|
// Form submissions
|
|
$('#restoreExistingForm').submit(function(e) {
|
|
e.preventDefault();
|
|
initiateRestore('existing');
|
|
});
|
|
|
|
$('#restoreUploadForm').submit(function(e) {
|
|
e.preventDefault();
|
|
initiateRestore('upload');
|
|
});
|
|
|
|
// Backup selection change
|
|
$('#existingBackupSelect').change(function() {
|
|
var backupId = $(this).val();
|
|
if (backupId) {
|
|
loadBackupPreview(backupId);
|
|
} else {
|
|
$('#backupPreview').hide();
|
|
}
|
|
});
|
|
|
|
// File input change
|
|
$('#backupFileInput').change(function() {
|
|
var file = this.files[0];
|
|
if (file) {
|
|
showFilePreview(file);
|
|
} else {
|
|
$('#uploadPreview').hide();
|
|
}
|
|
});
|
|
|
|
// Confirmation checkbox
|
|
$('#confirmRestore').change(function() {
|
|
$('#confirmRestoreBtn').prop('disabled', !$(this).is(':checked'));
|
|
});
|
|
|
|
// Confirm restore button
|
|
$('#confirmRestoreBtn').click(function() {
|
|
proceedWithRestore();
|
|
});
|
|
|
|
// Cancel restore
|
|
$('#cancelRestoreBtn').click(function() {
|
|
if (currentRestoreId && confirm('Are you sure you want to cancel the restore? This may leave the system in an inconsistent state.')) {
|
|
cancelRestore();
|
|
}
|
|
});
|
|
}
|
|
|
|
function loadAvailableBackups() {
|
|
$.get('{% url "core:get_backup_list" %}', function(data) {
|
|
var options = '<option value="">Select a backup to restore...</option>';
|
|
|
|
data.backups.forEach(function(backup) {
|
|
if (backup.status === 'completed') {
|
|
options += '<option value="' + backup.id + '">' +
|
|
backup.name + ' (' + formatDate(backup.created_at) + ') - ' +
|
|
formatFileSize(backup.size) + '</option>';
|
|
}
|
|
});
|
|
|
|
$('#existingBackupSelect').html(options);
|
|
}).fail(function() {
|
|
$('#existingBackupSelect').html('<option value="">Failed to load backups</option>');
|
|
});
|
|
}
|
|
|
|
function loadBackupPreview(backupId) {
|
|
$.get('{% url "core:get_backup_details" %}', {backup_id: backupId}, function(data) {
|
|
selectedBackupData = data;
|
|
|
|
var previewHtml = '<div class="row">' +
|
|
'<div class="col-md-6">' +
|
|
'<strong>Name:</strong> ' + data.name + '<br>' +
|
|
'<strong>Type:</strong> <span class="badge bg-primary">' + data.type + '</span><br>' +
|
|
'<strong>Size:</strong> ' + formatFileSize(data.size) + '<br>' +
|
|
'</div>' +
|
|
'<div class="col-md-6">' +
|
|
'<strong>Created:</strong> ' + formatDate(data.created_at) + '<br>' +
|
|
'<strong>Encrypted:</strong> ' + (data.encrypted ? 'Yes' : 'No') + '<br>' +
|
|
'<strong>Components:</strong> ' + (data.components || []).join(', ') + '<br>' +
|
|
'</div>' +
|
|
'</div>';
|
|
|
|
if (data.description) {
|
|
previewHtml += '<hr><strong>Description:</strong><br>' + data.description;
|
|
}
|
|
|
|
$('#backupPreviewContent').html(previewHtml);
|
|
$('#backupPreview').show();
|
|
});
|
|
}
|
|
|
|
function showFilePreview(file) {
|
|
var previewHtml = '<div class="row">' +
|
|
'<div class="col-md-6">' +
|
|
'<strong>File Name:</strong> ' + file.name + '<br>' +
|
|
'<strong>Size:</strong> ' + formatFileSize(file.size) + '<br>' +
|
|
'<strong>Type:</strong> ' + file.type + '<br>' +
|
|
'</div>' +
|
|
'<div class="col-md-6">' +
|
|
'<strong>Last Modified:</strong> ' + new Date(file.lastModified).toLocaleString() + '<br>' +
|
|
'</div>' +
|
|
'</div>';
|
|
|
|
$('#uploadPreviewContent').html(previewHtml);
|
|
$('#uploadPreview').show();
|
|
}
|
|
|
|
function initiateRestore(method) {
|
|
var confirmHtml = '';
|
|
|
|
if (method === 'existing') {
|
|
if (!selectedBackupData) {
|
|
toastr.error('Please select a backup to restore');
|
|
return;
|
|
}
|
|
|
|
confirmHtml = '<h6>Backup Information</h6>' +
|
|
'<ul>' +
|
|
'<li><strong>Name:</strong> ' + selectedBackupData.name + '</li>' +
|
|
'<li><strong>Type:</strong> ' + selectedBackupData.type + '</li>' +
|
|
'<li><strong>Created:</strong> ' + formatDate(selectedBackupData.created_at) + '</li>' +
|
|
'<li><strong>Size:</strong> ' + formatFileSize(selectedBackupData.size) + '</li>' +
|
|
'</ul>';
|
|
} else {
|
|
var file = $('#backupFileInput')[0].files[0];
|
|
if (!file) {
|
|
toastr.error('Please select a backup file to upload');
|
|
return;
|
|
}
|
|
|
|
confirmHtml = '<h6>File Information</h6>' +
|
|
'<ul>' +
|
|
'<li><strong>File Name:</strong> ' + file.name + '</li>' +
|
|
'<li><strong>Size:</strong> ' + formatFileSize(file.size) + '</li>' +
|
|
'<li><strong>Last Modified:</strong> ' + new Date(file.lastModified).toLocaleString() + '</li>' +
|
|
'</ul>';
|
|
}
|
|
|
|
confirmHtml += '<h6>Restore Options</h6><ul>';
|
|
|
|
if ($('#createBackupBefore').is(':checked')) {
|
|
confirmHtml += '<li>Create backup of current configuration</li>';
|
|
}
|
|
if ($('#validateBackup').is(':checked')) {
|
|
confirmHtml += '<li>Validate backup file before restore</li>';
|
|
}
|
|
if ($('#restartServices').is(':checked')) {
|
|
confirmHtml += '<li>Restart affected services</li>';
|
|
}
|
|
if ($('#clearCache').is(':checked')) {
|
|
confirmHtml += '<li>Clear application cache</li>';
|
|
}
|
|
if ($('#runMigrations').is(':checked')) {
|
|
confirmHtml += '<li>Run database migrations if needed</li>';
|
|
}
|
|
|
|
confirmHtml += '</ul>';
|
|
|
|
$('#restoreConfirmDetails').html(confirmHtml);
|
|
$('#confirmRestore').prop('checked', false);
|
|
$('#confirmRestoreBtn').prop('disabled', true);
|
|
$('#restoreConfirmModal').modal('show');
|
|
}
|
|
|
|
function proceedWithRestore() {
|
|
$('#restoreConfirmModal').modal('hide');
|
|
showRestoreProgress();
|
|
|
|
var formData = new FormData();
|
|
formData.append('csrfmiddlewaretoken', $('[name=csrfmiddlewaretoken]').val());
|
|
|
|
// Add restore options
|
|
formData.append('create_backup_before', $('#createBackupBefore').is(':checked'));
|
|
formData.append('validate_backup', $('#validateBackup').is(':checked'));
|
|
formData.append('check_dependencies', $('#checkDependencies').is(':checked'));
|
|
formData.append('restart_services', $('#restartServices').is(':checked'));
|
|
formData.append('clear_cache', $('#clearCache').is(':checked'));
|
|
formData.append('run_migrations', $('#runMigrations').is(':checked'));
|
|
formData.append('send_notification', $('#sendNotification').is(':checked'));
|
|
|
|
// Add backup source
|
|
if (selectedBackupData) {
|
|
formData.append('backup_id', selectedBackupData.id);
|
|
formData.append('restore_method', 'existing');
|
|
} else {
|
|
var file = $('#backupFileInput')[0].files[0];
|
|
formData.append('backup_file', file);
|
|
formData.append('restore_method', 'upload');
|
|
}
|
|
|
|
$.ajax({
|
|
url: '{% url "core:restore_configuration" %}',
|
|
type: 'POST',
|
|
data: formData,
|
|
processData: false,
|
|
contentType: false,
|
|
success: function(data) {
|
|
if (data.success) {
|
|
currentRestoreId = data.restore_id;
|
|
monitorRestoreProgress(data.restore_id);
|
|
} else {
|
|
hideRestoreProgress();
|
|
toastr.error('Failed to start restore: ' + data.error);
|
|
}
|
|
},
|
|
error: function() {
|
|
hideRestoreProgress();
|
|
toastr.error('Failed to start restore');
|
|
}
|
|
});
|
|
}
|
|
|
|
function showRestoreProgress() {
|
|
$('#restoreProgressLabel').text('Initializing restore...');
|
|
$('#restoreProgressPercent').text('0%');
|
|
$('#restoreProgressBar').css('width', '0%');
|
|
$('#restoreLog').html('');
|
|
$('#currentStepIcon').html('<i class="fa fa-clock fa-2x text-muted"></i>');
|
|
$('#currentStepText').text('Waiting...');
|
|
$('.restore-step i').removeClass('text-success text-danger').addClass('text-muted');
|
|
$('#cancelRestoreBtn').show();
|
|
$('#closeRestoreProgressBtn').hide();
|
|
$('#restoreProgressModal').modal('show');
|
|
}
|
|
|
|
function hideRestoreProgress() {
|
|
$('#restoreProgressModal').modal('hide');
|
|
}
|
|
|
|
function monitorRestoreProgress(restoreId) {
|
|
var checkProgress = function() {
|
|
$.get('{% url "core:get_restore_progress" %}', {restore_id: restoreId}, function(data) {
|
|
updateRestoreProgress(data.progress, data.message, data.log, data.current_step);
|
|
|
|
if (data.status === 'completed') {
|
|
$('#restoreProgressLabel').text('Restore completed successfully');
|
|
$('#currentStepIcon').html('<i class="fa fa-check-circle fa-2x text-success"></i>');
|
|
$('#currentStepText').text('Completed');
|
|
$('#cancelRestoreBtn').hide();
|
|
$('#closeRestoreProgressBtn').show();
|
|
toastr.success('Configuration restored successfully');
|
|
loadRestoreHistory();
|
|
} else if (data.status === 'failed') {
|
|
$('#restoreProgressLabel').text('Restore failed');
|
|
$('#currentStepIcon').html('<i class="fa fa-times-circle fa-2x text-danger"></i>');
|
|
$('#currentStepText').text('Failed');
|
|
$('#cancelRestoreBtn').hide();
|
|
$('#closeRestoreProgressBtn').show();
|
|
toastr.error('Restore failed: ' + data.error);
|
|
} else {
|
|
setTimeout(checkProgress, 2000);
|
|
}
|
|
}).fail(function() {
|
|
$('#restoreProgressLabel').text('Failed to get progress');
|
|
$('#currentStepIcon').html('<i class="fa fa-times-circle fa-2x text-danger"></i>');
|
|
$('#currentStepText').text('Error');
|
|
$('#cancelRestoreBtn').hide();
|
|
$('#closeRestoreProgressBtn').show();
|
|
});
|
|
};
|
|
|
|
checkProgress();
|
|
}
|
|
|
|
function updateRestoreProgress(progress, message, log, currentStep) {
|
|
$('#restoreProgressPercent').text(progress + '%');
|
|
$('#restoreProgressBar').css('width', progress + '%');
|
|
$('#restoreProgressLabel').text(message);
|
|
|
|
// Update current step
|
|
if (currentStep) {
|
|
$('#currentStepText').text(currentStep.charAt(0).toUpperCase() + currentStep.slice(1));
|
|
|
|
// Update step icons
|
|
$('.restore-step').each(function() {
|
|
var step = $(this).data('step');
|
|
var icon = $(this).find('i');
|
|
|
|
if (step === currentStep) {
|
|
icon.removeClass('text-muted text-success text-danger').addClass('text-warning');
|
|
icon.removeClass('fa-check-circle').addClass('fa-spinner fa-spin');
|
|
} else {
|
|
icon.removeClass('fa-spinner fa-spin').addClass('fa-check-circle');
|
|
}
|
|
});
|
|
}
|
|
|
|
if (log) {
|
|
var logHtml = $('#restoreLog').html();
|
|
logHtml += '<div>' + new Date().toLocaleTimeString() + ': ' + log + '</div>';
|
|
$('#restoreLog').html(logHtml);
|
|
$('#restoreLog').scrollTop($('#restoreLog')[0].scrollHeight);
|
|
}
|
|
}
|
|
|
|
function loadRestoreHistory() {
|
|
$.get('{% url "core:get_restore_history" %}', function(data) {
|
|
var html = '';
|
|
|
|
if (data.restores.length === 0) {
|
|
html = '<tr><td colspan="6" class="text-center text-muted">No restore history found</td></tr>';
|
|
} else {
|
|
data.restores.forEach(function(restore) {
|
|
html += '<tr>' +
|
|
'<td>' + formatDate(restore.created_at) + '</td>' +
|
|
'<td>' + restore.backup_name + '</td>' +
|
|
'<td><span class="badge bg-' + getStatusColor(restore.status) + '">' + restore.status + '</span></td>' +
|
|
'<td>' + (restore.duration || 'N/A') + '</td>' +
|
|
'<td>' + restore.restored_by + '</td>' +
|
|
'<td>' +
|
|
'<button class="btn btn-outline-primary btn-sm" onclick="viewRestoreDetails(\'' + restore.id + '\')">' +
|
|
'<i class="fa fa-eye"></i>' +
|
|
'</button>' +
|
|
'</td>' +
|
|
'</tr>';
|
|
});
|
|
}
|
|
|
|
$('#restoreHistoryBody').html(html);
|
|
}).fail(function() {
|
|
$('#restoreHistoryBody').html('<tr><td colspan="6" class="text-center text-danger">Failed to load restore history</td></tr>');
|
|
});
|
|
}
|
|
|
|
function viewRestoreDetails(restoreId) {
|
|
$.get('{% url "core:get_restore_details" %}', {restore_id: restoreId}, function(data) {
|
|
var detailsHtml = '<div class="row">' +
|
|
'<div class="col-md-6">' +
|
|
'<h6>Restore Information</h6>' +
|
|
'<table class="table table-sm">' +
|
|
'<tr><td><strong>Started:</strong></td><td>' + formatDate(data.created_at) + '</td></tr>' +
|
|
'<tr><td><strong>Completed:</strong></td><td>' + (data.completed_at ? formatDate(data.completed_at) : 'N/A') + '</td></tr>' +
|
|
'<tr><td><strong>Duration:</strong></td><td>' + (data.duration || 'N/A') + '</td></tr>' +
|
|
'<tr><td><strong>Status:</strong></td><td><span class="badge bg-' + getStatusColor(data.status) + '">' + data.status + '</span></td></tr>' +
|
|
'<tr><td><strong>Restored By:</strong></td><td>' + data.restored_by + '</td></tr>' +
|
|
'</table>' +
|
|
'</div>' +
|
|
'<div class="col-md-6">' +
|
|
'<h6>Backup Source</h6>' +
|
|
'<table class="table table-sm">' +
|
|
'<tr><td><strong>Backup Name:</strong></td><td>' + data.backup_name + '</td></tr>' +
|
|
'<tr><td><strong>Backup Type:</strong></td><td>' + data.backup_type + '</td></tr>' +
|
|
'<tr><td><strong>Backup Size:</strong></td><td>' + formatFileSize(data.backup_size) + '</td></tr>' +
|
|
'</table>' +
|
|
'</div>' +
|
|
'</div>';
|
|
|
|
if (data.log) {
|
|
detailsHtml += '<hr><h6>Restore Log</h6>' +
|
|
'<pre class="bg-light p-3" style="max-height: 300px; overflow-y: auto;">' + data.log + '</pre>';
|
|
}
|
|
|
|
if (data.error) {
|
|
detailsHtml += '<hr><h6>Error Details</h6>' +
|
|
'<div class="alert alert-danger">' + data.error + '</div>';
|
|
}
|
|
|
|
$('#restoreDetailsContent').html(detailsHtml);
|
|
$('#restoreDetailsModal').modal('show');
|
|
});
|
|
}
|
|
|
|
function refreshRestoreHistory() {
|
|
loadRestoreHistory();
|
|
toastr.success('Restore history refreshed');
|
|
}
|
|
|
|
function cancelRestore() {
|
|
$.post('{% url "core:cancel_restore" %}', {restore_id: currentRestoreId}, function(data) {
|
|
hideRestoreProgress();
|
|
if (data.success) {
|
|
toastr.info('Restore cancelled');
|
|
}
|
|
});
|
|
}
|
|
|
|
function getStatusColor(status) {
|
|
switch (status) {
|
|
case 'completed': return 'success';
|
|
case 'failed': return 'danger';
|
|
case 'in_progress': return 'warning';
|
|
case 'cancelled': 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();
|
|
}
|
|
|
|
// Close progress modal
|
|
$('#closeRestoreProgressBtn').click(function() {
|
|
hideRestoreProgress();
|
|
});
|
|
</script>
|
|
|
|
<style>
|
|
.card-tools {
|
|
margin-left: auto;
|
|
}
|
|
|
|
.progress {
|
|
height: 20px;
|
|
}
|
|
|
|
.progress-bar {
|
|
transition: width 0.3s ease;
|
|
}
|
|
|
|
#restoreLog {
|
|
font-size: 0.875rem;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
#restoreLog div {
|
|
margin-bottom: 2px;
|
|
}
|
|
|
|
.restore-step {
|
|
padding: 0.5rem 0;
|
|
border-bottom: 1px solid #eee;
|
|
}
|
|
|
|
.restore-step:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.restore-step i {
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
.alert-heading {
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.fa-2x {
|
|
font-size: 2em;
|
|
}
|
|
|
|
.fa-spin {
|
|
animation: fa-spin 2s infinite linear;
|
|
}
|
|
|
|
@keyframes fa-spin {
|
|
0% {
|
|
transform: rotate(0deg);
|
|
}
|
|
100% {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|