@futo-org/backups-orchestrator-ui 0.27.0 → 0.30.0
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/dist/components/backends/BackendItem.svelte +28 -24
- package/dist/components/backends/BackendsList.svelte +33 -34
- package/dist/components/backends/BackendsList.svelte.d.ts +1 -0
- package/dist/components/backends/dialogs/SelectBackendModal.svelte +44 -55
- package/dist/components/backups/BackupItem.svelte +33 -26
- package/dist/components/backups/BackupStatus.svelte +200 -0
- package/dist/components/backups/BackupStatus.svelte.d.ts +19 -0
- package/dist/components/backups/BackupsList.svelte +17 -11
- package/dist/components/backups/dialogs/RestoreSnapshotModal.svelte +2 -2
- package/dist/components/backups/dialogs/SelectRepositoryModal.svelte +26 -36
- package/dist/components/backups/dialogs/ViewStatusModal.svelte +175 -0
- package/dist/components/backups/dialogs/ViewStatusModal.svelte.d.ts +8 -0
- package/dist/components/backups/run-history/RepositoryRunHistory.svelte +23 -12
- package/dist/components/backups/run-history/RepositoryRunHistory.svelte.d.ts +2 -0
- package/dist/components/backups/run-history/RepositoryRunHistoryItem.svelte +46 -20
- package/dist/components/backups/run-history/RepositoryRunHistoryPage.svelte +136 -0
- package/dist/components/backups/run-history/RepositoryRunHistoryPage.svelte.d.ts +8 -0
- package/dist/components/backups/run-history/RunHistoryModal.svelte +2 -2
- package/dist/components/backups/snapshots-list/RepositorySnapshotsList.svelte +22 -10
- package/dist/components/backups/snapshots-list/RepositorySnapshotsList.svelte.d.ts +2 -0
- package/dist/components/backups/snapshots-list/RepositorySnapshotsListItem.svelte +23 -21
- package/dist/components/backups/snapshots-list/RepositorySnapshotsPage.svelte +115 -0
- package/dist/components/backups/snapshots-list/RepositorySnapshotsPage.svelte.d.ts +9 -0
- package/dist/components/backups/snapshots-list/SnapshotsListModal.svelte +2 -2
- package/dist/components/dashboard/Dashboard.svelte +14 -7
- package/dist/components/dashboard/Dashboard.svelte.d.ts +1 -1
- package/dist/components/dashboard/DashboardAvgBackupTime.svelte +1 -1
- package/dist/components/dashboard/DashboardBackupHealth.svelte +5 -9
- package/dist/components/dashboard/DashboardBackupHealth.svelte.d.ts +1 -1
- package/dist/components/dashboard/DashboardCurrentUsage.svelte +1 -1
- package/dist/components/dashboard/DashboardDailyBackupTime.svelte +1 -1
- package/dist/components/dashboard/DashboardInstall.svelte +1 -1
- package/dist/components/dashboard/DashboardRecentBackups.svelte +47 -78
- package/dist/components/dashboard/DashboardTotalStored.svelte +1 -1
- package/dist/components/integrations/immich/ImmichBackupSettings.svelte +12 -0
- package/dist/components/integrations/immich/ImmichBackupSettings.svelte.d.ts +18 -0
- package/dist/components/integrations/immich/ImmichBackupsCard.svelte +109 -0
- package/dist/components/integrations/immich/ImmichBackupsCard.svelte.d.ts +13 -0
- package/dist/components/integrations/immich/ImmichBackupsNavButton.svelte +55 -0
- package/dist/components/integrations/immich/ImmichBackupsNavButton.svelte.d.ts +10 -0
- package/dist/components/integrations/immich/ImmichBackupsPage.svelte +16 -3
- package/dist/components/integrations/immich/ImmichBackupsPage.svelte.d.ts +8 -1
- package/dist/components/integrations/immich/ImmichBackupsSidebarItem.svelte +102 -0
- package/dist/components/integrations/immich/ImmichBackupsSidebarItem.svelte.d.ts +8 -0
- package/dist/components/integrations/immich/ImmichManageBackup.svelte +53 -14
- package/dist/components/integrations/immich/ImmichManageBackupOverview.svelte +45 -71
- package/dist/components/integrations/immich/ImmichOnboardingSetupFlow.svelte +123 -82
- package/dist/components/integrations/immich/ImmichOnboardingSetupFlow.svelte.d.ts +1 -1
- package/dist/components/integrations/immich/settings/ImmichSettingsBackupContents.svelte +313 -0
- package/dist/components/integrations/immich/settings/ImmichSettingsBackupContents.svelte.d.ts +3 -0
- package/dist/components/integrations/immich/settings/ImmichSettingsSchedule.svelte +218 -0
- package/dist/components/integrations/immich/settings/ImmichSettingsSchedule.svelte.d.ts +3 -0
- package/dist/components/integrations/immich/settings/ImmichSettingsStorage.svelte +207 -0
- package/dist/components/integrations/immich/settings/ImmichSettingsStorage.svelte.d.ts +3 -0
- package/dist/components/onboarding/RecoveryKeyDisplay.svelte +34 -20
- package/dist/components/onboarding/SampleOnboarding.svelte +57 -38
- package/dist/components/onboarding/stages/OnboardingStageBackupServices.svelte +10 -24
- package/dist/components/onboarding/steps/OnboardingStep1FinishSetup.svelte +44 -0
- package/dist/components/onboarding/steps/OnboardingStep1FinishSetup.svelte.d.ts +7 -0
- package/dist/components/onboarding/steps/OnboardingStep2ConnectAccount.svelte +50 -0
- package/dist/components/onboarding/steps/OnboardingStep2ConnectAccount.svelte.d.ts +7 -0
- package/dist/components/onboarding/steps/OnboardingStep3SaveRecoveryKey.svelte +45 -0
- package/dist/components/onboarding/steps/OnboardingStep3SaveRecoveryKey.svelte.d.ts +8 -0
- package/dist/components/onboarding/steps/OnboardingStep4FirstBackup.svelte +67 -0
- package/dist/components/onboarding/steps/OnboardingStep4FirstBackup.svelte.d.ts +9 -0
- package/dist/components/onboarding/steps/OnboardingStepLayout.svelte +58 -0
- package/dist/components/onboarding/steps/OnboardingStepLayout.svelte.d.ts +19 -0
- package/dist/components/onboarding/upsell/UpsellBackupFeaturesGrid.svelte +52 -0
- package/dist/components/onboarding/upsell/UpsellBackupFeaturesGrid.svelte.d.ts +18 -0
- package/dist/components/onboarding/upsell/UpsellExplainFutoBackups.svelte +33 -0
- package/dist/components/onboarding/upsell/UpsellExplainFutoBackups.svelte.d.ts +8 -0
- package/dist/components/onboarding/upsell/UpsellFrequentlyAskedQuestions.svelte +32 -0
- package/dist/components/onboarding/upsell/UpsellFrequentlyAskedQuestions.svelte.d.ts +11 -0
- package/dist/components/onboarding/upsell/UpsellFutoBackupsBadge.svelte +7 -0
- package/dist/components/onboarding/upsell/UpsellFutoBackupsBadge.svelte.d.ts +18 -0
- package/dist/components/onboarding/upsell/UpsellHowItWorks.svelte +46 -0
- package/dist/components/onboarding/upsell/UpsellHowItWorks.svelte.d.ts +6 -0
- package/dist/components/onboarding/upsell/UpsellModal.svelte +43 -0
- package/dist/components/onboarding/upsell/UpsellModal.svelte.d.ts +9 -0
- package/dist/components/onboarding/upsell/UpsellPage.svelte +40 -0
- package/dist/components/onboarding/upsell/UpsellPage.svelte.d.ts +14 -0
- package/dist/components/onboarding/upsell/UpsellProtectYourImmichLibrary.svelte +58 -0
- package/dist/components/onboarding/upsell/UpsellProtectYourImmichLibrary.svelte.d.ts +8 -0
- package/dist/components/schedules/RepositoryPicker.svelte +45 -38
- package/dist/components/schedules/ScheduleItem.svelte +32 -26
- package/dist/components/schedules/ScheduleList.svelte +16 -10
- package/dist/components/settings/GlobalSettings.svelte +8 -0
- package/dist/components/settings/GlobalSettings.svelte.d.ts +18 -0
- package/dist/components/settings/SettingsBackends.svelte +16 -0
- package/dist/components/settings/SettingsBackends.svelte.d.ts +18 -0
- package/dist/components/settings/SettingsBilling.svelte +69 -0
- package/dist/components/settings/SettingsBilling.svelte.d.ts +6 -0
- package/dist/components/settings/SettingsNotifications.svelte +47 -0
- package/dist/components/settings/SettingsNotifications.svelte.d.ts +6 -0
- package/dist/components/test/ImmichTestUi.svelte +143 -27
- package/dist/components/test/TestUi.svelte +4 -4
- package/dist/components/test/dashboard/ActiveJobs.svelte +2 -2
- package/dist/components/test/immich/MockImmichHideBackupsReminder.svelte +12 -0
- package/dist/components/test/immich/MockImmichHideBackupsReminder.svelte.d.ts +3 -0
- package/dist/components/test/immich/MockImmichPhotos.svelte +36 -0
- package/dist/components/test/immich/MockImmichPhotos.svelte.d.ts +18 -0
- package/dist/components/test/immich/MockImmichPurchaseInfo.svelte +29 -0
- package/dist/components/test/immich/MockImmichPurchaseInfo.svelte.d.ts +18 -0
- package/dist/components/test/immich/MockImmichServerStatus.svelte +9 -0
- package/dist/components/test/immich/MockImmichServerStatus.svelte.d.ts +18 -0
- package/dist/components/test/immich/MockImmichSidebar.svelte +20 -0
- package/dist/components/test/immich/MockImmichSidebar.svelte.d.ts +7 -0
- package/dist/components/test/immich/MockImmichStorageSpace.svelte +25 -0
- package/dist/components/test/immich/MockImmichStorageSpace.svelte.d.ts +7 -0
- package/dist/components/ui/Accordion.svelte +67 -0
- package/dist/components/ui/Accordion.svelte.d.ts +11 -0
- package/dist/components/ui/ListToolbar.svelte +60 -0
- package/dist/components/ui/ListToolbar.svelte.d.ts +16 -0
- package/dist/components/ui/PageLayout.svelte +21 -9
- package/dist/components/ui/PageLayout.svelte.d.ts +1 -0
- package/dist/components/ui/Pagination.svelte +80 -0
- package/dist/components/ui/Pagination.svelte.d.ts +8 -0
- package/dist/components/ui/PathListField.svelte +17 -14
- package/dist/components/ui/StackList.svelte +18 -18
- package/dist/components/ui/StackList.svelte.d.ts +6 -27
- package/dist/components/ui/StackListItem.svelte +47 -25
- package/dist/components/ui/StackListItem.svelte.d.ts +5 -2
- package/dist/components/ui/StackListOption.svelte +33 -0
- package/dist/components/ui/StackListOption.svelte.d.ts +11 -0
- package/dist/components/ui/StackListPlaceholder.svelte +12 -0
- package/dist/components/ui/StackListPlaceholder.svelte.d.ts +7 -0
- package/dist/components/ui/StepNumber.svelte +20 -0
- package/dist/components/ui/StepNumber.svelte.d.ts +7 -0
- package/dist/components/util/Suspense.svelte +7 -3
- package/dist/components/util/YuccaContext.svelte +1 -1
- package/dist/events.js +1 -1
- package/dist/fetch-client.d.ts +2 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +6 -1
- package/dist/services/backend.service.js +3 -0
- package/dist/services/immich.integration.service.d.ts +1 -1
- package/dist/services/immich.integration.service.js +2 -3
- package/dist/services/integrations.service.d.ts +1 -0
- package/dist/services/integrations.service.js +2 -1
- package/dist/services/log.service.svelte.d.ts +12 -0
- package/dist/services/log.service.svelte.js +15 -1
- package/dist/services/repository.service.js +9 -3
- package/dist/services/runHistory.service.d.ts +1 -1
- package/dist/services/runHistory.service.js +5 -4
- package/dist/utils/format.d.ts +1 -1
- package/dist/utils/format.js +2 -2
- package/package.json +3 -3
- package/dist/components/backups/dialogs/ViewLogModal.svelte +0 -208
- package/dist/components/backups/dialogs/ViewLogModal.svelte.d.ts +0 -7
- package/dist/components/integrations/immich/ImmichConfigureBackup.svelte +0 -403
- package/dist/components/integrations/immich/ImmichConfigureBackup.svelte.d.ts +0 -9
- package/dist/components/integrations/immich/ImmichConfirmDefaultBackup.svelte +0 -80
- package/dist/components/integrations/immich/ImmichConfirmDefaultBackup.svelte.d.ts +0 -8
- package/dist/components/onboarding/stages/OnboardingStageKeyConfirm.svelte +0 -56
- package/dist/components/onboarding/stages/OnboardingStageKeyConfirm.svelte.d.ts +0 -9
- package/dist/components/onboarding/stages/OnboardingStageKeyIntro.svelte +0 -50
- package/dist/components/onboarding/stages/OnboardingStageKeyIntro.svelte.d.ts +0 -7
- package/dist/components/onboarding/stages/OnboardingStageKeySave.svelte +0 -44
- package/dist/components/onboarding/stages/OnboardingStageKeySave.svelte.d.ts +0 -8
- package/dist/components/onboarding/stages/OnboardingStageWelcome.svelte +0 -56
- package/dist/components/onboarding/stages/OnboardingStageWelcome.svelte.d.ts +0 -9
- package/dist/components/util/TimedButton.svelte +0 -37
- package/dist/components/util/TimedButton.svelte.d.ts +0 -7
|
@@ -1,39 +1,56 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import OnboardingBootstrapError from "../../onboarding/OnboardingBootstrapError.svelte";
|
|
3
|
-
import OnboardingStageBackupServices from "../../onboarding/stages/OnboardingStageBackupServices.svelte";
|
|
4
|
-
import OnboardingStageKeyConfirm from "../../onboarding/stages/OnboardingStageKeyConfirm.svelte";
|
|
5
3
|
import OnboardingStageKeyImport from "../../onboarding/stages/OnboardingStageKeyImport.svelte";
|
|
6
|
-
import OnboardingStageKeyIntro from "../../onboarding/stages/OnboardingStageKeyIntro.svelte";
|
|
7
|
-
import OnboardingStageSaveKey from "../../onboarding/stages/OnboardingStageKeySave.svelte";
|
|
8
4
|
import OnboardingStageTelemetry from "../../onboarding/stages/OnboardingStageTelemetry.svelte";
|
|
9
|
-
import
|
|
5
|
+
import OnboardingStepFinishSetup from "../../onboarding/steps/OnboardingStep1FinishSetup.svelte";
|
|
6
|
+
import OnboardingStepConnectAccount from "../../onboarding/steps/OnboardingStep2ConnectAccount.svelte";
|
|
7
|
+
import OnboardingStepSaveRecoveryKey from "../../onboarding/steps/OnboardingStep3SaveRecoveryKey.svelte";
|
|
8
|
+
import OnboardingStepFirstBackup from "../../onboarding/steps/OnboardingStep4FirstBackup.svelte";
|
|
10
9
|
import type { OnboardingStatusResponseDto } from "../../../fetch-client";
|
|
10
|
+
import {
|
|
11
|
+
handleSetupLocalStorage,
|
|
12
|
+
handleStartYuccaLogin,
|
|
13
|
+
} from "../../../services/backend.service";
|
|
14
|
+
import {
|
|
15
|
+
IMMICH_DEFAULT_CRON,
|
|
16
|
+
useConfigureImmichDefaults,
|
|
17
|
+
} from "../../../services/integrations.service";
|
|
11
18
|
import {
|
|
12
19
|
handleConfirmRecoveryKey,
|
|
13
20
|
handleCurrentRecoveryKey,
|
|
14
21
|
handleOnboardingStatus,
|
|
15
22
|
} from "../../../services/onboarding.service";
|
|
16
|
-
import { LoadingSpinner } from "@immich/ui";
|
|
23
|
+
import { LoadingSpinner, Modal, ModalBody } from "@immich/ui";
|
|
24
|
+
import cronstrue from "cronstrue";
|
|
17
25
|
import { onMount, type Snippet } from "svelte";
|
|
18
|
-
|
|
19
|
-
|
|
26
|
+
|
|
27
|
+
type Stage =
|
|
28
|
+
| "idle"
|
|
29
|
+
| "intro"
|
|
30
|
+
| "telemetry"
|
|
31
|
+
| "connect"
|
|
32
|
+
| "key-import"
|
|
33
|
+
| "key"
|
|
34
|
+
| "backup"
|
|
35
|
+
| "finished";
|
|
20
36
|
|
|
21
37
|
type Props = {
|
|
22
|
-
|
|
38
|
+
fallback: Snippet<[() => void]>;
|
|
23
39
|
children: Snippet;
|
|
24
40
|
};
|
|
25
41
|
|
|
26
|
-
const {
|
|
42
|
+
const { fallback, children }: Props = $props();
|
|
27
43
|
|
|
28
44
|
let code = $state("");
|
|
29
|
-
let
|
|
45
|
+
let storageLocation = $state("FUTO Backups");
|
|
30
46
|
let status: OnboardingStatusResponseDto | undefined = $state();
|
|
31
|
-
let stage:
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
47
|
+
let stage: Stage = $state("idle");
|
|
48
|
+
let resume: Stage = $state("intro");
|
|
49
|
+
let confirming = $state(false);
|
|
50
|
+
|
|
51
|
+
const defaults = useConfigureImmichDefaults();
|
|
52
|
+
|
|
53
|
+
const schedule = cronstrue.toString(IMMICH_DEFAULT_CRON, { verbose: true });
|
|
37
54
|
|
|
38
55
|
onMount(() => {
|
|
39
56
|
handleOnboardingStatus().then(async (data) => {
|
|
@@ -44,12 +61,12 @@
|
|
|
44
61
|
}
|
|
45
62
|
|
|
46
63
|
if (data.hasOnboardedKey) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
64
|
+
if (data.hasBackup) {
|
|
65
|
+
stage = "finished";
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
resume = data.hasTelemetry === "none" ? "telemetry" : "connect";
|
|
53
70
|
} else {
|
|
54
71
|
const { recoveryKey } = await handleCurrentRecoveryKey();
|
|
55
72
|
code = recoveryKey;
|
|
@@ -57,75 +74,99 @@
|
|
|
57
74
|
});
|
|
58
75
|
});
|
|
59
76
|
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
77
|
+
const onStart = () => (stage = resume);
|
|
78
|
+
const onCancel = () => (stage = "idle");
|
|
79
|
+
|
|
80
|
+
const afterTelemetry = () =>
|
|
81
|
+
(stage = status?.hasOnboardedKey && status.hasBackup
|
|
82
|
+
? "finished"
|
|
83
|
+
: "connect");
|
|
84
|
+
|
|
85
|
+
const onBackendReady = () => (stage = status?.hasOnboardedKey ? "backup" : "key");
|
|
86
|
+
|
|
87
|
+
const onConnect = () => {
|
|
88
|
+
storageLocation = "FUTO Backups";
|
|
89
|
+
handleStartYuccaLogin(onBackendReady);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const onLocalStorage = () => {
|
|
93
|
+
storageLocation = "Local Storage";
|
|
94
|
+
handleSetupLocalStorage(onBackendReady);
|
|
63
95
|
};
|
|
64
96
|
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
97
|
+
const onConfirmKey = async () => {
|
|
98
|
+
confirming = true;
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
await handleConfirmRecoveryKey();
|
|
102
|
+
stage = "backup";
|
|
103
|
+
} finally {
|
|
104
|
+
confirming = false;
|
|
105
|
+
}
|
|
68
106
|
};
|
|
107
|
+
|
|
108
|
+
const onStartBackup = () =>
|
|
109
|
+
defaults.mutate(undefined, { onSuccess: () => (stage = "finished") });
|
|
69
110
|
</script>
|
|
70
111
|
|
|
71
112
|
{#if status === undefined || status.status === "not-ready"}
|
|
72
|
-
<
|
|
73
|
-
|
|
74
|
-
|
|
113
|
+
<div class="flex h-full items-center justify-center p-8">
|
|
114
|
+
<LoadingSpinner />
|
|
115
|
+
</div>
|
|
75
116
|
{:else if stage === "finished"}
|
|
76
117
|
{@render children()}
|
|
77
|
-
{:else
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
/>
|
|
118
|
+
{:else}
|
|
119
|
+
{@render fallback(onStart)}
|
|
120
|
+
{/if}
|
|
121
|
+
|
|
122
|
+
{#if status?.status === "error" && stage !== "idle"}
|
|
123
|
+
<OnboardingBootstrapError error={status.error} onQuit={onCancel} />
|
|
124
|
+
{:else if stage === "intro"}
|
|
125
|
+
<Modal size="small" title="FUTO Backups" onClose={onCancel}>
|
|
126
|
+
<ModalBody>
|
|
127
|
+
<OnboardingStepFinishSetup
|
|
128
|
+
onContinue={() =>
|
|
129
|
+
(stage = status?.hasTelemetry === "none" ? "telemetry" : "connect")}
|
|
130
|
+
onImportKey={() => (stage = "key-import")}
|
|
131
|
+
/>
|
|
132
|
+
</ModalBody>
|
|
133
|
+
</Modal>
|
|
83
134
|
{:else if stage === "telemetry"}
|
|
84
|
-
<OnboardingStageTelemetry
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
onCancel={onExit}
|
|
92
|
-
/>
|
|
135
|
+
<OnboardingStageTelemetry onContinue={afterTelemetry} {onCancel} />
|
|
136
|
+
{:else if stage === "connect"}
|
|
137
|
+
<Modal size="small" title="FUTO Backups" onClose={onCancel}>
|
|
138
|
+
<ModalBody>
|
|
139
|
+
<OnboardingStepConnectAccount {onConnect} {onLocalStorage} />
|
|
140
|
+
</ModalBody>
|
|
141
|
+
</Modal>
|
|
93
142
|
{:else if stage === "key-import"}
|
|
94
143
|
<OnboardingStageKeyImport
|
|
95
|
-
onStart={() => (stage = "
|
|
96
|
-
onImported={() =>
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
onNext={() => (stage = "key-save")}
|
|
102
|
-
onCancel={onExit}
|
|
103
|
-
/>
|
|
104
|
-
{:else if stage === "key-save"}
|
|
105
|
-
<OnboardingStageSaveKey
|
|
106
|
-
{code}
|
|
107
|
-
onNext={() => (stage = "key-confirm")}
|
|
108
|
-
onCancel={onExit}
|
|
109
|
-
/>
|
|
110
|
-
{:else if stage === "key-confirm"}
|
|
111
|
-
<OnboardingStageKeyConfirm
|
|
112
|
-
{code}
|
|
113
|
-
onBack={() => (stage = "key-save")}
|
|
114
|
-
onCancel={onExit}
|
|
115
|
-
{onConfirmKey}
|
|
116
|
-
/>
|
|
117
|
-
{:else if stage === "backup-service"}
|
|
118
|
-
<OnboardingStageBackupServices onNext={onSelectBackend} onCancel={onExit} />
|
|
119
|
-
{:else if stage === "backup-confirm"}
|
|
120
|
-
<ImmichConfirmDefaultBackup
|
|
121
|
-
onCustomize={() => (stage = "backup-create")}
|
|
122
|
-
onConfirm={() => (stage = "finished")}
|
|
123
|
-
onCancel={onExit}
|
|
124
|
-
/>
|
|
125
|
-
{:else if stage === "backup-create"}
|
|
126
|
-
<ImmichConfigureBackup
|
|
127
|
-
onFinish={() => (stage = "finished")}
|
|
128
|
-
onCancel={onExit}
|
|
129
|
-
{backendId}
|
|
144
|
+
onStart={() => (stage = "intro")}
|
|
145
|
+
onImported={(key) => {
|
|
146
|
+
code = key;
|
|
147
|
+
stage = "connect";
|
|
148
|
+
}}
|
|
149
|
+
{onCancel}
|
|
130
150
|
/>
|
|
151
|
+
{:else if stage === "key"}
|
|
152
|
+
<Modal size="small" title="FUTO Backups" onClose={onCancel}>
|
|
153
|
+
<ModalBody>
|
|
154
|
+
<OnboardingStepSaveRecoveryKey
|
|
155
|
+
{code}
|
|
156
|
+
onContinue={onConfirmKey}
|
|
157
|
+
loading={confirming}
|
|
158
|
+
/>
|
|
159
|
+
</ModalBody>
|
|
160
|
+
</Modal>
|
|
161
|
+
{:else if stage === "backup"}
|
|
162
|
+
<Modal size="small" title="FUTO Backups" onClose={onCancel}>
|
|
163
|
+
<ModalBody>
|
|
164
|
+
<OnboardingStepFirstBackup
|
|
165
|
+
{schedule}
|
|
166
|
+
{storageLocation}
|
|
167
|
+
{onStartBackup}
|
|
168
|
+
loading={defaults.isPending}
|
|
169
|
+
/>
|
|
170
|
+
</ModalBody>
|
|
171
|
+
</Modal>
|
|
131
172
|
{/if}
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Accordion from "../../../ui/Accordion.svelte";
|
|
3
|
+
import OnEvents from "../../../util/OnEvents.svelte";
|
|
4
|
+
import {
|
|
5
|
+
useConfigureImmichIntegration,
|
|
6
|
+
useIntegrationEventHandler,
|
|
7
|
+
useIntegrations,
|
|
8
|
+
} from "../../../../services/integrations.service";
|
|
9
|
+
import {
|
|
10
|
+
useRepositories,
|
|
11
|
+
useRepositoryEventHandler,
|
|
12
|
+
} from "../../../../services/repository.service";
|
|
13
|
+
import {
|
|
14
|
+
useScheduleEventHandler,
|
|
15
|
+
useSchedules,
|
|
16
|
+
} from "../../../../services/schedule.service";
|
|
17
|
+
import {
|
|
18
|
+
Button,
|
|
19
|
+
Checkbox,
|
|
20
|
+
Field,
|
|
21
|
+
HStack,
|
|
22
|
+
LoadingSpinner,
|
|
23
|
+
Select,
|
|
24
|
+
Stack,
|
|
25
|
+
Switch,
|
|
26
|
+
Text,
|
|
27
|
+
} from "@immich/ui";
|
|
28
|
+
import { mdiImageMultiple } from "@mdi/js";
|
|
29
|
+
import { SvelteSet } from "svelte/reactivity";
|
|
30
|
+
|
|
31
|
+
type Group = {
|
|
32
|
+
key: string;
|
|
33
|
+
label: string;
|
|
34
|
+
description: string;
|
|
35
|
+
folders: string[];
|
|
36
|
+
required?: boolean;
|
|
37
|
+
warning?: string;
|
|
38
|
+
warningColor?: "danger" | "warning";
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const groups: Group[] = [
|
|
42
|
+
{
|
|
43
|
+
key: "media",
|
|
44
|
+
label: "Photos and videos",
|
|
45
|
+
description: "Your media uploaded directly to Immich.",
|
|
46
|
+
folders: ["upload", "profile", "library"],
|
|
47
|
+
warning: "It is highly recommended you back this up!",
|
|
48
|
+
warningColor: "danger",
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
key: "database",
|
|
52
|
+
label: "Database and metadata",
|
|
53
|
+
description:
|
|
54
|
+
"Albums, people, tags, favorites and other library details. Required to restore.",
|
|
55
|
+
folders: ["backups"],
|
|
56
|
+
required: true,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
key: "thumbs",
|
|
60
|
+
label: "Thumbnails and previews",
|
|
61
|
+
description: "Generated photo previews, can be recreated later.",
|
|
62
|
+
folders: ["thumbs"],
|
|
63
|
+
warning:
|
|
64
|
+
"When restoring, you will see broken previews. Use the admin panel to regenerate them.",
|
|
65
|
+
warningColor: "warning",
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
key: "encoded",
|
|
69
|
+
label: "Encoded videos",
|
|
70
|
+
description: "Generated video previews, can be recreated later.",
|
|
71
|
+
folders: ["encoded-video"],
|
|
72
|
+
warning:
|
|
73
|
+
"When restoring, you will see broken previews. Use the admin panel to regenerate them.",
|
|
74
|
+
warningColor: "warning",
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
const integrations = useIntegrations();
|
|
79
|
+
const repositories = useRepositories();
|
|
80
|
+
const schedules = useSchedules();
|
|
81
|
+
const mutation = useConfigureImmichIntegration();
|
|
82
|
+
|
|
83
|
+
const { onIntegrationUpdate } = useIntegrationEventHandler();
|
|
84
|
+
const { onRepositoryCreate, onRepositoryUpdate, onRepositoryDelete } =
|
|
85
|
+
useRepositoryEventHandler();
|
|
86
|
+
const { onScheduleCreate, onScheduleUpdate, onScheduleDelete } =
|
|
87
|
+
useScheduleEventHandler();
|
|
88
|
+
|
|
89
|
+
const integration = $derived(integrations.data?.immichIntegration);
|
|
90
|
+
|
|
91
|
+
const repository = $derived(
|
|
92
|
+
integration
|
|
93
|
+
? repositories.data?.find((entry) => entry.id === integration.id)
|
|
94
|
+
: undefined,
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const schedule = $derived(
|
|
98
|
+
integration
|
|
99
|
+
? schedules.data?.find((entry) => entry.id === integration.scheduleId)
|
|
100
|
+
: undefined,
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const isLoading = $derived(
|
|
104
|
+
integrations.isLoading || repositories.isLoading || schedules.isLoading,
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
type LibrariesMode = "all" | "none" | "some";
|
|
108
|
+
|
|
109
|
+
const libraryModes = [
|
|
110
|
+
{ value: "all", label: "All libraries" },
|
|
111
|
+
{ value: "none", label: "No libraries" },
|
|
112
|
+
{ value: "some", label: "Choose libraries" },
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
const folders = new SvelteSet<string>();
|
|
116
|
+
const selectedLibraries = new SvelteSet<string>();
|
|
117
|
+
let backupConfiguration = $state(true);
|
|
118
|
+
let librariesMode = $state<LibrariesMode>("all");
|
|
119
|
+
let loaded = false;
|
|
120
|
+
|
|
121
|
+
$effect(() => {
|
|
122
|
+
if (loaded || !integration) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
loaded = true;
|
|
127
|
+
backupConfiguration = integration.configuration.backupConfiguration;
|
|
128
|
+
|
|
129
|
+
const libraries = integration.configuration.libraries;
|
|
130
|
+
|
|
131
|
+
if (libraries === "all") {
|
|
132
|
+
librariesMode = "all";
|
|
133
|
+
} else if (libraries.length === 0) {
|
|
134
|
+
librariesMode = "none";
|
|
135
|
+
} else {
|
|
136
|
+
librariesMode = "some";
|
|
137
|
+
|
|
138
|
+
for (const id of libraries) {
|
|
139
|
+
selectedLibraries.add(id);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
for (const folder of integration.configuration.dataFolders) {
|
|
144
|
+
folders.add(folder);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const toggleLibrary = (id: string) => {
|
|
149
|
+
if (selectedLibraries.has(id)) {
|
|
150
|
+
selectedLibraries.delete(id);
|
|
151
|
+
} else {
|
|
152
|
+
selectedLibraries.add(id);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const available = $derived(
|
|
157
|
+
integrations.data?.immichState?.dataFolders ?? [],
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
const libraries = $derived(integrations.data?.immichState?.libraries ?? []);
|
|
161
|
+
|
|
162
|
+
const applicable = (group: Group) =>
|
|
163
|
+
group.folders.filter((folder) => available.includes(folder));
|
|
164
|
+
|
|
165
|
+
const isChecked = (group: Group) => {
|
|
166
|
+
const own = applicable(group);
|
|
167
|
+
|
|
168
|
+
return own.length > 0 && own.every((folder) => folders.has(folder));
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const toggle = (group: Group) => {
|
|
172
|
+
const own = applicable(group);
|
|
173
|
+
const enabled = own.every((folder) => folders.has(folder));
|
|
174
|
+
|
|
175
|
+
for (const folder of own) {
|
|
176
|
+
if (enabled) {
|
|
177
|
+
folders.delete(folder);
|
|
178
|
+
} else {
|
|
179
|
+
folders.add(folder);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const onSave = () => {
|
|
185
|
+
folders.add("backups");
|
|
186
|
+
|
|
187
|
+
if (!integration || !repository || !schedule) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
mutation.mutate({
|
|
192
|
+
name: repository.name,
|
|
193
|
+
worm: repository.worm,
|
|
194
|
+
cron: schedule.cron,
|
|
195
|
+
retentionPolicy: repository.configuration?.retentionPolicy ?? null,
|
|
196
|
+
dataFolders: [...folders],
|
|
197
|
+
backupConfiguration,
|
|
198
|
+
libraries:
|
|
199
|
+
librariesMode === "all"
|
|
200
|
+
? "all"
|
|
201
|
+
: librariesMode === "none"
|
|
202
|
+
? []
|
|
203
|
+
: [...selectedLibraries],
|
|
204
|
+
});
|
|
205
|
+
};
|
|
206
|
+
</script>
|
|
207
|
+
|
|
208
|
+
<OnEvents
|
|
209
|
+
{onIntegrationUpdate}
|
|
210
|
+
{onRepositoryCreate}
|
|
211
|
+
{onRepositoryUpdate}
|
|
212
|
+
{onRepositoryDelete}
|
|
213
|
+
{onScheduleCreate}
|
|
214
|
+
{onScheduleUpdate}
|
|
215
|
+
{onScheduleDelete}
|
|
216
|
+
/>
|
|
217
|
+
|
|
218
|
+
<Accordion
|
|
219
|
+
title="Backup Contents"
|
|
220
|
+
subtitle="Choose what Immich includes in each backup."
|
|
221
|
+
icon={mdiImageMultiple}
|
|
222
|
+
>
|
|
223
|
+
<Stack gap={4} class="pt-2">
|
|
224
|
+
{#if isLoading}
|
|
225
|
+
<LoadingSpinner />
|
|
226
|
+
{:else}
|
|
227
|
+
{#each groups as group (group.key)}
|
|
228
|
+
{#if applicable(group).length > 0}
|
|
229
|
+
<Stack gap={1}>
|
|
230
|
+
<Field
|
|
231
|
+
label={group.label}
|
|
232
|
+
description={group.description}
|
|
233
|
+
readOnly={group.required}
|
|
234
|
+
required={group.required ? "indicator" : undefined}
|
|
235
|
+
>
|
|
236
|
+
<Switch
|
|
237
|
+
checked={group.required || isChecked(group)}
|
|
238
|
+
class={group.required
|
|
239
|
+
? "cursor-not-allowed opacity-38"
|
|
240
|
+
: undefined}
|
|
241
|
+
onCheckedChange={() => toggle(group)}
|
|
242
|
+
/>
|
|
243
|
+
</Field>
|
|
244
|
+
|
|
245
|
+
{#if group.warning && !group.required && !isChecked(group)}
|
|
246
|
+
<Text size="small" color={group.warningColor}>
|
|
247
|
+
{group.warning}
|
|
248
|
+
</Text>
|
|
249
|
+
{/if}
|
|
250
|
+
</Stack>
|
|
251
|
+
{/if}
|
|
252
|
+
{/each}
|
|
253
|
+
|
|
254
|
+
<Stack gap={1}>
|
|
255
|
+
<Field
|
|
256
|
+
label="Backup configuration"
|
|
257
|
+
description="Include these backup settings, so they survive a restore."
|
|
258
|
+
>
|
|
259
|
+
<Switch bind:checked={backupConfiguration} />
|
|
260
|
+
</Field>
|
|
261
|
+
|
|
262
|
+
{#if !backupConfiguration}
|
|
263
|
+
<Text size="small" color="warning">
|
|
264
|
+
You will need to reconfigure backups from scratch after restoring.
|
|
265
|
+
</Text>
|
|
266
|
+
{/if}
|
|
267
|
+
</Stack>
|
|
268
|
+
|
|
269
|
+
<Field
|
|
270
|
+
label="External libraries"
|
|
271
|
+
description="Libraries stored outside of Immich. New libraries are included automatically unless you choose specific ones."
|
|
272
|
+
>
|
|
273
|
+
<Select
|
|
274
|
+
options={libraryModes}
|
|
275
|
+
value={librariesMode}
|
|
276
|
+
onChange={(value) => (librariesMode = value as LibrariesMode)}
|
|
277
|
+
/>
|
|
278
|
+
</Field>
|
|
279
|
+
|
|
280
|
+
{#if librariesMode === "some"}
|
|
281
|
+
{#if libraries.length === 0}
|
|
282
|
+
<Text size="small" color="muted">No external libraries found.</Text>
|
|
283
|
+
{:else}
|
|
284
|
+
<Stack gap={2} class="ps-1">
|
|
285
|
+
{#each libraries as library (library.id)}
|
|
286
|
+
<label class="flex select-none items-center gap-3">
|
|
287
|
+
<Checkbox
|
|
288
|
+
checked={selectedLibraries.has(library.id)}
|
|
289
|
+
onCheckedChange={() => toggleLibrary(library.id)}
|
|
290
|
+
/>
|
|
291
|
+
<Text size="small">{library.name}</Text>
|
|
292
|
+
</label>
|
|
293
|
+
{/each}
|
|
294
|
+
</Stack>
|
|
295
|
+
{/if}
|
|
296
|
+
{:else if librariesMode === "all" && libraries.length > 0}
|
|
297
|
+
<Text size="small" color="muted">
|
|
298
|
+
Included: {libraries.map((library) => library.name).join(", ")}
|
|
299
|
+
</Text>
|
|
300
|
+
{/if}
|
|
301
|
+
|
|
302
|
+
<HStack class="justify-end">
|
|
303
|
+
<Button
|
|
304
|
+
shape="round"
|
|
305
|
+
loading={mutation.isPending}
|
|
306
|
+
onclick={onSave}
|
|
307
|
+
>
|
|
308
|
+
Save
|
|
309
|
+
</Button>
|
|
310
|
+
</HStack>
|
|
311
|
+
{/if}
|
|
312
|
+
</Stack>
|
|
313
|
+
</Accordion>
|