feat(admin): add DB backup and restore functionality

This commit is contained in:
Vito Sansevero
2025-05-27 14:29:17 -07:00
parent 4fca3f6d87
commit 57aae95f8d
+160
View File
@@ -180,6 +180,14 @@
class="px-4 py-2 bg-teal-600 hover:bg-teal-700 text-white text-sm font-medium rounded-lg transition-colors">
🔧 Maintenance
</button>
<button id="backupBtn"
class="px-4 py-2 bg-sky-600 hover:bg-sky-700 text-white text-sm font-medium rounded-lg transition-colors">
💾 Backup DB
</button>
<button id="restoreBtn"
class="px-4 py-2 bg-amber-600 hover:bg-amber-700 text-white text-sm font-medium rounded-lg transition-colors">
📁 Restore DB
</button>
</div>
</div>
</div>
@@ -297,6 +305,40 @@
</div>
</div>
<!-- Database Restore Modal -->
<div id="restoreModal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50">
<div class="bg-gray-800 rounded-xl p-6 max-w-md w-full mx-4 border border-gray-700">
<h3 class="text-xl font-semibold text-gray-100 mb-6">📁 Restore Database</h3>
<div class="mb-4">
<div class="bg-yellow-900/20 border border-yellow-600 rounded-lg p-4 mb-4">
<div class="flex items-start space-x-2">
<span class="text-yellow-400 text-xl">⚠️</span>
<div>
<h4 class="text-yellow-400 font-medium">Warning</h4>
<p class="text-gray-300 text-sm">This will replace your current database. A backup will be created automatically.</p>
</div>
</div>
</div>
<label class="block text-sm font-medium text-gray-400 mb-2">Select Database File (.db)</label>
<input type="file" id="restoreFileInput" accept=".db"
class="w-full px-4 py-3 bg-gray-700 border border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-gray-100 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100">
</div>
<div class="flex justify-end space-x-3 mt-6">
<button id="cancelRestore"
class="px-6 py-2 bg-gray-600 hover:bg-gray-700 text-white font-medium rounded-lg transition-colors">
Cancel
</button>
<button id="confirmRestore"
class="px-6 py-2 bg-amber-600 hover:bg-amber-700 text-white font-medium rounded-lg transition-colors">
Restore Database
</button>
</div>
</div>
</div>
<!-- Diagnostics Modal -->
<div id="diagnosticsModal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50">
<div class="bg-gray-800 rounded-xl max-w-4xl w-full max-h-[90vh] overflow-hidden mx-4 border border-gray-700">
@@ -535,6 +577,8 @@
document.getElementById("statsBtn").addEventListener("click", () => this.showStats());
document.getElementById("diagnosticsBtn").addEventListener("click", () => this.showDiagnosticsModal());
document.getElementById("maintenanceBtn").addEventListener("click", () => this.showMaintenanceModal());
document.getElementById("backupBtn").addEventListener("click", () => this.backupDatabase());
document.getElementById("restoreBtn").addEventListener("click", () => this.showRestoreModal());
// Modals
this.bindModalEvents();
@@ -558,6 +602,10 @@
document.getElementById("confirmBulkCategory").addEventListener("click", () => this.confirmBulkCategory());
document.getElementById("cancelBulkCategory").addEventListener("click", () => this.hideModal("bulkCategoryModal"));
// Restore modal
document.getElementById("confirmRestore").addEventListener("click", () => this.confirmRestore());
document.getElementById("cancelRestore").addEventListener("click", () => this.hideModal("restoreModal"));
// Diagnostics modal
document.getElementById("runDiagnosticsBtn").addEventListener("click", () => this.runDiagnostics());
document.getElementById("testImageLinkBtn").addEventListener("click", () => this.testImageLink());
@@ -1755,6 +1803,118 @@
}
}
// Database backup and restore functionality
async backupDatabase() {
try {
const response = await fetch('/prompt_manager/backup');
if (response.ok) {
// Get the filename from the Content-Disposition header
const contentDisposition = response.headers.get('Content-Disposition');
let filename = 'prompts_backup.db';
if (contentDisposition) {
const filenameMatch = contentDisposition.match(/filename="(.+)"/);
if (filenameMatch) {
filename = filenameMatch[1];
}
}
// Create blob and download
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
this.showNotification('💾 Database backup downloaded successfully!', 'success');
} else {
const errorData = await response.json();
throw new Error(errorData.error || 'Backup failed');
}
} catch (error) {
console.error('Backup error:', error);
this.showNotification(`❌ Backup failed: ${error.message}`, 'error');
}
}
showRestoreModal() {
// Clear previous file selection
document.getElementById('restoreFileInput').value = '';
this.showModal('restoreModal');
}
async confirmRestore() {
const fileInput = document.getElementById('restoreFileInput');
const file = fileInput.files[0];
if (!file) {
this.showNotification('Please select a database file to restore', 'warning');
return;
}
if (!file.name.endsWith('.db')) {
this.showNotification('Please select a valid .db file', 'warning');
return;
}
// Show confirmation
const confirmed = confirm(
`Are you sure you want to restore from "${file.name}"?\n\n` +
'This will replace your current database. A backup will be created automatically.\n\n' +
'This action cannot be undone.'
);
if (!confirmed) {
return;
}
try {
// Disable the restore button and show loading
const confirmBtn = document.getElementById('confirmRestore');
confirmBtn.disabled = true;
confirmBtn.innerHTML = '🔄 Restoring...';
// Create FormData for file upload
const formData = new FormData();
formData.append('database_file', file);
const response = await fetch('/prompt_manager/restore', {
method: 'POST',
body: formData
});
const data = await response.json();
if (response.ok && data.success) {
this.showNotification(
`✅ Database restored successfully! Found ${data.prompt_count} prompts.`,
'success'
);
this.hideModal('restoreModal');
// Refresh the interface to show new data
setTimeout(() => {
window.location.reload();
}, 2000);
} else {
throw new Error(data.error || 'Restore failed');
}
} catch (error) {
console.error('Restore error:', error);
this.showNotification(`❌ Restore failed: ${error.message}`, 'error');
} finally {
// Re-enable the restore button
const confirmBtn = document.getElementById('confirmRestore');
confirmBtn.disabled = false;
confirmBtn.innerHTML = 'Restore Database';
}
}
// Copy prompt to clipboard functionality
async copyPromptToClipboard(promptId) {
// Find the prompt text from the prompts array