@futo-org/backups-orchestrator-ui 0.1.71
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +41 -0
- package/dist/components/backends/BackendItem.svelte +64 -0
- package/dist/components/backends/BackendItem.svelte.d.ts +10 -0
- package/dist/components/backends/BackendsList.svelte +73 -0
- package/dist/components/backends/BackendsList.svelte.d.ts +7 -0
- package/dist/components/backends/CreateLocalBackend.svelte +46 -0
- package/dist/components/backends/CreateLocalBackend.svelte.d.ts +7 -0
- package/dist/components/backends/OAuthDeviceFlow.svelte +78 -0
- package/dist/components/backends/OAuthDeviceFlow.svelte.d.ts +9 -0
- package/dist/components/backups/BackupItem.svelte +59 -0
- package/dist/components/backups/BackupItem.svelte.d.ts +7 -0
- package/dist/components/backups/BackupsList.svelte +82 -0
- package/dist/components/backups/BackupsList.svelte.d.ts +8 -0
- package/dist/components/backups/dialogs/ConfigureRepositoryModal.svelte +102 -0
- package/dist/components/backups/dialogs/ConfigureRepositoryModal.svelte.d.ts +8 -0
- package/dist/components/backups/dialogs/CreateRepositoryModal.svelte +59 -0
- package/dist/components/backups/dialogs/CreateRepositoryModal.svelte.d.ts +6 -0
- package/dist/components/backups/dialogs/ImportRepositoryModal.svelte +62 -0
- package/dist/components/backups/dialogs/ImportRepositoryModal.svelte.d.ts +8 -0
- package/dist/components/backups/dialogs/RestoreSnapshotModal.svelte +109 -0
- package/dist/components/backups/dialogs/RestoreSnapshotModal.svelte.d.ts +8 -0
- package/dist/components/backups/dialogs/ViewLogModal.svelte +208 -0
- package/dist/components/backups/dialogs/ViewLogModal.svelte.d.ts +7 -0
- package/dist/components/backups/metrics-history/MetricsHistoryModal.svelte +166 -0
- package/dist/components/backups/metrics-history/MetricsHistoryModal.svelte.d.ts +8 -0
- package/dist/components/backups/run-history/RepositoryRunHistory.svelte +39 -0
- package/dist/components/backups/run-history/RepositoryRunHistory.svelte.d.ts +7 -0
- package/dist/components/backups/run-history/RepositoryRunHistoryItem.svelte +45 -0
- package/dist/components/backups/run-history/RepositoryRunHistoryItem.svelte.d.ts +7 -0
- package/dist/components/backups/run-history/RunHistoryModal.svelte +18 -0
- package/dist/components/backups/run-history/RunHistoryModal.svelte.d.ts +8 -0
- package/dist/components/backups/snapshots-list/RepositorySnapshotsList.svelte +53 -0
- package/dist/components/backups/snapshots-list/RepositorySnapshotsList.svelte.d.ts +7 -0
- package/dist/components/backups/snapshots-list/RepositorySnapshotsListItem.svelte +41 -0
- package/dist/components/backups/snapshots-list/RepositorySnapshotsListItem.svelte.d.ts +8 -0
- package/dist/components/backups/snapshots-list/SnapshotsListModal.svelte +18 -0
- package/dist/components/backups/snapshots-list/SnapshotsListModal.svelte.d.ts +8 -0
- package/dist/components/dashboard/Dashboard.svelte +52 -0
- package/dist/components/dashboard/Dashboard.svelte.d.ts +9 -0
- package/dist/components/dashboard/DashboardAvgBackupTime.svelte +34 -0
- package/dist/components/dashboard/DashboardAvgBackupTime.svelte.d.ts +7 -0
- package/dist/components/dashboard/DashboardBackupHealth.svelte +91 -0
- package/dist/components/dashboard/DashboardBackupHealth.svelte.d.ts +9 -0
- package/dist/components/dashboard/DashboardCurrentUsage.svelte +10 -0
- package/dist/components/dashboard/DashboardCurrentUsage.svelte.d.ts +18 -0
- package/dist/components/dashboard/DashboardDailyBackupTime.svelte +31 -0
- package/dist/components/dashboard/DashboardDailyBackupTime.svelte.d.ts +7 -0
- package/dist/components/dashboard/DashboardInstall.svelte +15 -0
- package/dist/components/dashboard/DashboardInstall.svelte.d.ts +18 -0
- package/dist/components/dashboard/DashboardRecentBackups.svelte +104 -0
- package/dist/components/dashboard/DashboardRecentBackups.svelte.d.ts +8 -0
- package/dist/components/dashboard/DashboardTotalStored.svelte +27 -0
- package/dist/components/dashboard/DashboardTotalStored.svelte.d.ts +7 -0
- package/dist/components/integrations/immich/ImmichBackupsPage.svelte +14 -0
- package/dist/components/integrations/immich/ImmichBackupsPage.svelte.d.ts +6 -0
- package/dist/components/integrations/immich/ImmichConfigureBackup.svelte +402 -0
- package/dist/components/integrations/immich/ImmichConfigureBackup.svelte.d.ts +9 -0
- package/dist/components/integrations/immich/ImmichConfirmDefaultBackup.svelte +80 -0
- package/dist/components/integrations/immich/ImmichConfirmDefaultBackup.svelte.d.ts +8 -0
- package/dist/components/integrations/immich/ImmichManageBackup.svelte +77 -0
- package/dist/components/integrations/immich/ImmichManageBackup.svelte.d.ts +3 -0
- package/dist/components/integrations/immich/ImmichManageBackupOverview.svelte +100 -0
- package/dist/components/integrations/immich/ImmichManageBackupOverview.svelte.d.ts +8 -0
- package/dist/components/integrations/immich/ImmichOnboardingRestoreFlow.svelte +75 -0
- package/dist/components/integrations/immich/ImmichOnboardingRestoreFlow.svelte.d.ts +7 -0
- package/dist/components/integrations/immich/ImmichOnboardingSetupFlow.svelte +113 -0
- package/dist/components/integrations/immich/ImmichOnboardingSetupFlow.svelte.d.ts +8 -0
- package/dist/components/onboarding/OnboardingGate.svelte +48 -0
- package/dist/components/onboarding/OnboardingGate.svelte.d.ts +9 -0
- package/dist/components/onboarding/RecoveryKeyDisplay.svelte +103 -0
- package/dist/components/onboarding/RecoveryKeyDisplay.svelte.d.ts +6 -0
- package/dist/components/onboarding/SampleOnboarding.svelte +98 -0
- package/dist/components/onboarding/SampleOnboarding.svelte.d.ts +9 -0
- package/dist/components/onboarding/dialogs/BackupsRecoveryKeyModal.svelte +43 -0
- package/dist/components/onboarding/dialogs/BackupsRecoveryKeyModal.svelte.d.ts +6 -0
- package/dist/components/onboarding/restore-point-flow/RestorePointFlow.svelte +96 -0
- package/dist/components/onboarding/restore-point-flow/RestorePointFlow.svelte.d.ts +8 -0
- package/dist/components/onboarding/restore-point-flow/RestorePointFlow2SelectSnapshot.svelte +83 -0
- package/dist/components/onboarding/restore-point-flow/RestorePointFlow2SelectSnapshot.svelte.d.ts +9 -0
- package/dist/components/onboarding/restore-point-flow/RestorePointFlow3ConfirmRestore.svelte +118 -0
- package/dist/components/onboarding/restore-point-flow/RestorePointFlow3ConfirmRestore.svelte.d.ts +10 -0
- package/dist/components/onboarding/restore-point-flow/RestorePointFlow4Restore.svelte +59 -0
- package/dist/components/onboarding/restore-point-flow/RestorePointFlow4Restore.svelte.d.ts +8 -0
- package/dist/components/onboarding/stages/OnboardingStageBackupServices.svelte +82 -0
- package/dist/components/onboarding/stages/OnboardingStageBackupServices.svelte.d.ts +8 -0
- package/dist/components/onboarding/stages/OnboardingStageKeyConfirm.svelte +56 -0
- package/dist/components/onboarding/stages/OnboardingStageKeyConfirm.svelte.d.ts +9 -0
- package/dist/components/onboarding/stages/OnboardingStageKeyImport.svelte +57 -0
- package/dist/components/onboarding/stages/OnboardingStageKeyImport.svelte.d.ts +8 -0
- package/dist/components/onboarding/stages/OnboardingStageKeyIntro.svelte +50 -0
- package/dist/components/onboarding/stages/OnboardingStageKeyIntro.svelte.d.ts +7 -0
- package/dist/components/onboarding/stages/OnboardingStageKeySave.svelte +44 -0
- package/dist/components/onboarding/stages/OnboardingStageKeySave.svelte.d.ts +8 -0
- package/dist/components/onboarding/stages/OnboardingStageWelcome.svelte +56 -0
- package/dist/components/onboarding/stages/OnboardingStageWelcome.svelte.d.ts +9 -0
- package/dist/components/onboarding/stages/SampleCreateFirstBackup.svelte +43 -0
- package/dist/components/onboarding/stages/SampleCreateFirstBackup.svelte.d.ts +7 -0
- package/dist/components/onboarding/stages/SampleCreateFirstSchedule.svelte +49 -0
- package/dist/components/onboarding/stages/SampleCreateFirstSchedule.svelte.d.ts +7 -0
- package/dist/components/schedules/RepositoryPicker.svelte +105 -0
- package/dist/components/schedules/RepositoryPicker.svelte.d.ts +6 -0
- package/dist/components/schedules/ScheduleItem.svelte +47 -0
- package/dist/components/schedules/ScheduleItem.svelte.d.ts +8 -0
- package/dist/components/schedules/ScheduleList.svelte +51 -0
- package/dist/components/schedules/ScheduleList.svelte.d.ts +3 -0
- package/dist/components/schedules/dialogs/ConfigureScheduleModal.svelte +48 -0
- package/dist/components/schedules/dialogs/ConfigureScheduleModal.svelte.d.ts +8 -0
- package/dist/components/schedules/dialogs/CreateScheduleModal.svelte +43 -0
- package/dist/components/schedules/dialogs/CreateScheduleModal.svelte.d.ts +6 -0
- package/dist/components/test/ImmichTestUi.svelte +183 -0
- package/dist/components/test/ImmichTestUi.svelte.d.ts +6 -0
- package/dist/components/test/TestUi.svelte +134 -0
- package/dist/components/test/TestUi.svelte.d.ts +6 -0
- package/dist/components/test/dashboard/ActiveJobs.svelte +380 -0
- package/dist/components/test/dashboard/ActiveJobs.svelte.d.ts +3 -0
- package/dist/components/test/dashboard/BackupHealth.svelte +95 -0
- package/dist/components/test/dashboard/BackupHealth.svelte.d.ts +7 -0
- package/dist/components/test/dashboard/BackupStats.svelte +117 -0
- package/dist/components/test/dashboard/BackupStats.svelte.d.ts +8 -0
- package/dist/components/test/dashboard/Dashboard.svelte +76 -0
- package/dist/components/test/dashboard/Dashboard.svelte.d.ts +6 -0
- package/dist/components/test/dashboard/RecentBackups.svelte +96 -0
- package/dist/components/test/dashboard/RecentBackups.svelte.d.ts +8 -0
- package/dist/components/ui/PageLayout.svelte +67 -0
- package/dist/components/ui/PageLayout.svelte.d.ts +10 -0
- package/dist/components/ui/PathListField.svelte +83 -0
- package/dist/components/ui/PathListField.svelte.d.ts +17 -0
- package/dist/components/ui/PathPickerField.svelte +74 -0
- package/dist/components/ui/PathPickerField.svelte.d.ts +15 -0
- package/dist/components/ui/PathPickerModal.svelte +219 -0
- package/dist/components/ui/PathPickerModal.svelte.d.ts +14 -0
- package/dist/components/ui/StackList.svelte +30 -0
- package/dist/components/ui/StackList.svelte.d.ts +30 -0
- package/dist/components/ui/StackListItem.svelte +64 -0
- package/dist/components/ui/StackListItem.svelte.d.ts +13 -0
- package/dist/components/ui/VisualisationGauge.svelte +25 -0
- package/dist/components/ui/VisualisationGauge.svelte.d.ts +10 -0
- package/dist/components/ui/VisualisationSegmentedBar.svelte +48 -0
- package/dist/components/ui/VisualisationSegmentedBar.svelte.d.ts +14 -0
- package/dist/components/util/OnEvents.svelte +31 -0
- package/dist/components/util/OnEvents.svelte.d.ts +7 -0
- package/dist/components/util/RelativeTime.svelte +21 -0
- package/dist/components/util/RelativeTime.svelte.d.ts +6 -0
- package/dist/components/util/Suspense.svelte +21 -0
- package/dist/components/util/Suspense.svelte.d.ts +29 -0
- package/dist/components/util/TimedButton.svelte +37 -0
- package/dist/components/util/TimedButton.svelte.d.ts +7 -0
- package/dist/components/util/YuccaContext.svelte +26 -0
- package/dist/components/util/YuccaContext.svelte.d.ts +8 -0
- package/dist/events.d.ts +6 -0
- package/dist/events.js +47 -0
- package/dist/fetch-client.d.ts +289 -0
- package/dist/fetch-client.js +233 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +28 -0
- package/dist/options.d.ts +5 -0
- package/dist/options.js +6 -0
- package/dist/providers.d.ts +11 -0
- package/dist/providers.js +35 -0
- package/dist/query-client.d.ts +2 -0
- package/dist/query-client.js +2 -0
- package/dist/services/backend.service.d.ts +18 -0
- package/dist/services/backend.service.js +61 -0
- package/dist/services/filesystem.service.d.ts +2 -0
- package/dist/services/filesystem.service.js +11 -0
- package/dist/services/immich.integration.service.d.ts +6 -0
- package/dist/services/immich.integration.service.js +24 -0
- package/dist/services/integrations.service.d.ts +13 -0
- package/dist/services/integrations.service.js +42 -0
- package/dist/services/log.service.svelte.d.ts +53 -0
- package/dist/services/log.service.svelte.js +93 -0
- package/dist/services/metricsHistory.service.d.ts +4 -0
- package/dist/services/metricsHistory.service.js +12 -0
- package/dist/services/onboarding.service.d.ts +11 -0
- package/dist/services/onboarding.service.js +56 -0
- package/dist/services/repository.service.d.ts +45 -0
- package/dist/services/repository.service.js +157 -0
- package/dist/services/runHistory.service.d.ts +26 -0
- package/dist/services/runHistory.service.js +54 -0
- package/dist/services/schedule.service.d.ts +35 -0
- package/dist/services/schedule.service.js +126 -0
- package/dist/services/snapshot.service.d.ts +29 -0
- package/dist/services/snapshot.service.js +108 -0
- package/dist/services/task.service.d.ts +3 -0
- package/dist/services/task.service.js +20 -0
- package/dist/utils/actions.d.ts +2 -0
- package/dist/utils/actions.js +3 -0
- package/dist/utils/format.d.ts +2 -0
- package/dist/utils/format.js +24 -0
- package/dist/utils/handle-error.d.ts +9 -0
- package/dist/utils/handle-error.js +42 -0
- package/package.json +79 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Suspense from "../../util/Suspense.svelte";
|
|
3
|
+
import * as sdk from "../../../fetch-client";
|
|
4
|
+
import {
|
|
5
|
+
useConfigureImmichIntegration,
|
|
6
|
+
useIntegrations,
|
|
7
|
+
} from "../../../services/integrations.service";
|
|
8
|
+
import { useRepositories } from "../../../services/repository.service";
|
|
9
|
+
import { useSchedules } from "../../../services/schedule.service";
|
|
10
|
+
import {
|
|
11
|
+
Button,
|
|
12
|
+
Checkbox,
|
|
13
|
+
Field,
|
|
14
|
+
FormModal,
|
|
15
|
+
Heading,
|
|
16
|
+
HStack,
|
|
17
|
+
Input,
|
|
18
|
+
Select,
|
|
19
|
+
Stack,
|
|
20
|
+
Switch,
|
|
21
|
+
Text,
|
|
22
|
+
} from "@immich/ui";
|
|
23
|
+
import validateCron from "cron-validate";
|
|
24
|
+
import cronstrue from "cronstrue";
|
|
25
|
+
import { SvelteSet } from "svelte/reactivity";
|
|
26
|
+
|
|
27
|
+
type Props = {
|
|
28
|
+
onFinish?: () => void;
|
|
29
|
+
onCancel?: () => void;
|
|
30
|
+
onClose?: () => void;
|
|
31
|
+
backendId?: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
let { onFinish, onCancel, onClose }: Props = $props();
|
|
35
|
+
|
|
36
|
+
const query = useIntegrations();
|
|
37
|
+
const schedulesQuery = useSchedules();
|
|
38
|
+
const repositoriesQuery = useRepositories();
|
|
39
|
+
|
|
40
|
+
type RetentionChoice = {
|
|
41
|
+
key: string;
|
|
42
|
+
label: string;
|
|
43
|
+
policy: sdk.RetentionPolicyDto | null;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const retentionChoices: RetentionChoice[] = [
|
|
47
|
+
{ key: "15d", label: "After 15 days", policy: { keepWithin: "15d" } },
|
|
48
|
+
{ key: "30d", label: "After 30 days", policy: { keepWithin: "30d" } },
|
|
49
|
+
{ key: "60d", label: "After 60 days", policy: { keepWithin: "60d" } },
|
|
50
|
+
{ key: "90d", label: "After 90 days", policy: { keepWithin: "90d" } },
|
|
51
|
+
{ key: "2", label: "Keep latest two backups", policy: { keepLast: 2 } },
|
|
52
|
+
{ key: "never", label: "Never (keep all backups)", policy: null },
|
|
53
|
+
];
|
|
54
|
+
const defaultRetentionKey = "60d";
|
|
55
|
+
|
|
56
|
+
let name = $state("Immich");
|
|
57
|
+
let worm = $state(false);
|
|
58
|
+
let backupConfiguration = $state(true);
|
|
59
|
+
let librariesMode = $state<"all" | "none" | "some">("all");
|
|
60
|
+
let retentionKey = $state<string>(defaultRetentionKey);
|
|
61
|
+
|
|
62
|
+
let cron = $state("0 3 * * *");
|
|
63
|
+
let scheduleMode = $state<"daily" | "custom">("daily");
|
|
64
|
+
let scheduleHour = $state(3);
|
|
65
|
+
let scheduleMinute = $state(0);
|
|
66
|
+
|
|
67
|
+
const selectedFolders = new SvelteSet<string>();
|
|
68
|
+
const selectedLibraries = new SvelteSet<string>();
|
|
69
|
+
|
|
70
|
+
type FolderItem = { label: string; description?: string; folders: string[] };
|
|
71
|
+
|
|
72
|
+
let populated = false;
|
|
73
|
+
|
|
74
|
+
$effect(() => {
|
|
75
|
+
if (populated || !query.data?.immichState) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
populated = true;
|
|
79
|
+
|
|
80
|
+
const integration = query.data.immichIntegration;
|
|
81
|
+
if (integration) {
|
|
82
|
+
const config = integration.configuration;
|
|
83
|
+
backupConfiguration = config.backupConfiguration;
|
|
84
|
+
|
|
85
|
+
const repository = repositoriesQuery.data?.find(
|
|
86
|
+
(entry) => entry.id === integration.id,
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const policy = repository?.configuration?.retentionPolicy ?? null;
|
|
90
|
+
const matched = retentionChoices.find(
|
|
91
|
+
(choice) => JSON.stringify(choice.policy) === JSON.stringify(policy),
|
|
92
|
+
);
|
|
93
|
+
retentionKey = matched?.key ?? defaultRetentionKey;
|
|
94
|
+
|
|
95
|
+
if (config.libraries === "all") {
|
|
96
|
+
librariesMode = "all";
|
|
97
|
+
} else if (config.libraries.length === 0) {
|
|
98
|
+
librariesMode = "none";
|
|
99
|
+
} else {
|
|
100
|
+
librariesMode = "some";
|
|
101
|
+
|
|
102
|
+
for (const id of config.libraries) {
|
|
103
|
+
selectedLibraries.add(id);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
for (const folder of config.dataFolders) {
|
|
108
|
+
selectedFolders.add(folder);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const schedule = schedulesQuery.data?.find(
|
|
112
|
+
(schedule) => schedule.id === integration.scheduleId,
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
if (schedule) {
|
|
116
|
+
cron = schedule.cron;
|
|
117
|
+
|
|
118
|
+
const match = /(\d+) (\d+) \* \* \*/.exec(cron);
|
|
119
|
+
if (match) {
|
|
120
|
+
scheduleMode = "daily";
|
|
121
|
+
scheduleHour = parseInt(match[2]);
|
|
122
|
+
scheduleMinute = parseInt(match[1]);
|
|
123
|
+
} else {
|
|
124
|
+
scheduleMode = "custom";
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
for (const folder of query.data.immichState.dataFolders) {
|
|
129
|
+
selectedFolders.add(folder);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const effectiveCron = $derived(
|
|
135
|
+
scheduleMode === "daily" ? `${scheduleMinute} ${scheduleHour} * * *` : cron,
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const mutation = useConfigureImmichIntegration();
|
|
139
|
+
|
|
140
|
+
const onSubmit = () => {
|
|
141
|
+
selectedFolders.add("backups");
|
|
142
|
+
|
|
143
|
+
mutation.mutate(
|
|
144
|
+
{
|
|
145
|
+
name,
|
|
146
|
+
worm,
|
|
147
|
+
cron: effectiveCron,
|
|
148
|
+
dataFolders: [...selectedFolders],
|
|
149
|
+
backupConfiguration,
|
|
150
|
+
libraries:
|
|
151
|
+
librariesMode === "all"
|
|
152
|
+
? "all"
|
|
153
|
+
: librariesMode === "none"
|
|
154
|
+
? []
|
|
155
|
+
: [...selectedLibraries],
|
|
156
|
+
retentionPolicy: worm
|
|
157
|
+
? null
|
|
158
|
+
: (retentionChoices.find((choice) => choice.key === retentionKey)
|
|
159
|
+
?.policy ?? null),
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
onSuccess: () => {
|
|
163
|
+
onFinish?.();
|
|
164
|
+
onClose?.();
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
function applicableFolders(item: FolderItem, available: string[]): string[] {
|
|
171
|
+
return item.folders.filter((folder) => available.includes(folder));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function isItemChecked(item: FolderItem, available: string[]): boolean {
|
|
175
|
+
const folders = applicableFolders(item, available);
|
|
176
|
+
return (
|
|
177
|
+
folders.length > 0 &&
|
|
178
|
+
folders.every((folder) => selectedFolders.has(folder))
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function toggleItem(item: FolderItem, available: string[]) {
|
|
183
|
+
const folders = applicableFolders(item, available);
|
|
184
|
+
const allOn = folders.every((folder) => selectedFolders.has(folder));
|
|
185
|
+
for (const folder of folders) {
|
|
186
|
+
if (allOn) {
|
|
187
|
+
selectedFolders.delete(folder);
|
|
188
|
+
} else {
|
|
189
|
+
selectedFolders.add(folder);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function toggleLibrary(id: string) {
|
|
195
|
+
if (selectedLibraries.has(id)) {
|
|
196
|
+
selectedLibraries.delete(id);
|
|
197
|
+
} else {
|
|
198
|
+
selectedLibraries.add(id);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
</script>
|
|
202
|
+
|
|
203
|
+
<FormModal
|
|
204
|
+
title="Backup settings"
|
|
205
|
+
size="large"
|
|
206
|
+
disabled={!validateCron(effectiveCron) ||
|
|
207
|
+
name.length === 0 ||
|
|
208
|
+
mutation.isPending}
|
|
209
|
+
onClose={() => {
|
|
210
|
+
onCancel?.();
|
|
211
|
+
onClose?.();
|
|
212
|
+
}}
|
|
213
|
+
{onSubmit}
|
|
214
|
+
>
|
|
215
|
+
<Stack gap={6}>
|
|
216
|
+
<Field label="Name">
|
|
217
|
+
<Input bind:value={name} />
|
|
218
|
+
</Field>
|
|
219
|
+
|
|
220
|
+
<Suspense {query}>
|
|
221
|
+
{#snippet children({ immichState: immich })}
|
|
222
|
+
<Stack gap={2}>
|
|
223
|
+
<Heading size="tiny">What to back up</Heading>
|
|
224
|
+
|
|
225
|
+
{#each [{ label: "Photos and videos", description: "Your media uploaded directly to Immich.", folders: ["upload", "profile", "library"], recommended: true }, { label: "Database backups", description: "Albums, faces, and metadata. You'll need this to restore.", folders: ["backups"], required: true }, { label: "Thumbnails and previews", description: "Generated photo previews, can be recreated later.", folders: ["thumbs"], regenerate: true }, { label: "Encoded videos", description: "Generated video previews, can be recreated later.", folders: ["encoded-video"], regenerate: true }] as item (item.label)}
|
|
226
|
+
<label class="select-none">
|
|
227
|
+
<Stack direction="row">
|
|
228
|
+
<Checkbox
|
|
229
|
+
checked={item.required ||
|
|
230
|
+
isItemChecked(item, immich!.dataFolders)}
|
|
231
|
+
onCheckedChange={() => toggleItem(item, immich!.dataFolders)}
|
|
232
|
+
disabled={item.required}
|
|
233
|
+
/>
|
|
234
|
+
<Stack gap={0}>
|
|
235
|
+
<Text>{item.label}</Text>
|
|
236
|
+
{#if item.description}
|
|
237
|
+
<Text size="small" color="secondary"
|
|
238
|
+
>{item.description}</Text
|
|
239
|
+
>
|
|
240
|
+
{/if}
|
|
241
|
+
{#if item.recommended && !isItemChecked(item, immich!.dataFolders)}
|
|
242
|
+
<Text size="small" color="danger"
|
|
243
|
+
>It is highly recommended you back this up!</Text
|
|
244
|
+
>
|
|
245
|
+
{/if}
|
|
246
|
+
{#if item.regenerate && !isItemChecked(item, immich!.dataFolders)}
|
|
247
|
+
<Text size="small" color="warning"
|
|
248
|
+
>When restoring, you will see broken previews, use the
|
|
249
|
+
admin panel to regenerate them.</Text
|
|
250
|
+
>
|
|
251
|
+
{/if}
|
|
252
|
+
</Stack>
|
|
253
|
+
</Stack>
|
|
254
|
+
</label>
|
|
255
|
+
{/each}
|
|
256
|
+
|
|
257
|
+
<label class="select-none">
|
|
258
|
+
<Stack direction="row">
|
|
259
|
+
<Checkbox bind:checked={backupConfiguration} />
|
|
260
|
+
<Stack gap={0}>
|
|
261
|
+
<Text>Backup configuration</Text>
|
|
262
|
+
<Text size="small" color="secondary"
|
|
263
|
+
>Saves these settings too.</Text
|
|
264
|
+
>
|
|
265
|
+
{#if !backupConfiguration}
|
|
266
|
+
<Text size="small" color="warning"
|
|
267
|
+
>You will need to reconfigure backups from scratch after
|
|
268
|
+
restoring.</Text
|
|
269
|
+
>
|
|
270
|
+
{/if}
|
|
271
|
+
</Stack>
|
|
272
|
+
</Stack>
|
|
273
|
+
</label>
|
|
274
|
+
</Stack>
|
|
275
|
+
|
|
276
|
+
<Stack gap={2}>
|
|
277
|
+
<Stack gap={0}>
|
|
278
|
+
<Heading size="tiny">External Libraries</Heading>
|
|
279
|
+
<Text size="small" color="secondary">
|
|
280
|
+
External libraries stored outside of Immich can also be included
|
|
281
|
+
in your backup. <br /> By default, all existing and future external
|
|
282
|
+
libraries will be included in your backup.
|
|
283
|
+
</Text>
|
|
284
|
+
</Stack>
|
|
285
|
+
|
|
286
|
+
<HStack fullWidth>
|
|
287
|
+
{#each [{ id: "all" as const, label: "All" }, { id: "none" as const, label: "None" }, { id: "some" as const, label: "Pick" }] as option (option.id)}
|
|
288
|
+
<Button
|
|
289
|
+
fullWidth
|
|
290
|
+
size="small"
|
|
291
|
+
variant={librariesMode === option.id ? "filled" : "outline"}
|
|
292
|
+
color={librariesMode === option.id ? "primary" : "secondary"}
|
|
293
|
+
onclick={() => (librariesMode = option.id)}
|
|
294
|
+
>
|
|
295
|
+
{option.label}
|
|
296
|
+
</Button>
|
|
297
|
+
{/each}
|
|
298
|
+
</HStack>
|
|
299
|
+
|
|
300
|
+
{#if librariesMode === "all"}
|
|
301
|
+
{#if immich!.libraries.length !== 0}
|
|
302
|
+
<Text size="small" color="secondary"
|
|
303
|
+
>Libraries included: {immich!.libraries
|
|
304
|
+
.map((item) => item.name)
|
|
305
|
+
.join(", ")}</Text
|
|
306
|
+
>
|
|
307
|
+
{/if}
|
|
308
|
+
{:else if librariesMode === "some"}
|
|
309
|
+
{#if immich!.libraries.length === 0}
|
|
310
|
+
<Text size="small" color="secondary"
|
|
311
|
+
>No external libraries found.</Text
|
|
312
|
+
>
|
|
313
|
+
{:else}
|
|
314
|
+
<Stack gap={1}>
|
|
315
|
+
{#each immich!.libraries as library (library.id)}
|
|
316
|
+
<label class="flex select-none items-center gap-2">
|
|
317
|
+
<Checkbox
|
|
318
|
+
checked={selectedLibraries.has(library.id)}
|
|
319
|
+
onCheckedChange={() => toggleLibrary(library.id)}
|
|
320
|
+
/>
|
|
321
|
+
<Text>{library.name}</Text>
|
|
322
|
+
</label>
|
|
323
|
+
{/each}
|
|
324
|
+
</Stack>
|
|
325
|
+
{/if}
|
|
326
|
+
{/if}
|
|
327
|
+
</Stack>
|
|
328
|
+
|
|
329
|
+
<Stack gap={2}>
|
|
330
|
+
<Heading size="tiny">Schedule</Heading>
|
|
331
|
+
|
|
332
|
+
<HStack fullWidth>
|
|
333
|
+
{#each [{ id: "daily" as const, label: "Every day" }, { id: "custom" as const, label: "Custom cron expression" }] as option (option.id)}
|
|
334
|
+
<Button
|
|
335
|
+
fullWidth
|
|
336
|
+
size="small"
|
|
337
|
+
variant={scheduleMode === option.id ? "filled" : "outline"}
|
|
338
|
+
color={scheduleMode === option.id ? "primary" : "secondary"}
|
|
339
|
+
onclick={() => (scheduleMode = option.id)}
|
|
340
|
+
>
|
|
341
|
+
{option.label}
|
|
342
|
+
</Button>
|
|
343
|
+
{/each}
|
|
344
|
+
</HStack>
|
|
345
|
+
|
|
346
|
+
{#if scheduleMode === "daily"}
|
|
347
|
+
<Field label="Time">
|
|
348
|
+
<Input
|
|
349
|
+
type="time"
|
|
350
|
+
value={`${String(scheduleHour).padStart(2, "0")}:${String(scheduleMinute).padStart(2, "0")}`}
|
|
351
|
+
oninput={(event) => {
|
|
352
|
+
const [hours, minutes] = (
|
|
353
|
+
event.currentTarget as HTMLInputElement
|
|
354
|
+
).value.split(":");
|
|
355
|
+
scheduleHour = Number(hours);
|
|
356
|
+
scheduleMinute = Number(minutes);
|
|
357
|
+
}}
|
|
358
|
+
/>
|
|
359
|
+
</Field>
|
|
360
|
+
{:else}
|
|
361
|
+
<Field label="Cron expression">
|
|
362
|
+
<Input bind:value={cron} placeholder="0 3 * * *" />
|
|
363
|
+
</Field>
|
|
364
|
+
|
|
365
|
+
<Text size="small" color="secondary"
|
|
366
|
+
>Your backups will run <span class="lowercase"
|
|
367
|
+
>{cronstrue.toString(cron, {
|
|
368
|
+
verbose: true,
|
|
369
|
+
})}</span
|
|
370
|
+
></Text
|
|
371
|
+
>
|
|
372
|
+
{/if}
|
|
373
|
+
</Stack>
|
|
374
|
+
{/snippet}
|
|
375
|
+
</Suspense>
|
|
376
|
+
|
|
377
|
+
<Stack gap={2}>
|
|
378
|
+
<Heading size="tiny">Advanced</Heading>
|
|
379
|
+
<Field
|
|
380
|
+
label="Write-only"
|
|
381
|
+
description="Once written, files can't be removed."
|
|
382
|
+
>
|
|
383
|
+
<Switch bind:checked={worm} />
|
|
384
|
+
</Field>
|
|
385
|
+
|
|
386
|
+
<Field
|
|
387
|
+
label="Delete old backups"
|
|
388
|
+
description="Older snapshots will be pruned automatically."
|
|
389
|
+
disabled={worm}
|
|
390
|
+
>
|
|
391
|
+
<Select
|
|
392
|
+
options={retentionChoices.map(({ key, label }) => ({
|
|
393
|
+
value: key,
|
|
394
|
+
label,
|
|
395
|
+
}))}
|
|
396
|
+
value={worm ? "never" : retentionKey}
|
|
397
|
+
onChange={(value) => (retentionKey = value)}
|
|
398
|
+
/>
|
|
399
|
+
</Field>
|
|
400
|
+
</Stack>
|
|
401
|
+
</Stack>
|
|
402
|
+
</FormModal>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
type Props = {
|
|
2
|
+
onFinish?: () => void;
|
|
3
|
+
onCancel?: () => void;
|
|
4
|
+
onClose?: () => void;
|
|
5
|
+
backendId?: string;
|
|
6
|
+
};
|
|
7
|
+
declare const ImmichConfigureBackup: import("svelte").Component<Props, {}, "">;
|
|
8
|
+
type ImmichConfigureBackup = ReturnType<typeof ImmichConfigureBackup>;
|
|
9
|
+
export default ImmichConfigureBackup;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { useConfigureImmichDefaults } from "../../../services/integrations.service";
|
|
3
|
+
import {
|
|
4
|
+
Button,
|
|
5
|
+
HStack,
|
|
6
|
+
Icon,
|
|
7
|
+
Modal,
|
|
8
|
+
ModalBody,
|
|
9
|
+
ModalFooter,
|
|
10
|
+
Stack,
|
|
11
|
+
Text,
|
|
12
|
+
} from "@immich/ui";
|
|
13
|
+
import {
|
|
14
|
+
mdiClockOutline,
|
|
15
|
+
mdiFolderMultipleOutline,
|
|
16
|
+
mdiImageMultipleOutline,
|
|
17
|
+
} from "@mdi/js";
|
|
18
|
+
|
|
19
|
+
type Props = {
|
|
20
|
+
onCustomize: () => void;
|
|
21
|
+
onConfirm: () => void;
|
|
22
|
+
onCancel: () => void;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const { onCustomize, onConfirm, onCancel }: Props = $props();
|
|
26
|
+
|
|
27
|
+
const mutation = useConfigureImmichDefaults();
|
|
28
|
+
|
|
29
|
+
const onCreate = () =>
|
|
30
|
+
mutation.mutate(undefined, { onSuccess: () => onConfirm() });
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<Modal title="Ready to back up Immich" size="small" onClose={onCancel}>
|
|
34
|
+
<ModalBody>
|
|
35
|
+
<Stack gap={3}>
|
|
36
|
+
<Text>
|
|
37
|
+
We'll create a backup with sensible defaults. You can change anything
|
|
38
|
+
later from the Immich backups page.
|
|
39
|
+
</Text>
|
|
40
|
+
|
|
41
|
+
<Stack>
|
|
42
|
+
<HStack>
|
|
43
|
+
<Icon
|
|
44
|
+
icon={mdiImageMultipleOutline}
|
|
45
|
+
class="shrink-0 place-self-start mt-1"
|
|
46
|
+
/>
|
|
47
|
+
<Text>Photos, videos, database, and configuration</Text>
|
|
48
|
+
</HStack>
|
|
49
|
+
<HStack>
|
|
50
|
+
<Icon
|
|
51
|
+
icon={mdiFolderMultipleOutline}
|
|
52
|
+
class="shrink-0 place-self-start mt-1"
|
|
53
|
+
/>
|
|
54
|
+
<Text>All external libraries</Text>
|
|
55
|
+
</HStack>
|
|
56
|
+
<HStack>
|
|
57
|
+
<Icon
|
|
58
|
+
icon={mdiClockOutline}
|
|
59
|
+
class="shrink-0 place-self-start mt-1"
|
|
60
|
+
/>
|
|
61
|
+
<Text>Runs every day at 3:00 AM</Text>
|
|
62
|
+
</HStack>
|
|
63
|
+
</Stack>
|
|
64
|
+
</Stack>
|
|
65
|
+
</ModalBody>
|
|
66
|
+
<ModalFooter>
|
|
67
|
+
<HStack gap={2}>
|
|
68
|
+
<Button
|
|
69
|
+
variant="ghost"
|
|
70
|
+
onclick={onCustomize}
|
|
71
|
+
disabled={mutation.isPending}
|
|
72
|
+
>
|
|
73
|
+
Customize
|
|
74
|
+
</Button>
|
|
75
|
+
<Button onclick={onCreate} loading={mutation.isPending}>
|
|
76
|
+
Create backup
|
|
77
|
+
</Button>
|
|
78
|
+
</HStack>
|
|
79
|
+
</ModalFooter>
|
|
80
|
+
</Modal>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
type Props = {
|
|
2
|
+
onCustomize: () => void;
|
|
3
|
+
onConfirm: () => void;
|
|
4
|
+
onCancel: () => void;
|
|
5
|
+
};
|
|
6
|
+
declare const ImmichConfirmDefaultBackup: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type ImmichConfirmDefaultBackup = ReturnType<typeof ImmichConfirmDefaultBackup>;
|
|
8
|
+
export default ImmichConfirmDefaultBackup;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import BackendsList from "../../backends/BackendsList.svelte";
|
|
3
|
+
import RepositoryRunHistory from "../../backups/run-history/RepositoryRunHistory.svelte";
|
|
4
|
+
import RepositorySnapshotsList from "../../backups/snapshots-list/RepositorySnapshotsList.svelte";
|
|
5
|
+
import PageLayout from "../../ui/PageLayout.svelte";
|
|
6
|
+
import OnEvents from "../../util/OnEvents.svelte";
|
|
7
|
+
import { getBackupPageActions } from "../../../services/immich.integration.service";
|
|
8
|
+
import {
|
|
9
|
+
useIntegrationEventHandler,
|
|
10
|
+
useIntegrations,
|
|
11
|
+
} from "../../../services/integrations.service";
|
|
12
|
+
import {
|
|
13
|
+
useRepositories,
|
|
14
|
+
useRepositoryEventHandler,
|
|
15
|
+
} from "../../../services/repository.service";
|
|
16
|
+
import {
|
|
17
|
+
useScheduleEventHandler,
|
|
18
|
+
useSchedules,
|
|
19
|
+
} from "../../../services/schedule.service";
|
|
20
|
+
import { Container, Stack } from "@immich/ui";
|
|
21
|
+
import ImmichManageBackupOverview from "./ImmichManageBackupOverview.svelte";
|
|
22
|
+
|
|
23
|
+
const schedules = useSchedules();
|
|
24
|
+
const repositories = useRepositories();
|
|
25
|
+
const integrations = useIntegrations();
|
|
26
|
+
|
|
27
|
+
const { onScheduleCreate, onScheduleUpdate, onScheduleDelete } =
|
|
28
|
+
useScheduleEventHandler();
|
|
29
|
+
const { onRepositoryCreate, onRepositoryUpdate, onRepositoryDelete } =
|
|
30
|
+
useRepositoryEventHandler();
|
|
31
|
+
const { onIntegrationUpdate } = useIntegrationEventHandler();
|
|
32
|
+
|
|
33
|
+
const schedule = $derived(
|
|
34
|
+
integrations.data?.immichIntegration
|
|
35
|
+
? schedules.data?.find(
|
|
36
|
+
(schedule) =>
|
|
37
|
+
schedule.id === integrations.data.immichIntegration!.scheduleId,
|
|
38
|
+
)
|
|
39
|
+
: undefined,
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const repository = $derived(
|
|
43
|
+
integrations.data?.immichIntegration
|
|
44
|
+
? repositories.data?.find(
|
|
45
|
+
(repository) =>
|
|
46
|
+
repository.id === integrations.data.immichIntegration!.id,
|
|
47
|
+
)
|
|
48
|
+
: undefined,
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const { ViewRecoveryKey, Configure, BackUpNow } = $derived(
|
|
52
|
+
getBackupPageActions(repository?.id),
|
|
53
|
+
);
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
<OnEvents
|
|
57
|
+
{onScheduleCreate}
|
|
58
|
+
{onScheduleUpdate}
|
|
59
|
+
{onScheduleDelete}
|
|
60
|
+
{onRepositoryCreate}
|
|
61
|
+
{onRepositoryUpdate}
|
|
62
|
+
{onRepositoryDelete}
|
|
63
|
+
{onIntegrationUpdate}
|
|
64
|
+
/>
|
|
65
|
+
|
|
66
|
+
<PageLayout title="Backups" actions={[ViewRecoveryKey, Configure, BackUpNow]}>
|
|
67
|
+
<Container size="large" center>
|
|
68
|
+
{#if repository && schedule}
|
|
69
|
+
<Stack class="mt-4" gap={8}>
|
|
70
|
+
<ImmichManageBackupOverview {repository} {schedule} />
|
|
71
|
+
<BackendsList {repository} />
|
|
72
|
+
<RepositoryRunHistory {repository} />
|
|
73
|
+
<RepositorySnapshotsList {repository} />
|
|
74
|
+
</Stack>
|
|
75
|
+
{/if}
|
|
76
|
+
</Container>
|
|
77
|
+
</PageLayout>
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import StackList from "../../ui/StackList.svelte";
|
|
3
|
+
import StackListItem from "../../ui/StackListItem.svelte";
|
|
4
|
+
import RelativeTime from "../../util/RelativeTime.svelte";
|
|
5
|
+
import type { LocalRepositoryDto, ScheduleDto } from "../../../fetch-client";
|
|
6
|
+
import {
|
|
7
|
+
handlePauseSchedule,
|
|
8
|
+
handleResumeSchedule,
|
|
9
|
+
} from "../../../services/schedule.service";
|
|
10
|
+
import {
|
|
11
|
+
FormatBytes,
|
|
12
|
+
Heading,
|
|
13
|
+
HStack,
|
|
14
|
+
Icon,
|
|
15
|
+
IconButton,
|
|
16
|
+
Stack,
|
|
17
|
+
Text,
|
|
18
|
+
} from "@immich/ui";
|
|
19
|
+
import {
|
|
20
|
+
mdiAlert,
|
|
21
|
+
mdiArchiveOutline,
|
|
22
|
+
mdiCheck,
|
|
23
|
+
mdiInformation,
|
|
24
|
+
mdiPauseCircleOutline,
|
|
25
|
+
mdiPlayCircleOutline,
|
|
26
|
+
} from "@mdi/js";
|
|
27
|
+
import cronstrue from "cronstrue";
|
|
28
|
+
|
|
29
|
+
type Props = {
|
|
30
|
+
repository: LocalRepositoryDto;
|
|
31
|
+
schedule: ScheduleDto;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const { repository, schedule }: Props = $props();
|
|
35
|
+
|
|
36
|
+
function togglePause() {
|
|
37
|
+
if (schedule!.paused) {
|
|
38
|
+
handleResumeSchedule(schedule!.id, "Immich");
|
|
39
|
+
} else {
|
|
40
|
+
handlePauseSchedule(schedule!.id, "Immich");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<StackList>
|
|
46
|
+
<StackListItem>
|
|
47
|
+
{#snippet icon()}
|
|
48
|
+
<Icon icon={mdiArchiveOutline} size="32px" />
|
|
49
|
+
{/snippet}
|
|
50
|
+
|
|
51
|
+
<HStack gap={4}>
|
|
52
|
+
<Stack gap={0}>
|
|
53
|
+
<Heading size="tiny">Your library</Heading>
|
|
54
|
+
<Text>
|
|
55
|
+
<FormatBytes bytes={repository.metrics.sizeBytes} /> ·
|
|
56
|
+
<span class="lowercase"
|
|
57
|
+
>{cronstrue.toString(schedule.cron, {
|
|
58
|
+
verbose: true,
|
|
59
|
+
})}</span
|
|
60
|
+
>
|
|
61
|
+
</Text>
|
|
62
|
+
</Stack>
|
|
63
|
+
</HStack>
|
|
64
|
+
|
|
65
|
+
{#snippet trailing()}
|
|
66
|
+
<IconButton
|
|
67
|
+
variant="ghost"
|
|
68
|
+
onclick={togglePause}
|
|
69
|
+
aria-label={schedule.paused ? "Resume backups" : "Pause backups"}
|
|
70
|
+
icon={schedule.paused ? mdiPlayCircleOutline : mdiPauseCircleOutline}
|
|
71
|
+
/>
|
|
72
|
+
{/snippet}
|
|
73
|
+
</StackListItem>
|
|
74
|
+
|
|
75
|
+
{#if repository.metrics.lastBackup}
|
|
76
|
+
{#if repository.metrics.lastBackup !== repository.metrics.lastSuccessfulBackup}
|
|
77
|
+
<StackListItem class="bg-danger-100">
|
|
78
|
+
<HStack>
|
|
79
|
+
<Icon icon={mdiAlert} /> Last backup failed <RelativeTime
|
|
80
|
+
time={repository.metrics.lastBackup}
|
|
81
|
+
/>
|
|
82
|
+
</HStack>
|
|
83
|
+
</StackListItem>
|
|
84
|
+
{:else}
|
|
85
|
+
<StackListItem class="bg-success-50">
|
|
86
|
+
<HStack>
|
|
87
|
+
<Icon icon={mdiCheck} /> Last backup successful <RelativeTime
|
|
88
|
+
time={repository.metrics.lastBackup}
|
|
89
|
+
/>
|
|
90
|
+
</HStack>
|
|
91
|
+
</StackListItem>
|
|
92
|
+
{/if}
|
|
93
|
+
{:else}
|
|
94
|
+
<StackListItem class="bg-warning-50">
|
|
95
|
+
<HStack>
|
|
96
|
+
<Icon icon={mdiInformation} /> Backup is yet to run.
|
|
97
|
+
</HStack>
|
|
98
|
+
</StackListItem>
|
|
99
|
+
{/if}
|
|
100
|
+
</StackList>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { LocalRepositoryDto, ScheduleDto } from "../../../fetch-client";
|
|
2
|
+
type Props = {
|
|
3
|
+
repository: LocalRepositoryDto;
|
|
4
|
+
schedule: ScheduleDto;
|
|
5
|
+
};
|
|
6
|
+
declare const ImmichManageBackupOverview: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type ImmichManageBackupOverview = ReturnType<typeof ImmichManageBackupOverview>;
|
|
8
|
+
export default ImmichManageBackupOverview;
|