From b6c6887842b03a1d4570461f95534e10d55c8b49 Mon Sep 17 00:00:00 2001 From: 3252a8 <3252a8@proton.me> Date: Wed, 27 May 2026 23:22:20 +0300 Subject: [PATCH] chore: improve backup admin controls --- .../admin/sections/AppearanceSection.svelte | 16 +- .../src/admin/sections/BackupsSection.svelte | 261 +++++++++++++----- .../src/admin/sections/UserDetailModal.svelte | 12 +- .../src/lib/components/ui/checkbox.svelte | 78 ++++++ frontend/src/lib/components/ui/index.js | 3 + .../lib/components/ui/radio-group-item.svelte | 80 ++++++ .../src/lib/components/ui/radio-group.svelte | 36 +++ 7 files changed, 393 insertions(+), 93 deletions(-) create mode 100644 frontend/src/lib/components/ui/checkbox.svelte create mode 100644 frontend/src/lib/components/ui/radio-group-item.svelte create mode 100644 frontend/src/lib/components/ui/radio-group.svelte diff --git a/frontend/src/admin/sections/AppearanceSection.svelte b/frontend/src/admin/sections/AppearanceSection.svelte index 016845a..6dab078 100644 --- a/frontend/src/admin/sections/AppearanceSection.svelte +++ b/frontend/src/admin/sections/AppearanceSection.svelte @@ -6,6 +6,7 @@ AdminEmptyState, AdminSelect, } from "$components/patterns/admin/index.js"; + import { Checkbox } from "$components/ui/index.js"; import { Switch } from "$components/ui/primitives.js"; import { getContext, onDestroy, onMount } from "svelte"; @@ -343,9 +344,8 @@ } } - function toggleAdminTheme(event, theme) { - event.stopPropagation(); - themesStore.toggleAdminUse(theme.key, event.currentTarget.checked); + function toggleAdminTheme(theme, checked) { + themesStore.toggleAdminUse(theme.key, checked); } function setThemeAccent(theme, value) { @@ -675,11 +675,11 @@ /> @@ -855,10 +855,6 @@ font-size: 13px; } - .admin-theme-card-option input[type="checkbox"] { - accent-color: var(--accent); - } - .admin-theme-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); diff --git a/frontend/src/admin/sections/BackupsSection.svelte b/frontend/src/admin/sections/BackupsSection.svelte index cc1fff7..e5115da 100644 --- a/frontend/src/admin/sections/BackupsSection.svelte +++ b/frontend/src/admin/sections/BackupsSection.svelte @@ -4,9 +4,11 @@ AdminBadge, AdminButton, AdminEmptyState, + AdminPagination, AdminTable, AdminTableSkeleton, } from "$components/patterns/admin/index.js"; + import { Checkbox, RadioGroup, RadioGroupItem } from "$components/ui/index.js"; import { CheckCircle2, Database, @@ -16,15 +18,18 @@ TriangleAlert, Upload, } from "$components/ui/icons.js"; + import { Tooltip } from "$components/ui/primitives.js"; export let at = (key) => key; export let fmtDate = (value) => value; + const BACKUPS_PAGE_SIZE = 10; const backupsStore = getContext("backupsStore"); let selectedName = ""; let restoreDatabase = true; let restoreCompose = false; + let backupsPage = 0; let fileInput = null; $: ({ @@ -36,9 +41,18 @@ backupsRestoring, lastRestore, } = $backupsStore); - $: if (!selectedName && archives?.length) selectedName = archives[0].name; + $: totalArchives = archives?.length || 0; + $: backupsPageCount = Math.max(1, Math.ceil(totalArchives / BACKUPS_PAGE_SIZE)); + $: if (backupsPage > backupsPageCount - 1) backupsPage = backupsPageCount - 1; + $: backupsPageStart = backupsPage * BACKUPS_PAGE_SIZE; + $: pagedArchives = (archives || []).slice(backupsPageStart, backupsPageStart + BACKUPS_PAGE_SIZE); + $: if (!selectedName && archives?.length) { + selectedName = archives[0].name; + backupsPage = 0; + } $: if (selectedName && archives?.length && !archives.some((item) => item.name === selectedName)) { selectedName = archives[0].name; + backupsPage = 0; } $: selectedArchive = (archives || []).find((item) => item.name === selectedName) || null; $: if (selectedArchive && restoreDatabase && !selectedArchive.has_database) @@ -82,17 +96,45 @@ return parts.join(" + "); } + function selectArchive(name) { + selectedName = name; + } + + function focusArchivePage(name) { + const index = (archives || []).findIndex((item) => item.name === name); + if (index >= 0) backupsPage = Math.floor(index / BACKUPS_PAGE_SIZE); + } + + function warningsText(warnings) { + return (warnings || []).filter(Boolean).join("\n"); + } + + function paginationMeta() { + const end = Math.min(backupsPageStart + pagedArchives.length, totalArchives); + return at( + "backups_pagination_meta", + { from: backupsPageStart + 1, to: end, total: totalArchives }, + `${backupsPageStart + 1}-${end} / ${totalArchives}` + ); + } + async function uploadSelectedFile(event) { const file = event?.currentTarget?.files?.[0]; if (!file) return; const archive = await backupsStore.uploadArchive(file); - if (archive?.name) selectedName = archive.name; + if (archive?.name) { + selectedName = archive.name; + focusArchivePage(archive.name); + } event.currentTarget.value = ""; } async function createManualBackup() { const archive = await backupsStore.createBackup(); - if (archive?.name) selectedName = archive.name; + if (archive?.name) { + selectedName = archive.name; + focusArchivePage(archive.name); + } } async function restoreSelected() { @@ -167,19 +209,19 @@
@@ -335,12 +404,6 @@ font-size: 13px; } - .backups-check input { - width: 16px; - height: 16px; - margin: 0; - } - .backups-check.is-disabled { opacity: 0.55; } @@ -362,6 +425,56 @@ gap: 6px; } + :global(.backups-archive-radio-group.ui-radio-group) { + display: block; + } + + :global(.backups-radio.ui-radio-item) { + margin-inline: auto; + } + + :global(.backups-warning-trigger) { + display: inline-flex; + align-items: center; + gap: 4px; + min-height: 24px; + padding: 2px 7px; + border: 1px solid var(--warning-border); + border-radius: 999px; + background: var(--warning-soft); + color: var(--warning-text); + font: inherit; + font-size: 11px; + font-weight: 600; + cursor: help; + outline: none; + } + + :global(.backups-warning-trigger:focus-visible) { + box-shadow: 0 0 0 3px color-mix(in srgb, var(--warning) 22%, transparent); + } + + :global(.backups-warning-tooltip) { + z-index: 120; + display: grid; + gap: 6px; + max-width: min(440px, calc(100vw - 32px)); + padding: 10px 12px; + border: 1px solid var(--admin-border); + border-radius: 10px; + background: var(--admin-surface); + color: var(--admin-text); + box-shadow: var(--shadow-popover); + font-size: 12px; + line-height: 1.4; + } + + :global(.backups-warning-tooltip) p { + margin: 0; + white-space: pre-wrap; + overflow-wrap: anywhere; + } + @media (max-width: 760px) { .backups-restore-body { grid-template-columns: minmax(0, 1fr); diff --git a/frontend/src/admin/sections/UserDetailModal.svelte b/frontend/src/admin/sections/UserDetailModal.svelte index 3a6fe23..a18ce7e 100644 --- a/frontend/src/admin/sections/UserDetailModal.svelte +++ b/frontend/src/admin/sections/UserDetailModal.svelte @@ -1,5 +1,6 @@ + + + {#if checked || indeterminate} + + + diff --git a/frontend/src/lib/components/ui/index.js b/frontend/src/lib/components/ui/index.js index 10341cc..d331437 100644 --- a/frontend/src/lib/components/ui/index.js +++ b/frontend/src/lib/components/ui/index.js @@ -1,9 +1,12 @@ export { default as AttentionDot } from "./attention-dot.svelte"; export { default as Badge } from "./badge.svelte"; export { default as Button } from "./button.svelte"; +export { default as Checkbox } from "./checkbox.svelte"; export { default as Dialog } from "./dialog.svelte"; export { default as Input } from "./input.svelte"; export { default as LegacyCard } from "./card.svelte"; +export { default as RadioGroup } from "./radio-group.svelte"; +export { default as RadioGroupItem } from "./radio-group-item.svelte"; export { default as Skeleton } from "./skeleton.svelte"; export { default as Spinner } from "./spinner.svelte"; export { default as ScrollArea } from "./scroll-area.svelte"; diff --git a/frontend/src/lib/components/ui/radio-group-item.svelte b/frontend/src/lib/components/ui/radio-group-item.svelte new file mode 100644 index 0000000..0ee32d9 --- /dev/null +++ b/frontend/src/lib/components/ui/radio-group-item.svelte @@ -0,0 +1,80 @@ + + + + + + + + diff --git a/frontend/src/lib/components/ui/radio-group.svelte b/frontend/src/lib/components/ui/radio-group.svelte new file mode 100644 index 0000000..0e2e453 --- /dev/null +++ b/frontend/src/lib/components/ui/radio-group.svelte @@ -0,0 +1,36 @@ + + + + + + +