796 lines
31 KiB
HTML
Executable File
796 lines
31 KiB
HTML
Executable File
{% extends "base.html" %}
|
||
|
||
{% block title %}Settings{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="card-container">
|
||
<div class="card">
|
||
<h3>Manage Users (Legacy)</h3>
|
||
<ul class="user-list">
|
||
{% for user in users %}
|
||
<li data-user-id="{{ user.id }}" data-username="{{ user.username }}" data-email="{{ user.email if user.email else '' }}" data-role="{{ user.role }}">
|
||
<span class="user-name">{{ user.username }}</span>
|
||
<span class="user-role">Role: {{ user.role }}</span>
|
||
<button class="btn edit-user-btn" data-user-id="{{ user.id }}" data-username="{{ user.username }}" data-email="{{ user.email if user.email else '' }}" data-role="{{ user.role }}">Edit User</button>
|
||
<button class="btn delete-btn delete-user-btn" data-user-id="{{ user.id }}" data-username="{{ user.username }}">Delete User</button>
|
||
</li>
|
||
{% endfor %}
|
||
</ul>
|
||
<button id="create-user-btn" class="btn create-btn">Create User</button>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h3>External Server Settings</h3>
|
||
<form method="POST" action="{{ url_for('main.save_external_db') }}" class="form-centered">
|
||
<label for="db_server_domain">Server Domain/IP Address:</label>
|
||
<input type="text" id="db_server_domain" name="server_domain" value="{{ external_settings.get('server_domain', '') }}" required>
|
||
<label for="db_port">Port:</label>
|
||
<input type="number" id="db_port" name="port" value="{{ external_settings.get('port', '') }}" required>
|
||
<label for="db_database_name">Database Name:</label>
|
||
<input type="text" id="db_database_name" name="database_name" value="{{ external_settings.get('database_name', '') }}" required>
|
||
<label for="db_username">Username:</label>
|
||
<input type="text" id="db_username" name="username" value="{{ external_settings.get('username', '') }}" required>
|
||
<label for="db_password">Password:</label>
|
||
<input type="password" id="db_password" name="password" value="{{ external_settings.get('password', '') }}" required>
|
||
<button type="submit" class="btn">Save/Update External Database Info Settings</button>
|
||
</form>
|
||
</div>
|
||
|
||
<div class="card" style="margin-top: 32px;">
|
||
<h3>🎯 User & Permissions Management</h3>
|
||
<p><strong>Simplified 4-Tier System:</strong> Superadmin → Admin → Manager → Worker</p>
|
||
<p>Streamlined interface with module-based permissions (Quality, Warehouse, Labels)</p>
|
||
<div style="margin-top: 15px;">
|
||
<a href="{{ url_for('main.user_management_simple') }}" class="btn" style="background-color: #2196f3; color: white; margin-right: 10px;">
|
||
🎯 Manage Users (Simplified)
|
||
</a>
|
||
|
||
</div>
|
||
<small style="display: block; margin-top: 10px; color: #666;">
|
||
Recommended: Use the simplified user management for easier administration
|
||
</small>
|
||
</div>
|
||
|
||
{% if session.role in ['superadmin', 'admin'] %}
|
||
<div class="card backup-card" style="margin-top: 32px;">
|
||
<h3>💾 Database Backup Management</h3>
|
||
<p><strong>Automated Backup System:</strong> Schedule and manage database backups</p>
|
||
|
||
<!-- Backup Controls -->
|
||
<div class="backup-controls">
|
||
<h4 style="margin-top: 0;">Quick Actions</h4>
|
||
<button id="backup-now-btn" class="btn" style="background-color: #4caf50; color: white; margin-right: 10px;">
|
||
⚡ Backup Now
|
||
</button>
|
||
<button id="refresh-backups-btn" class="btn" style="background-color: #2196f3; color: white;">
|
||
🔄 Refresh List
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Schedule Configuration -->
|
||
<div class="backup-schedule">
|
||
<h4 style="margin-top: 0;">Backup Schedule</h4>
|
||
<form id="backup-schedule-form" class="schedule-form">
|
||
<div>
|
||
<label for="schedule-enabled">
|
||
<input type="checkbox" id="schedule-enabled" name="enabled"> Enable Scheduled Backups
|
||
</label>
|
||
</div>
|
||
<div>
|
||
<label for="schedule-time">Backup Time:</label>
|
||
<input type="time" id="schedule-time" name="time" value="02:00">
|
||
</div>
|
||
<div>
|
||
<label for="schedule-frequency">Frequency:</label>
|
||
<select id="schedule-frequency" name="frequency">
|
||
<option value="daily">Daily</option>
|
||
<option value="weekly">Weekly</option>
|
||
<option value="monthly">Monthly</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label for="retention-days">Keep backups for (days):</label>
|
||
<input type="number" id="retention-days" name="retention_days" value="30" min="1" max="365">
|
||
</div>
|
||
<div style="grid-column: span 2;">
|
||
<button type="submit" class="btn" style="background-color: #ff9800; color: white;">
|
||
💾 Save Schedule
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<!-- Backup List -->
|
||
<div style="margin-top: 20px;">
|
||
<h4>Available Backups</h4>
|
||
<div id="backup-list" class="backup-list-container">
|
||
<p style="text-align: center; color: #999;">Loading backups...</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Backup Path Info -->
|
||
<div class="backup-info">
|
||
<strong>ℹ️ Backup Location:</strong> <code id="backup-path-display">/srv/quality_app/backups</code>
|
||
<br>
|
||
<small>Configure backup path in docker-compose.yml (BACKUP_PATH environment variable)</small>
|
||
</div>
|
||
|
||
<!-- Restore Database Section (Superadmin Only) -->
|
||
{% if current_user.role == 'superadmin' %}
|
||
<div class="restore-section" style="margin-top: 30px; padding: 20px; border: 2px solid #ff9800; border-radius: 8px; background: #fff3e0;">
|
||
<h4 style="margin: 0 0 10px 0; color: #e65100;">⚠️ Restore Database</h4>
|
||
<p style="margin: 0 0 15px 0; color: #e65100; font-weight: bold;">
|
||
WARNING: Restoring will permanently replace ALL current data with the backup data. This action cannot be undone!
|
||
</p>
|
||
|
||
<!-- Upload External Backup File -->
|
||
<div style="margin-bottom: 20px; padding: 15px; background: #e3f2fd; border: 1px solid #2196f3; border-radius: 4px;">
|
||
<h5 style="margin: 0 0 10px 0; color: #1976d2;">📤 Upload External Backup File</h5>
|
||
<p style="margin: 0 0 10px 0; font-size: 0.9em; color: #555;">
|
||
Upload a backup file from another server or external source. File will be saved to the backups directory.
|
||
</p>
|
||
<div style="display: flex; gap: 10px; align-items: center;">
|
||
<input type="file" id="backup-file-upload" accept=".sql" style="flex: 1; padding: 8px; border: 1px solid #ccc; border-radius: 4px;">
|
||
<button id="upload-backup-btn" class="btn" style="background: #2196f3; color: white; padding: 10px 20px; white-space: nowrap;">
|
||
⬆️ Upload File
|
||
</button>
|
||
</div>
|
||
<small style="color: #666; display: block; margin-top: 5px;">
|
||
Accepted format: .sql files only | Max size: 100MB
|
||
</small>
|
||
</div>
|
||
|
||
<!-- Select Backup to Restore -->
|
||
<div style="margin-bottom: 15px;">
|
||
<label for="restore-backup-select" style="display: block; margin-bottom: 5px; font-weight: bold;">Select Backup to Restore:</label>
|
||
<select id="restore-backup-select" style="width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 4px;">
|
||
<option value="">-- Select a backup file --</option>
|
||
</select>
|
||
</div>
|
||
<button id="restore-btn" class="btn" style="background: #ff5722; color: white; padding: 10px 20px; width: 100%; font-weight: bold;" disabled>
|
||
🔄 Restore Database from Selected Backup
|
||
</button>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<style>
|
||
/* Backup Card Styles */
|
||
.backup-card {
|
||
background: #fff;
|
||
padding: 20px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.backup-controls {
|
||
margin-top: 20px;
|
||
padding: 15px;
|
||
background: #f5f5f5;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.backup-schedule {
|
||
margin-top: 20px;
|
||
padding: 15px;
|
||
background: #f9f9f9;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.schedule-form {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 15px;
|
||
}
|
||
|
||
.schedule-form label {
|
||
display: block;
|
||
margin-bottom: 5px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.schedule-form input[type="time"],
|
||
.schedule-form input[type="number"],
|
||
.schedule-form select {
|
||
width: 100%;
|
||
padding: 8px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
background: #fff;
|
||
color: #333;
|
||
}
|
||
|
||
.backup-list-container {
|
||
max-height: 400px;
|
||
overflow-y: auto;
|
||
background: #fafafa;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
padding: 10px;
|
||
}
|
||
|
||
.backup-list-container table {
|
||
background: #fff;
|
||
}
|
||
|
||
.backup-list-container th {
|
||
background: #f0f0f0;
|
||
color: #333;
|
||
}
|
||
|
||
.backup-list-container tr {
|
||
border-bottom: 1px solid #ddd;
|
||
}
|
||
|
||
.backup-info {
|
||
margin-top: 15px;
|
||
padding: 10px;
|
||
background: #e3f2fd;
|
||
border-left: 4px solid #2196f3;
|
||
border-radius: 4px;
|
||
color: #0d47a1;
|
||
}
|
||
|
||
.backup-info code {
|
||
background: #bbdefb;
|
||
padding: 2px 6px;
|
||
border-radius: 3px;
|
||
color: #01579b;
|
||
}
|
||
|
||
.backup-info small {
|
||
color: #1565c0;
|
||
}
|
||
|
||
/* Dark Mode Styles */
|
||
body.dark-mode .backup-card {
|
||
background: #2d2d2d;
|
||
color: #e0e0e0;
|
||
}
|
||
|
||
body.dark-mode .backup-controls {
|
||
background: #3a3a3a;
|
||
}
|
||
|
||
body.dark-mode .backup-schedule {
|
||
background: #353535;
|
||
}
|
||
|
||
body.dark-mode .schedule-form input[type="time"],
|
||
body.dark-mode .schedule-form input[type="number"],
|
||
body.dark-mode .schedule-form select {
|
||
background: #2d2d2d;
|
||
color: #e0e0e0;
|
||
border-color: #555;
|
||
}
|
||
|
||
body.dark-mode .schedule-form label {
|
||
color: #e0e0e0;
|
||
}
|
||
|
||
body.dark-mode .backup-list-container {
|
||
background: #2d2d2d;
|
||
border-color: #555;
|
||
}
|
||
|
||
body.dark-mode .backup-list-container table {
|
||
background: #2d2d2d;
|
||
color: #e0e0e0;
|
||
}
|
||
|
||
body.dark-mode .backup-list-container th {
|
||
background: #3a3a3a;
|
||
color: #e0e0e0;
|
||
}
|
||
|
||
body.dark-mode .backup-list-container tr {
|
||
border-bottom-color: #555;
|
||
}
|
||
|
||
body.dark-mode .backup-list-container td {
|
||
color: #e0e0e0;
|
||
}
|
||
|
||
body.dark-mode .backup-info {
|
||
background: #1e3a5f;
|
||
border-left-color: #2196f3;
|
||
color: #90caf9;
|
||
}
|
||
|
||
body.dark-mode .backup-info code {
|
||
background: #2d4a6d;
|
||
color: #64b5f6;
|
||
}
|
||
|
||
body.dark-mode .backup-info small {
|
||
color: #81c784;
|
||
}
|
||
|
||
/* Dark mode for restore section */
|
||
body.dark-mode .restore-section {
|
||
background: #3a2a1f !important;
|
||
border-color: #ff9800 !important;
|
||
}
|
||
|
||
body.dark-mode .restore-section h4,
|
||
body.dark-mode .restore-section p {
|
||
color: #ffb74d !important;
|
||
}
|
||
|
||
body.dark-mode .restore-section label {
|
||
color: #e0e0e0;
|
||
}
|
||
|
||
body.dark-mode .restore-section select {
|
||
background: #2d2d2d;
|
||
color: #e0e0e0;
|
||
border-color: #555;
|
||
}
|
||
|
||
/* Dark mode for upload section */
|
||
body.dark-mode .restore-section div[style*="background: #e3f2fd"] {
|
||
background: #1a3a52 !important;
|
||
border-color: #2196f3 !important;
|
||
}
|
||
|
||
body.dark-mode .restore-section h5 {
|
||
color: #64b5f6 !important;
|
||
}
|
||
|
||
body.dark-mode .restore-section input[type="file"] {
|
||
background: #2d2d2d;
|
||
color: #e0e0e0;
|
||
border-color: #555;
|
||
}
|
||
</style>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<!-- Popup for creating/editing a user -->
|
||
<div id="user-popup" class="popup" style="display:none; position:fixed; top:0; left:0; width:100vw; height:100vh; background:var(--app-overlay-bg, rgba(30,41,59,0.85)); z-index:9999; align-items:center; justify-content:center;">
|
||
<div class="popup-content" style="margin:auto; padding:32px; border-radius:8px; box-shadow:0 2px 8px #333; min-width:320px; max-width:400px; text-align:center;">
|
||
<h3 id="user-popup-title">Create/Edit User</h3>
|
||
<form id="user-form" method="POST" action="{{ url_for('main.create_user') }}">
|
||
<input type="hidden" id="user-id" name="user_id">
|
||
<label for="user_username">Username:</label>
|
||
<input type="text" id="user_username" name="username" required>
|
||
<label for="user_email">Email (Optional):</label>
|
||
<input type="email" id="user_email" name="email">
|
||
<label for="user_password">Password:</label>
|
||
<input type="password" id="user_password" name="password" required>
|
||
<label for="user_role">Role:</label>
|
||
<select id="user_role" name="role" required>
|
||
<option value="superadmin">Superadmin</option>
|
||
<option value="admin">Admin</option>
|
||
<option value="manager">Manager</option>
|
||
<option value="warehouse_manager">Warehouse Manager</option>
|
||
<option value="warehouse_worker">Warehouse Worker</option>
|
||
<option value="quality_manager">Quality Manager</option>
|
||
<option value="quality_worker">Quality Worker</option>
|
||
</select>
|
||
<button type="submit" class="btn">Save</button>
|
||
<button type="button" id="close-user-popup-btn" class="btn cancel-btn">Cancel</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Popup for confirming user deletion -->
|
||
<div id="delete-user-popup" class="popup">
|
||
<div class="popup-content">
|
||
<h3>Do you really want to delete the user <span id="delete-username"></span>?</h3>
|
||
<form id="delete-user-form" method="POST" action="{{ url_for('main.delete_user') }}">
|
||
<input type="hidden" id="delete-user-id" name="user_id">
|
||
<button type="submit" class="btn delete-confirm-btn">Yes</button>
|
||
<button type="button" id="close-delete-popup-btn" class="btn cancel-btn">No</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
<script>
|
||
document.getElementById('create-user-btn').onclick = function() {
|
||
document.getElementById('user-popup').style.display = 'flex';
|
||
document.getElementById('user-popup-title').innerText = 'Create User';
|
||
document.getElementById('user-form').reset();
|
||
document.getElementById('user-form').setAttribute('action', '{{ url_for("main.create_user") }}');
|
||
document.getElementById('user-id').value = '';
|
||
document.getElementById('user_password').required = true;
|
||
document.getElementById('user_password').placeholder = '';
|
||
document.getElementById('user_username').readOnly = false;
|
||
};
|
||
|
||
document.getElementById('close-user-popup-btn').onclick = function() {
|
||
document.getElementById('user-popup').style.display = 'none';
|
||
};
|
||
|
||
// Edit User button logic
|
||
Array.from(document.getElementsByClassName('edit-user-btn')).forEach(function(btn) {
|
||
btn.onclick = function() {
|
||
document.getElementById('user-popup').style.display = 'flex';
|
||
document.getElementById('user-popup-title').innerText = 'Edit User';
|
||
document.getElementById('user-id').value = btn.getAttribute('data-user-id');
|
||
document.getElementById('user_username').value = btn.getAttribute('data-username');
|
||
document.getElementById('user_email').value = btn.getAttribute('data-email') || '';
|
||
document.getElementById('user_role').value = btn.getAttribute('data-role');
|
||
document.getElementById('user_password').value = '';
|
||
document.getElementById('user_password').required = false;
|
||
document.getElementById('user_password').placeholder = 'Leave blank to keep current password';
|
||
document.getElementById('user_username').readOnly = true;
|
||
document.getElementById('user-form').setAttribute('action', '{{ url_for("main.edit_user") }}');
|
||
};
|
||
});
|
||
|
||
// Delete User button logic
|
||
Array.from(document.getElementsByClassName('delete-user-btn')).forEach(function(btn) {
|
||
btn.onclick = function() {
|
||
document.getElementById('delete-user-popup').style.display = 'flex';
|
||
document.getElementById('delete-username').innerText = btn.getAttribute('data-username');
|
||
document.getElementById('delete-user-id').value = btn.getAttribute('data-user-id');
|
||
};
|
||
});
|
||
|
||
document.getElementById('close-delete-popup-btn').onclick = function() {
|
||
document.getElementById('delete-user-popup').style.display = 'none';
|
||
};
|
||
|
||
// ========================================
|
||
// Database Backup Management Functions
|
||
// ========================================
|
||
|
||
// Load backup schedule on page load
|
||
function loadBackupSchedule() {
|
||
fetch('/api/backup/schedule')
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
document.getElementById('schedule-enabled').checked = data.schedule.enabled;
|
||
document.getElementById('schedule-time').value = data.schedule.time;
|
||
document.getElementById('schedule-frequency').value = data.schedule.frequency;
|
||
document.getElementById('retention-days').value = data.schedule.retention_days;
|
||
}
|
||
})
|
||
.catch(error => console.error('Error loading schedule:', error));
|
||
}
|
||
|
||
// Load backup list
|
||
function loadBackupList() {
|
||
const backupList = document.getElementById('backup-list');
|
||
if (!backupList) return;
|
||
|
||
backupList.innerHTML = '<p style="text-align: center; color: #999;">Loading backups...</p>';
|
||
|
||
fetch('/api/backup/list')
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.success && data.backups.length > 0) {
|
||
let html = '<table style="width: 100%; border-collapse: collapse;">';
|
||
html += '<thead><tr style="background: #f0f0f0;"><th style="padding: 10px; text-align: left;">Filename</th><th>Size</th><th>Created</th><th>Actions</th></tr></thead>';
|
||
html += '<tbody>';
|
||
|
||
data.backups.forEach(backup => {
|
||
html += `<tr style="border-bottom: 1px solid #ddd;">
|
||
<td style="padding: 10px;">${backup.filename}</td>
|
||
<td style="text-align: center;">${backup.size_mb} MB</td>
|
||
<td style="text-align: center;">${backup.created}</td>
|
||
<td style="text-align: center;">
|
||
<button onclick="downloadBackup('${backup.filename}')" class="btn" style="background: #2196f3; color: white; padding: 5px 10px; margin: 2px;">⬇️ Download</button>
|
||
<button onclick="deleteBackup('${backup.filename}')" class="btn" style="background: #f44336; color: white; padding: 5px 10px; margin: 2px;">🗑️ Delete</button>
|
||
</td>
|
||
</tr>`;
|
||
});
|
||
|
||
html += '</tbody></table>';
|
||
backupList.innerHTML = html;
|
||
|
||
// Populate restore dropdown
|
||
const restoreSelect = document.getElementById('restore-backup-select');
|
||
if (restoreSelect) {
|
||
restoreSelect.innerHTML = '<option value="">-- Select a backup file --</option>';
|
||
data.backups.forEach(backup => {
|
||
restoreSelect.innerHTML += `<option value="${backup.filename}">${backup.filename} (${backup.size_mb} MB - ${backup.created})</option>`;
|
||
});
|
||
}
|
||
} else {
|
||
backupList.innerHTML = '<p style="text-align: center; color: #999;">No backups available</p>';
|
||
// Clear restore dropdown
|
||
const restoreSelect = document.getElementById('restore-backup-select');
|
||
if (restoreSelect) {
|
||
restoreSelect.innerHTML = '<option value="">-- No backups available --</option>';
|
||
}
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('Error loading backups:', error);
|
||
backupList.innerHTML = '<p style="text-align: center; color: #f44336;">Error loading backups</p>';
|
||
});
|
||
}
|
||
|
||
// Backup now button
|
||
document.getElementById('backup-now-btn')?.addEventListener('click', function() {
|
||
const btn = this;
|
||
btn.disabled = true;
|
||
btn.innerHTML = '⏳ Creating backup...';
|
||
|
||
fetch('/api/backup/create', {
|
||
method: 'POST'
|
||
})
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
alert('✅ ' + data.message + '\nFile: ' + data.filename + '\nSize: ' + data.size);
|
||
loadBackupList();
|
||
} else {
|
||
alert('❌ ' + data.message);
|
||
}
|
||
btn.disabled = false;
|
||
btn.innerHTML = '⚡ Backup Now';
|
||
})
|
||
.catch(error => {
|
||
console.error('Error creating backup:', error);
|
||
alert('❌ Failed to create backup');
|
||
btn.disabled = false;
|
||
btn.innerHTML = '⚡ Backup Now';
|
||
});
|
||
});
|
||
|
||
// Refresh backups button
|
||
document.getElementById('refresh-backups-btn')?.addEventListener('click', function() {
|
||
loadBackupList();
|
||
});
|
||
|
||
// Save schedule form
|
||
document.getElementById('backup-schedule-form')?.addEventListener('submit', function(e) {
|
||
e.preventDefault();
|
||
|
||
const formData = {
|
||
enabled: document.getElementById('schedule-enabled').checked,
|
||
time: document.getElementById('schedule-time').value,
|
||
frequency: document.getElementById('schedule-frequency').value,
|
||
retention_days: parseInt(document.getElementById('retention-days').value)
|
||
};
|
||
|
||
fetch('/api/backup/schedule', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify(formData)
|
||
})
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
alert('✅ ' + data.message);
|
||
} else {
|
||
alert('❌ ' + data.message);
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('Error saving schedule:', error);
|
||
alert('❌ Failed to save schedule');
|
||
});
|
||
});
|
||
|
||
// Download backup function
|
||
function downloadBackup(filename) {
|
||
window.location.href = `/api/backup/download/${filename}`;
|
||
}
|
||
|
||
// Delete backup function
|
||
function deleteBackup(filename) {
|
||
if (confirm(`Are you sure you want to delete backup: ${filename}?`)) {
|
||
fetch(`/api/backup/delete/${filename}`, {
|
||
method: 'DELETE'
|
||
})
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
alert('✅ ' + data.message);
|
||
loadBackupList();
|
||
} else {
|
||
alert('❌ ' + data.message);
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('Error deleting backup:', error);
|
||
alert('❌ Failed to delete backup');
|
||
});
|
||
}
|
||
}
|
||
|
||
// Restore dropdown change - enable/disable button
|
||
document.getElementById('restore-backup-select')?.addEventListener('change', function() {
|
||
const restoreBtn = document.getElementById('restore-btn');
|
||
if (this.value) {
|
||
restoreBtn.disabled = false;
|
||
} else {
|
||
restoreBtn.disabled = true;
|
||
}
|
||
});
|
||
|
||
// Restore backup function
|
||
document.getElementById('restore-btn')?.addEventListener('click', function() {
|
||
const filename = document.getElementById('restore-backup-select').value;
|
||
|
||
if (!filename) {
|
||
alert('❌ Please select a backup file to restore');
|
||
return;
|
||
}
|
||
|
||
// First confirmation
|
||
const firstConfirm = confirm(
|
||
`⚠️ CRITICAL WARNING ⚠️\n\n` +
|
||
`You are about to RESTORE the database from:\n${filename}\n\n` +
|
||
`This will PERMANENTLY DELETE all current data and replace it with the backup data.\n\n` +
|
||
`This action CANNOT be undone!\n\n` +
|
||
`Do you want to continue?`
|
||
);
|
||
|
||
if (!firstConfirm) {
|
||
return;
|
||
}
|
||
|
||
// Second confirmation (require typing confirmation)
|
||
const secondConfirm = prompt(
|
||
`⚠️ FINAL CONFIRMATION ⚠️\n\n` +
|
||
`Type "RESTORE" in capital letters to confirm you understand:\n` +
|
||
`• All current database data will be PERMANENTLY DELETED\n` +
|
||
`• This action is IRREVERSIBLE\n` +
|
||
`• Users may experience downtime during restore\n\n` +
|
||
`Type RESTORE to continue:`
|
||
);
|
||
|
||
if (secondConfirm !== 'RESTORE') {
|
||
alert('❌ Restore cancelled - confirmation text did not match');
|
||
return;
|
||
}
|
||
|
||
// Perform restore
|
||
const btn = this;
|
||
btn.disabled = true;
|
||
btn.innerHTML = '⏳ Restoring database... Please wait...';
|
||
|
||
fetch(`/api/backup/restore/${filename}`, {
|
||
method: 'POST'
|
||
})
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
alert(
|
||
`✅ DATABASE RESTORE COMPLETE!\n\n` +
|
||
`${data.message}\n\n` +
|
||
`The application will now reload to apply changes.`
|
||
);
|
||
// Reload the page to ensure all data is fresh
|
||
window.location.reload();
|
||
} else {
|
||
alert(`❌ RESTORE FAILED\n\n${data.message}`);
|
||
btn.disabled = false;
|
||
btn.innerHTML = '🔄 Restore Database from Selected Backup';
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('Error restoring backup:', error);
|
||
alert(`❌ RESTORE FAILED\n\nAn error occurred while restoring the database.`);
|
||
btn.disabled = false;
|
||
btn.innerHTML = '🔄 Restore Database from Selected Backup';
|
||
});
|
||
});
|
||
|
||
// Upload backup file
|
||
document.getElementById('upload-backup-btn')?.addEventListener('click', function() {
|
||
const fileInput = document.getElementById('backup-file-upload');
|
||
const file = fileInput.files[0];
|
||
|
||
if (!file) {
|
||
alert('❌ Please select a file to upload');
|
||
return;
|
||
}
|
||
|
||
// Validate file extension
|
||
if (!file.name.toLowerCase().endsWith('.sql')) {
|
||
alert('❌ Invalid file format. Only .sql files are allowed.');
|
||
return;
|
||
}
|
||
|
||
// Validate file size (10GB max for large databases)
|
||
const maxSize = 10 * 1024 * 1024 * 1024; // 10GB in bytes
|
||
if (file.size > maxSize) {
|
||
alert('❌ File is too large. Maximum size is 10GB.');
|
||
return;
|
||
}
|
||
|
||
// Warn about large files
|
||
const warningSize = 1 * 1024 * 1024 * 1024; // 1GB
|
||
if (file.size > warningSize) {
|
||
const sizeGB = (file.size / (1024 * 1024 * 1024)).toFixed(2);
|
||
if (!confirm(`⚠️ Large File Warning\n\nYou are uploading a ${sizeGB} GB file.\nThis may take several minutes.\n\nDo you want to continue?`)) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
// Prepare form data
|
||
const formData = new FormData();
|
||
formData.append('backup_file', file);
|
||
|
||
// Disable button and show loading
|
||
const btn = this;
|
||
btn.disabled = true;
|
||
btn.innerHTML = '⏳ Uploading and validating...';
|
||
|
||
// Upload file
|
||
fetch('/api/backup/upload', {
|
||
method: 'POST',
|
||
body: formData
|
||
})
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
// Build detailed success message with validation info
|
||
let message = `✅ File uploaded and validated successfully!\n\n`;
|
||
message += `Filename: ${data.filename}\n`;
|
||
message += `Size: ${data.size}\n`;
|
||
|
||
// Add validation details if available
|
||
if (data.validation && data.validation.details) {
|
||
const details = data.validation.details;
|
||
message += `\n📊 Validation Results:\n`;
|
||
message += `• Lines: ${details.line_count || 'N/A'}\n`;
|
||
message += `• Has Users Table: ${details.has_users_table ? '✓' : '✗'}\n`;
|
||
message += `• Has Data: ${details.has_insert_statements ? '✓' : '✗'}\n`;
|
||
}
|
||
|
||
// Add warnings if any
|
||
if (data.validation && data.validation.warnings && data.validation.warnings.length > 0) {
|
||
message += `\n⚠️ Warnings:\n`;
|
||
data.validation.warnings.forEach(warning => {
|
||
message += `• ${warning}\n`;
|
||
});
|
||
}
|
||
|
||
message += `\nThe file is now available in the restore dropdown.`;
|
||
|
||
alert(message);
|
||
|
||
// Clear file input
|
||
fileInput.value = '';
|
||
// Reload backup list to show the new file
|
||
loadBackupList();
|
||
} else {
|
||
// Build detailed error message
|
||
let message = `❌ Upload failed\n\n${data.message}`;
|
||
|
||
// Add validation details if available
|
||
if (data.validation_details) {
|
||
message += `\n\n📊 Validation Details:\n`;
|
||
const details = data.validation_details;
|
||
if (details.size_mb) message += `• File Size: ${details.size_mb} MB\n`;
|
||
if (details.line_count) message += `• Lines: ${details.line_count}\n`;
|
||
}
|
||
|
||
// Add warnings if any
|
||
if (data.warnings && data.warnings.length > 0) {
|
||
message += `\n⚠️ Issues Found:\n`;
|
||
data.warnings.forEach(warning => {
|
||
message += `• ${warning}\n`;
|
||
});
|
||
}
|
||
|
||
alert(message);
|
||
}
|
||
btn.disabled = false;
|
||
btn.innerHTML = '⬆️ Upload File';
|
||
})
|
||
.catch(error => {
|
||
console.error('Error uploading backup:', error);
|
||
alert('❌ Failed to upload file');
|
||
btn.disabled = false;
|
||
btn.innerHTML = '⬆️ Upload File';
|
||
});
|
||
});
|
||
|
||
// Load backup data on page load
|
||
if (document.getElementById('backup-list')) {
|
||
loadBackupSchedule();
|
||
loadBackupList();
|
||
}
|
||
|
||
</script>
|
||
{% endblock %} |