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}
+
+ {/if}
+
+
+
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 @@
+
+
+
+
+
+
+