add database backup and restore admin page
Some checks failed
deploy / deploy (push) Has been cancelled

- SQLCipher-encrypted backup creation via VACUUM INTO
- Backup history with auto-pruning (keeps last 5)
- Pre-restore automatic backup for safety
- Restore from history or uploaded file
- Stats display with table breakdown
- Download hook for client-side file download
- SECRET_KEY_DB config for encryption at rest

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
jamey
2026-03-13 13:33:29 +00:00
parent b0f8eea2bc
commit 09f55dfe67
10 changed files with 2183 additions and 1 deletions

View File

@@ -932,10 +932,25 @@ const EditorKeyboard = {
}
}
// Hook to trigger file downloads from LiveView
const Download = {
mounted() {
this.handleEvent("download", ({filename, content, content_type}) => {
const blob = new Blob([Uint8Array.from(atob(content), c => c.charCodeAt(0))], {type: content_type})
const url = URL.createObjectURL(blob)
const a = document.createElement("a")
a.href = url
a.download = filename
a.click()
URL.revokeObjectURL(url)
})
}
}
const csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
const liveSocket = new LiveSocket("/live", Socket, {
params: {_csrf_token: csrfToken, screen_width: window.innerWidth},
hooks: {...colocatedHooks, ColorSync, Lightbox, CartPersist, CartDrawer, ProductImageScroll, SearchModal, MobileNavDrawer, CollectionFilters, AnalyticsInit, AnalyticsExport, ChartTooltip, Clipboard, DirtyGuard, EditorKeyboard, EditorSheet},
hooks: {...colocatedHooks, ColorSync, Lightbox, CartPersist, CartDrawer, ProductImageScroll, SearchModal, MobileNavDrawer, CollectionFilters, AnalyticsInit, AnalyticsExport, ChartTooltip, Clipboard, DirtyGuard, EditorKeyboard, EditorSheet, Download},
})
// Show progress bar on live navigation and form submits
@@ -956,6 +971,11 @@ window.addEventListener("phx:scroll-top", () => {
window.scrollTo({top: 0, behavior: 'instant'})
})
// Scroll element into view (used by flash messages)
window.addEventListener("scroll-into-view", (e) => {
e.target.scrollIntoView({behavior: 'smooth', block: 'nearest'})
})
// connect if there are any LiveViews on the page
liveSocket.connect()