From 7e9f571df1aaa26542950b11d8f529137fcceb38 Mon Sep 17 00:00:00 2001 From: ReaperOfVeriod Date: Sun, 25 Oct 2026 05:20:32 +0100 Subject: [PATCH] add backup controls to settings --- src/App.svelte | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/App.svelte b/src/App.svelte index 2f68d0a..9308aad 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -3,6 +3,9 @@ let connectionState = 'OFFLINE'; let serverVersion = '—'; let diagnostics: { status: string; database: string; twitch_identity_count: number; unseen_notification_count: number; realtime: string } | undefined; + let backups: Array<{ filename: string; size_bytes: number; modified_at: string | null }> = []; + let backupError = ''; + let backupBusy = false; let activeView = 'Live dashboard'; let sessionToken = ''; let authenticated = false; @@ -134,6 +137,7 @@ await loadBotCommands(); await loadBotSecrets(); await loadOverlays(); + await loadBackups(); await loadStreamInfo(); connectRealtime(); if (!mustReplaceCredentials) password = ''; @@ -146,6 +150,32 @@ } } + async function loadBackups() { + backupError = ''; + try { + const response = await fetch(`${serverAddress}/api/v1/admin/backups`, { headers: { authorization: `Bearer ${sessionToken}` } }); + if (!response.ok) throw new Error('Backup list unavailable.'); + backups = await response.json(); + } catch (error) { + backupError = error instanceof Error ? error.message : 'Backup list unavailable.'; + } + } + + async function runBackup() { + backupBusy = true; + backupError = ''; + try { + const response = await fetch(`${serverAddress}/api/v1/admin/backups/run`, { method: 'POST', headers: { authorization: `Bearer ${sessionToken}` } }); + const result: { status: string; reason?: string | null } = await response.json(); + if (!response.ok || result.status !== 'ok') throw new Error(result.reason || 'Backup failed.'); + await loadBackups(); + } catch (error) { + backupError = error instanceof Error ? error.message : 'Backup failed.'; + } finally { + backupBusy = false; + } + } + async function replaceCredentials() { authBusy = true; authError = ''; @@ -701,7 +731,7 @@
◇TRIXIE
@@ -785,6 +815,8 @@
BOT COMMANDS
PUBLIC COMMAND CATALOG
{#if botCommandError}
{botCommandError}
{:else if botCommands.length}
{#each botCommands as command}
!{command.name}{command.description}{#if command.tags.length}
{command.tags.join(' · ')}
{/if}
{command.permission_level.toUpperCase()}{#if command.cooldown_seconds}{command.cooldown_seconds}s / {command.cooldown_scope}{/if}
{/each}
{:else}
No enabled commandsCreate commands on the server to make them available here.
{/if}
SECRETS MANAGEMENT
Values are encrypted and cannot be read after saving.
{#if botSecrets.length}
{#each botSecrets as secret}
{secret.name}{secret.description || 'No description'}{#if secret.used_by.length}
USED BY: {secret.used_by.join(', ')}
{/if}
{/each}
{:else}
There are no secrets yet.
{/if}{#if secretError}
{secretError}
{/if}
{:else if activeView === 'Overlays'}
OVERLAYS
{#if selectedOverlayId || overlayName}
LIVE PREVIEW
{#if overlayError}
{overlayError}
{/if}{:else}
Select or create an overlayDrafts autosave when explicitly saved; publishing controls the browser source.
{/if}
+ {:else if activeView === 'Settings'} +
SERVER SETTINGS
DATABASE BACKUPS

Daily backups use the server's local schedule. Restore remains a destructive recovery workflow and is not started from this list.

{#if backupError}
{backupError}
{/if}{#if backups.length}
{#each backups as backup}
{backup.filename}{Math.round(backup.size_bytes / 1024)} KiB{backup.modified_at ? new Date(backup.modified_at).toLocaleString() : 'UNKNOWN TIME'}
{/each}
{:else}
No backup archives are available.
{/if}
{:else}
STREAM PREVIEW⋮
{#if playerSrc}{:else}
STREAM OFFLINE
CONNECT TWITCH TO BEGIN
{/if}