@futo-org/backups-orchestrator-ui 0.26.0 → 0.28.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.
Files changed (156) hide show
  1. package/dist/components/backends/BackendItem.svelte +28 -24
  2. package/dist/components/backends/BackendsList.svelte +33 -34
  3. package/dist/components/backends/BackendsList.svelte.d.ts +1 -0
  4. package/dist/components/backends/dialogs/SelectBackendModal.svelte +44 -55
  5. package/dist/components/backups/BackupItem.svelte +33 -26
  6. package/dist/components/backups/BackupStatus.svelte +200 -0
  7. package/dist/components/backups/BackupStatus.svelte.d.ts +19 -0
  8. package/dist/components/backups/BackupsList.svelte +17 -11
  9. package/dist/components/backups/dialogs/RestoreSnapshotModal.svelte +2 -2
  10. package/dist/components/backups/dialogs/SelectRepositoryModal.svelte +26 -36
  11. package/dist/components/backups/dialogs/ViewStatusModal.svelte +175 -0
  12. package/dist/components/backups/dialogs/ViewStatusModal.svelte.d.ts +8 -0
  13. package/dist/components/backups/run-history/RepositoryRunHistory.svelte +22 -10
  14. package/dist/components/backups/run-history/RepositoryRunHistory.svelte.d.ts +2 -0
  15. package/dist/components/backups/run-history/RepositoryRunHistoryItem.svelte +46 -20
  16. package/dist/components/backups/run-history/RepositoryRunHistoryPage.svelte +135 -0
  17. package/dist/components/backups/run-history/RepositoryRunHistoryPage.svelte.d.ts +8 -0
  18. package/dist/components/backups/run-history/RunHistoryModal.svelte +2 -2
  19. package/dist/components/backups/snapshots-list/RepositorySnapshotsList.svelte +22 -10
  20. package/dist/components/backups/snapshots-list/RepositorySnapshotsList.svelte.d.ts +2 -0
  21. package/dist/components/backups/snapshots-list/RepositorySnapshotsListItem.svelte +23 -21
  22. package/dist/components/backups/snapshots-list/RepositorySnapshotsPage.svelte +115 -0
  23. package/dist/components/backups/snapshots-list/RepositorySnapshotsPage.svelte.d.ts +9 -0
  24. package/dist/components/backups/snapshots-list/SnapshotsListModal.svelte +2 -2
  25. package/dist/components/dashboard/Dashboard.svelte +14 -7
  26. package/dist/components/dashboard/Dashboard.svelte.d.ts +1 -1
  27. package/dist/components/dashboard/DashboardAvgBackupTime.svelte +1 -1
  28. package/dist/components/dashboard/DashboardBackupHealth.svelte +5 -9
  29. package/dist/components/dashboard/DashboardBackupHealth.svelte.d.ts +1 -1
  30. package/dist/components/dashboard/DashboardCurrentUsage.svelte +1 -1
  31. package/dist/components/dashboard/DashboardDailyBackupTime.svelte +1 -1
  32. package/dist/components/dashboard/DashboardInstall.svelte +1 -1
  33. package/dist/components/dashboard/DashboardRecentBackups.svelte +47 -78
  34. package/dist/components/dashboard/DashboardTotalStored.svelte +1 -1
  35. package/dist/components/integrations/immich/ImmichBackupSettings.svelte +12 -0
  36. package/dist/components/integrations/immich/ImmichBackupSettings.svelte.d.ts +18 -0
  37. package/dist/components/integrations/immich/ImmichBackupsCard.svelte +73 -0
  38. package/dist/components/integrations/immich/ImmichBackupsCard.svelte.d.ts +12 -0
  39. package/dist/components/integrations/immich/ImmichBackupsNavButton.svelte +55 -0
  40. package/dist/components/integrations/immich/ImmichBackupsNavButton.svelte.d.ts +10 -0
  41. package/dist/components/integrations/immich/ImmichBackupsPage.svelte +15 -3
  42. package/dist/components/integrations/immich/ImmichBackupsPage.svelte.d.ts +7 -1
  43. package/dist/components/integrations/immich/ImmichBackupsSidebarItem.svelte +66 -0
  44. package/dist/components/integrations/immich/ImmichBackupsSidebarItem.svelte.d.ts +8 -0
  45. package/dist/components/integrations/immich/ImmichManageBackup.svelte +53 -14
  46. package/dist/components/integrations/immich/ImmichManageBackupOverview.svelte +45 -71
  47. package/dist/components/integrations/immich/ImmichOnboardingSetupFlow.svelte +123 -82
  48. package/dist/components/integrations/immich/ImmichOnboardingSetupFlow.svelte.d.ts +1 -1
  49. package/dist/components/integrations/immich/settings/ImmichSettingsBackupContents.svelte +313 -0
  50. package/dist/components/integrations/immich/settings/ImmichSettingsBackupContents.svelte.d.ts +3 -0
  51. package/dist/components/integrations/immich/settings/ImmichSettingsSchedule.svelte +218 -0
  52. package/dist/components/integrations/immich/settings/ImmichSettingsSchedule.svelte.d.ts +3 -0
  53. package/dist/components/integrations/immich/settings/ImmichSettingsStorage.svelte +207 -0
  54. package/dist/components/integrations/immich/settings/ImmichSettingsStorage.svelte.d.ts +3 -0
  55. package/dist/components/onboarding/RecoveryKeyDisplay.svelte +34 -20
  56. package/dist/components/onboarding/SampleOnboarding.svelte +57 -38
  57. package/dist/components/onboarding/stages/OnboardingStageBackupServices.svelte +10 -24
  58. package/dist/components/onboarding/steps/OnboardingStep1FinishSetup.svelte +44 -0
  59. package/dist/components/onboarding/steps/OnboardingStep1FinishSetup.svelte.d.ts +7 -0
  60. package/dist/components/onboarding/steps/OnboardingStep2ConnectAccount.svelte +50 -0
  61. package/dist/components/onboarding/steps/OnboardingStep2ConnectAccount.svelte.d.ts +7 -0
  62. package/dist/components/onboarding/steps/OnboardingStep3SaveRecoveryKey.svelte +45 -0
  63. package/dist/components/onboarding/steps/OnboardingStep3SaveRecoveryKey.svelte.d.ts +8 -0
  64. package/dist/components/onboarding/steps/OnboardingStep4FirstBackup.svelte +67 -0
  65. package/dist/components/onboarding/steps/OnboardingStep4FirstBackup.svelte.d.ts +9 -0
  66. package/dist/components/onboarding/steps/OnboardingStepLayout.svelte +58 -0
  67. package/dist/components/onboarding/steps/OnboardingStepLayout.svelte.d.ts +19 -0
  68. package/dist/components/onboarding/upsell/UpsellBackupFeaturesGrid.svelte +52 -0
  69. package/dist/components/onboarding/upsell/UpsellBackupFeaturesGrid.svelte.d.ts +18 -0
  70. package/dist/components/onboarding/upsell/UpsellExplainFutoBackups.svelte +33 -0
  71. package/dist/components/onboarding/upsell/UpsellExplainFutoBackups.svelte.d.ts +8 -0
  72. package/dist/components/onboarding/upsell/UpsellFrequentlyAskedQuestions.svelte +27 -0
  73. package/dist/components/onboarding/upsell/UpsellFrequentlyAskedQuestions.svelte.d.ts +10 -0
  74. package/dist/components/onboarding/upsell/UpsellFutoBackupsBadge.svelte +7 -0
  75. package/dist/components/onboarding/upsell/UpsellFutoBackupsBadge.svelte.d.ts +18 -0
  76. package/dist/components/onboarding/upsell/UpsellHowItWorks.svelte +46 -0
  77. package/dist/components/onboarding/upsell/UpsellHowItWorks.svelte.d.ts +6 -0
  78. package/dist/components/onboarding/upsell/UpsellModal.svelte +43 -0
  79. package/dist/components/onboarding/upsell/UpsellModal.svelte.d.ts +9 -0
  80. package/dist/components/onboarding/upsell/UpsellPage.svelte +39 -0
  81. package/dist/components/onboarding/upsell/UpsellPage.svelte.d.ts +13 -0
  82. package/dist/components/onboarding/upsell/UpsellProtectYourImmichLibrary.svelte +58 -0
  83. package/dist/components/onboarding/upsell/UpsellProtectYourImmichLibrary.svelte.d.ts +8 -0
  84. package/dist/components/schedules/RepositoryPicker.svelte +45 -38
  85. package/dist/components/schedules/ScheduleItem.svelte +32 -26
  86. package/dist/components/schedules/ScheduleList.svelte +16 -10
  87. package/dist/components/settings/GlobalSettings.svelte +8 -0
  88. package/dist/components/settings/GlobalSettings.svelte.d.ts +18 -0
  89. package/dist/components/settings/SettingsBackends.svelte +16 -0
  90. package/dist/components/settings/SettingsBackends.svelte.d.ts +18 -0
  91. package/dist/components/settings/SettingsBilling.svelte +69 -0
  92. package/dist/components/settings/SettingsBilling.svelte.d.ts +6 -0
  93. package/dist/components/settings/SettingsNotifications.svelte +47 -0
  94. package/dist/components/settings/SettingsNotifications.svelte.d.ts +6 -0
  95. package/dist/components/test/ImmichTestUi.svelte +129 -27
  96. package/dist/components/test/TestUi.svelte +4 -4
  97. package/dist/components/test/dashboard/ActiveJobs.svelte +2 -2
  98. package/dist/components/test/immich/MockImmichPhotos.svelte +36 -0
  99. package/dist/components/test/immich/MockImmichPhotos.svelte.d.ts +18 -0
  100. package/dist/components/test/immich/MockImmichPurchaseInfo.svelte +29 -0
  101. package/dist/components/test/immich/MockImmichPurchaseInfo.svelte.d.ts +18 -0
  102. package/dist/components/test/immich/MockImmichStorageSpace.svelte +37 -0
  103. package/dist/components/test/immich/MockImmichStorageSpace.svelte.d.ts +6 -0
  104. package/dist/components/ui/Accordion.svelte +67 -0
  105. package/dist/components/ui/Accordion.svelte.d.ts +11 -0
  106. package/dist/components/ui/ListToolbar.svelte +60 -0
  107. package/dist/components/ui/ListToolbar.svelte.d.ts +16 -0
  108. package/dist/components/ui/PageLayout.svelte +21 -9
  109. package/dist/components/ui/PageLayout.svelte.d.ts +1 -0
  110. package/dist/components/ui/Pagination.svelte +80 -0
  111. package/dist/components/ui/Pagination.svelte.d.ts +8 -0
  112. package/dist/components/ui/PathListField.svelte +17 -14
  113. package/dist/components/ui/StackList.svelte +18 -18
  114. package/dist/components/ui/StackList.svelte.d.ts +6 -27
  115. package/dist/components/ui/StackListItem.svelte +47 -25
  116. package/dist/components/ui/StackListItem.svelte.d.ts +5 -2
  117. package/dist/components/ui/StackListOption.svelte +33 -0
  118. package/dist/components/ui/StackListOption.svelte.d.ts +11 -0
  119. package/dist/components/ui/StackListPlaceholder.svelte +12 -0
  120. package/dist/components/ui/StackListPlaceholder.svelte.d.ts +7 -0
  121. package/dist/components/ui/StepNumber.svelte +20 -0
  122. package/dist/components/ui/StepNumber.svelte.d.ts +7 -0
  123. package/dist/components/util/Suspense.svelte +7 -3
  124. package/dist/components/util/YuccaContext.svelte +1 -1
  125. package/dist/events.js +1 -1
  126. package/dist/fetch-client.d.ts +2 -0
  127. package/dist/index.d.ts +6 -1
  128. package/dist/index.js +6 -1
  129. package/dist/services/backend.service.js +3 -0
  130. package/dist/services/immich.integration.service.d.ts +1 -1
  131. package/dist/services/immich.integration.service.js +2 -3
  132. package/dist/services/integrations.service.d.ts +1 -0
  133. package/dist/services/integrations.service.js +2 -1
  134. package/dist/services/log.service.svelte.d.ts +12 -0
  135. package/dist/services/log.service.svelte.js +15 -1
  136. package/dist/services/repository.service.js +9 -3
  137. package/dist/services/runHistory.service.js +2 -2
  138. package/dist/utils/format.d.ts +1 -1
  139. package/dist/utils/format.js +2 -2
  140. package/package.json +3 -3
  141. package/dist/components/backups/dialogs/ViewLogModal.svelte +0 -208
  142. package/dist/components/backups/dialogs/ViewLogModal.svelte.d.ts +0 -7
  143. package/dist/components/integrations/immich/ImmichConfigureBackup.svelte +0 -403
  144. package/dist/components/integrations/immich/ImmichConfigureBackup.svelte.d.ts +0 -9
  145. package/dist/components/integrations/immich/ImmichConfirmDefaultBackup.svelte +0 -80
  146. package/dist/components/integrations/immich/ImmichConfirmDefaultBackup.svelte.d.ts +0 -8
  147. package/dist/components/onboarding/stages/OnboardingStageKeyConfirm.svelte +0 -56
  148. package/dist/components/onboarding/stages/OnboardingStageKeyConfirm.svelte.d.ts +0 -9
  149. package/dist/components/onboarding/stages/OnboardingStageKeyIntro.svelte +0 -50
  150. package/dist/components/onboarding/stages/OnboardingStageKeyIntro.svelte.d.ts +0 -7
  151. package/dist/components/onboarding/stages/OnboardingStageKeySave.svelte +0 -44
  152. package/dist/components/onboarding/stages/OnboardingStageKeySave.svelte.d.ts +0 -8
  153. package/dist/components/onboarding/stages/OnboardingStageWelcome.svelte +0 -56
  154. package/dist/components/onboarding/stages/OnboardingStageWelcome.svelte.d.ts +0 -9
  155. package/dist/components/util/TimedButton.svelte +0 -37
  156. package/dist/components/util/TimedButton.svelte.d.ts +0 -7
@@ -0,0 +1,207 @@
1
+ <script lang="ts">
2
+ import BackendsList from "../../../backends/BackendsList.svelte";
3
+ import Accordion from "../../../ui/Accordion.svelte";
4
+ import OnEvents from "../../../util/OnEvents.svelte";
5
+ import type { RetentionPolicyDto } from "../../../../fetch-client";
6
+ import {
7
+ useConfigureImmichIntegration,
8
+ useIntegrationEventHandler,
9
+ useIntegrations,
10
+ } from "../../../../services/integrations.service";
11
+ import {
12
+ useRepositories,
13
+ useRepositoryEventHandler,
14
+ } from "../../../../services/repository.service";
15
+ import {
16
+ useScheduleEventHandler,
17
+ useSchedules,
18
+ } from "../../../../services/schedule.service";
19
+ import {
20
+ Button,
21
+ Field,
22
+ HStack,
23
+ Input,
24
+ LoadingSpinner,
25
+ Select,
26
+ Stack,
27
+ Switch,
28
+ } from "@immich/ui";
29
+ import { mdiDownloadBox } from "@mdi/js";
30
+
31
+ type RetentionChoice = {
32
+ key: string;
33
+ label: string;
34
+ policy: RetentionPolicyDto | null;
35
+ };
36
+
37
+ const retentionChoices: RetentionChoice[] = [
38
+ { key: "15d", label: "After 15 days", policy: { keepWithin: "15d" } },
39
+ { key: "30d", label: "After 30 days", policy: { keepWithin: "30d" } },
40
+ { key: "60d", label: "After 60 days", policy: { keepWithin: "60d" } },
41
+ { key: "90d", label: "After 90 days", policy: { keepWithin: "90d" } },
42
+ { key: "2", label: "Keep latest two backups", policy: { keepLast: 2 } },
43
+ { key: "never", label: "Never (keep all backups)", policy: null },
44
+ ];
45
+
46
+ const defaultRetentionKey = "60d";
47
+
48
+ const integrations = useIntegrations();
49
+ const repositories = useRepositories();
50
+ const schedules = useSchedules();
51
+ const mutation = useConfigureImmichIntegration();
52
+
53
+ const { onIntegrationUpdate } = useIntegrationEventHandler();
54
+ const { onRepositoryCreate, onRepositoryUpdate, onRepositoryDelete } =
55
+ useRepositoryEventHandler();
56
+ const { onScheduleCreate, onScheduleUpdate, onScheduleDelete } =
57
+ useScheduleEventHandler();
58
+
59
+ const integration = $derived(integrations.data?.immichIntegration);
60
+
61
+ const repository = $derived(
62
+ integration
63
+ ? repositories.data?.find((entry) => entry.id === integration.id)
64
+ : undefined,
65
+ );
66
+
67
+ const schedule = $derived(
68
+ integration
69
+ ? schedules.data?.find((entry) => entry.id === integration.scheduleId)
70
+ : undefined,
71
+ );
72
+
73
+ const isLoading = $derived(
74
+ integrations.isLoading || repositories.isLoading || schedules.isLoading,
75
+ );
76
+
77
+ let name = $state("");
78
+ let worm = $state(false);
79
+ let retentionKey = $state(defaultRetentionKey);
80
+ let loaded = false;
81
+
82
+ const load = () => {
83
+ if (!repository) {
84
+ return;
85
+ }
86
+
87
+ name = repository.name;
88
+ worm = repository.worm;
89
+
90
+ const policy = repository.configuration?.retentionPolicy ?? null;
91
+ retentionKey =
92
+ retentionChoices.find(
93
+ (choice) => JSON.stringify(choice.policy) === JSON.stringify(policy),
94
+ )?.key ?? defaultRetentionKey;
95
+ };
96
+
97
+ $effect(() => {
98
+ if (loaded || !repository) {
99
+ return;
100
+ }
101
+
102
+ loaded = true;
103
+ load();
104
+ });
105
+
106
+ const onSave = () => {
107
+ if (!integration || !repository || !schedule) {
108
+ return;
109
+ }
110
+
111
+ mutation.mutate({
112
+ name,
113
+ cron: schedule.cron,
114
+ dataFolders: integration.configuration.dataFolders,
115
+ backupConfiguration: integration.configuration.backupConfiguration,
116
+ libraries: integration.configuration.libraries,
117
+ worm,
118
+ retentionPolicy: worm
119
+ ? null
120
+ : (retentionChoices.find((choice) => choice.key === retentionKey)
121
+ ?.policy ?? null),
122
+ });
123
+ };
124
+ </script>
125
+
126
+ <OnEvents
127
+ {onIntegrationUpdate}
128
+ {onRepositoryCreate}
129
+ {onRepositoryUpdate}
130
+ {onRepositoryDelete}
131
+ {onScheduleCreate}
132
+ {onScheduleUpdate}
133
+ {onScheduleDelete}
134
+ />
135
+
136
+ <Accordion
137
+ title="Storage"
138
+ subtitle="Manage the backup name, where it is stored, and how long backups are kept."
139
+ icon={mdiDownloadBox}
140
+ >
141
+ <Stack gap={4} class="pt-2">
142
+ {#if isLoading}
143
+ <LoadingSpinner />
144
+ {:else}
145
+ <Field
146
+ label="Backup name"
147
+ description="How this backup is labelled across FUTO Backups."
148
+ required="indicator"
149
+ >
150
+ <Input bind:value={name} />
151
+ </Field>
152
+
153
+ <BackendsList {repository} />
154
+
155
+ {#if repository?.worm}
156
+ <Field
157
+ label="Write-only"
158
+ description="Once written, backups can't be removed. This can't be turned off again."
159
+ readOnly // @immich/ui forces enabled={false} when disabled={true}
160
+ >
161
+ <Switch checked class="grayscale brightness-90" />
162
+ </Field>
163
+ {:else}
164
+ <Field
165
+ label="Write-only"
166
+ description="Once written, backups can't be removed. This can't be turned off again."
167
+ >
168
+ <Switch bind:checked={worm} />
169
+ </Field>
170
+ {/if}
171
+
172
+ <Field
173
+ label="Delete old backups"
174
+ description="Older snapshots are pruned automatically."
175
+ disabled={worm}
176
+ >
177
+ <Select
178
+ options={retentionChoices.map(({ key, label }) => ({
179
+ value: key,
180
+ label,
181
+ }))}
182
+ value={worm ? "never" : retentionKey}
183
+ onChange={(value) => (retentionKey = value)}
184
+ />
185
+ </Field>
186
+
187
+ <HStack gap={2} class="justify-end">
188
+ <Button
189
+ shape="round"
190
+ color="secondary"
191
+ disabled={mutation.isPending}
192
+ onclick={load}
193
+ >
194
+ Reset
195
+ </Button>
196
+ <Button
197
+ shape="round"
198
+ disabled={name.trim().length === 0}
199
+ loading={mutation.isPending}
200
+ onclick={onSave}
201
+ >
202
+ Save
203
+ </Button>
204
+ </HStack>
205
+ {/if}
206
+ </Stack>
207
+ </Accordion>
@@ -0,0 +1,3 @@
1
+ declare const ImmichSettingsStorage: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type ImmichSettingsStorage = ReturnType<typeof ImmichSettingsStorage>;
3
+ export default ImmichSettingsStorage;
@@ -1,12 +1,11 @@
1
1
  <script lang="ts">
2
+ import { Button, Card, CardBody, HStack, Text, VStack } from "@immich/ui";
2
3
  import {
3
- Button,
4
- Card,
5
- CardBody,
6
- HStack,
7
- VStack,
8
- } from "@immich/ui";
9
- import { mdiAsterisk, mdiContentCopy, mdiFile, mdiPrinter } from "@mdi/js";
4
+ mdiAsterisk,
5
+ mdiContentCopy,
6
+ mdiDownloadOutline,
7
+ mdiPrinterOutline,
8
+ } from "@mdi/js";
10
9
 
11
10
  type Props = {
12
11
  code: string;
@@ -69,29 +68,44 @@
69
68
  };
70
69
  </script>
71
70
 
72
- <Card class="bg-primary-50 shadow-none">
71
+ <Card color="primary" class="shadow-none">
73
72
  <CardBody class="flex justify-center">
74
- <pre><code>{code}</code></pre>
73
+ <Text size="large" fontWeight="semi-bold"
74
+ ><pre><code>{code}</code></pre></Text
75
+ >
75
76
  </CardBody>
76
77
  </Card>
77
78
 
78
79
  <VStack>
79
- <HStack>
80
- <Button leadingIcon={mdiPrinter} variant="outline" onclick={print}>
81
- Print
82
- </Button>
83
- <Button leadingIcon={mdiFile} variant="outline" onclick={saveFile}>
84
- Save as file
85
- </Button>
86
- </HStack>
87
80
  <Button
88
- leadingIcon={mdiContentCopy}
81
+ shape="round"
89
82
  variant="outline"
83
+ leadingIcon={mdiContentCopy}
90
84
  onclick={copyToClipboard}
91
85
  >
92
- Copy to clipboard
86
+ Copy recovery key
93
87
  </Button>
94
- {#if PasswordCredential}
88
+
89
+ <HStack>
90
+ <Button
91
+ leadingIcon={mdiDownloadOutline}
92
+ variant="ghost"
93
+ shape="round"
94
+ onclick={saveFile}
95
+ >
96
+ Download
97
+ </Button>
98
+ <Button
99
+ leadingIcon={mdiPrinterOutline}
100
+ variant="ghost"
101
+ shape="round"
102
+ onclick={print}
103
+ >
104
+ Print
105
+ </Button>
106
+ </HStack>
107
+
108
+ {#if /* intentionally hide this to not overcrowd the modal */ PasswordCredential && false}
95
109
  <Button
96
110
  leadingIcon={mdiAsterisk}
97
111
  variant="outline"
@@ -1,20 +1,23 @@
1
1
  <script lang="ts">
2
2
  import type { OnboardingStatusResponseDto } from "../../fetch-client";
3
+ import {
4
+ handleSetupLocalStorage,
5
+ handleStartYuccaLogin,
6
+ } from "../../services/backend.service";
3
7
  import {
4
8
  handleConfirmRecoveryKey,
5
9
  handleCurrentRecoveryKey,
6
10
  handleSkipOnboardingExtraConfig,
7
11
  } from "../../services/onboarding.service";
12
+ import { Modal, ModalBody } from "@immich/ui";
8
13
  import { onMount } from "svelte";
9
14
  import CreateFirstBackup from "./stages/SampleCreateFirstBackup.svelte";
10
15
  import CreateFirstSchedule from "./stages/SampleCreateFirstSchedule.svelte";
11
- import BackupOptions from "./stages/OnboardingStageBackupServices.svelte";
12
- import ConfirmKey from "./stages/OnboardingStageKeyConfirm.svelte";
13
16
  import ImportKey from "./stages/OnboardingStageKeyImport.svelte";
14
- import KeyIntro from "./stages/OnboardingStageKeyIntro.svelte";
15
- import SaveKey from "./stages/OnboardingStageKeySave.svelte";
16
17
  import Telemetry from "./stages/OnboardingStageTelemetry.svelte";
17
- import Welcome from "./stages/OnboardingStageWelcome.svelte";
18
+ import StepFinishSetup from "./steps/OnboardingStep1FinishSetup.svelte";
19
+ import StepConnectAccount from "./steps/OnboardingStep2ConnectAccount.svelte";
20
+ import StepSaveRecoveryKey from "./steps/OnboardingStep3SaveRecoveryKey.svelte";
18
21
 
19
22
  type Props = {
20
23
  status: OnboardingStatusResponseDto;
@@ -25,21 +28,23 @@
25
28
  const { status, onFinish, onCancel }: Props = $props();
26
29
 
27
30
  let code = $state("");
31
+ let confirming = $state(false);
28
32
 
29
33
  // svelte-ignore state_referenced_locally
30
34
  let stage:
31
- | "welcome"
35
+ | "intro"
32
36
  | "telemetry"
33
- | `key-${"intro" | "save" | "confirm" | "import"}`
34
- | "backup-service"
37
+ | "key"
38
+ | "key-import"
39
+ | "connect"
35
40
  | "backup-create"
36
41
  | "schedule-create" = $state(
37
42
  !status.hasOnboardedKey
38
- ? "welcome"
43
+ ? "intro"
39
44
  : status.hasTelemetry === "none"
40
45
  ? "telemetry"
41
46
  : !status.hasBackend
42
- ? "backup-service"
47
+ ? "connect"
43
48
  : !status.hasBackup
44
49
  ? "backup-create"
45
50
  : "schedule-create",
@@ -57,56 +62,70 @@
57
62
  };
58
63
 
59
64
  const onConfirmKey = async () => {
60
- await handleConfirmRecoveryKey();
65
+ confirming = true;
66
+
67
+ try {
68
+ await handleConfirmRecoveryKey();
61
69
 
62
- if (status.hasBackend) {
63
- onFinish();
64
- } else {
65
- stage = "backup-service";
70
+ if (status.hasBackend) {
71
+ onFinish();
72
+ } else {
73
+ stage = "connect";
74
+ }
75
+ } finally {
76
+ confirming = false;
66
77
  }
67
78
  };
79
+
80
+ const onBackendReady = () => (stage = "backup-create");
68
81
  </script>
69
82
 
70
- {#if stage === "welcome"}
71
- <Welcome
72
- onNext={() => (stage = status.hasTelemetry === "none" ? "telemetry" : "key-intro")}
73
- onImportKey={() => (stage = "key-import")}
74
- {onCancel}
75
- />
83
+ {#if stage === "intro"}
84
+ <Modal size="small" title="FUTO Backups" onClose={onCancel}>
85
+ <ModalBody>
86
+ <StepFinishSetup
87
+ onContinue={() =>
88
+ (stage = status.hasTelemetry === "none" ? "telemetry" : "key")}
89
+ onImportKey={() => (stage = "key-import")}
90
+ />
91
+ </ModalBody>
92
+ </Modal>
76
93
  {:else if stage === "telemetry"}
77
94
  <Telemetry
78
95
  onContinue={() =>
79
96
  (stage = !status.hasOnboardedKey
80
- ? "key-intro"
97
+ ? "key"
81
98
  : !status.hasBackend
82
- ? "backup-service"
99
+ ? "connect"
83
100
  : !status.hasBackup
84
101
  ? "backup-create"
85
102
  : "schedule-create")}
86
103
  {onCancel}
87
104
  />
88
- {:else if stage === "key-intro"}
89
- <KeyIntro onNext={() => (stage = "key-save")} {onCancel} />
90
- {:else if stage === "key-save"}
91
- <SaveKey {code} onNext={() => (stage = "key-confirm")} {onCancel} />
92
- {:else if stage === "key-confirm"}
93
- <ConfirmKey
94
- {code}
95
- {onConfirmKey}
96
- onBack={() => (stage = "key-save")}
97
- {onCancel}
98
- />
105
+ {:else if stage === "key"}
106
+ <Modal size="small" title="FUTO Backups" onClose={onCancel}>
107
+ <ModalBody>
108
+ <StepSaveRecoveryKey {code} onContinue={onConfirmKey} loading={confirming} />
109
+ </ModalBody>
110
+ </Modal>
99
111
  {:else if stage === "key-import"}
100
112
  <ImportKey
101
- onStart={() => (stage = "welcome")}
113
+ onStart={() => (stage = "intro")}
102
114
  onImported={(key) => {
103
115
  code = key;
104
- stage = "key-save";
116
+ stage = "key";
105
117
  }}
106
118
  {onCancel}
107
119
  />
108
- {:else if stage === "backup-service"}
109
- <BackupOptions onNext={() => (stage = "backup-create")} {onCancel} />
120
+ {:else if stage === "connect"}
121
+ <Modal size="small" title="FUTO Backups" onClose={onCancel}>
122
+ <ModalBody>
123
+ <StepConnectAccount
124
+ onConnect={() => handleStartYuccaLogin(onBackendReady)}
125
+ onLocalStorage={() => handleSetupLocalStorage(onBackendReady)}
126
+ />
127
+ </ModalBody>
128
+ </Modal>
110
129
  {:else if stage === "backup-create"}
111
130
  <CreateFirstBackup onNext={() => (stage = "schedule-create")} {onSkip} />
112
131
  {:else if stage === "schedule-create"}
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import StackList from "../../ui/StackList.svelte";
3
- import StackListItem from "../../ui/StackListItem.svelte";
3
+ import StackListOption from "../../ui/StackListOption.svelte";
4
4
  import {
5
5
  handleSetupLocalStorage,
6
6
  handleStartYuccaLogin,
@@ -15,7 +15,7 @@
15
15
  Stack,
16
16
  Text,
17
17
  } from "@immich/ui";
18
- import { mdiChevronRight, mdiHarddisk, mdiShieldCheck } from "@mdi/js";
18
+ import { mdiHarddisk, mdiShieldCheck } from "@mdi/js";
19
19
 
20
20
  type Props = {
21
21
  restore?: boolean;
@@ -44,34 +44,20 @@
44
44
  >
45
45
  <ModalBody>
46
46
  <StackList>
47
- <StackListItem onclick={onFutoBackups}>
47
+ <StackListOption title="FUTO Backups" onclick={onFutoBackups}>
48
48
  {#snippet icon()}
49
- <Icon icon={mdiShieldCheck} size="36px" />
49
+ <Icon icon={mdiShieldCheck} />
50
50
  {/snippet}
51
51
 
52
- <Stack gap={0}>
53
- <Text class="font-bold">FUTO Backups</Text>
54
- <Text>Simple, hosted backups.</Text>
55
- </Stack>
56
-
57
- {#snippet trailing()}
58
- <Icon icon={mdiChevronRight} />
59
- {/snippet}
60
- </StackListItem>
61
- <StackListItem onclick={onLocalBackups}>
52
+ Simple, hosted backups.
53
+ </StackListOption>
54
+ <StackListOption title="Local Storage" onclick={onLocalBackups}>
62
55
  {#snippet icon()}
63
- <Icon icon={mdiHarddisk} size="36px" />
56
+ <Icon icon={mdiHarddisk} />
64
57
  {/snippet}
65
58
 
66
- <Stack gap={0}>
67
- <Text class="font-bold">Local Storage</Text>
68
- <Text>A folder on this computer.</Text>
69
- </Stack>
70
-
71
- {#snippet trailing()}
72
- <Icon icon={mdiChevronRight} />
73
- {/snippet}
74
- </StackListItem>
59
+ A folder on this computer.
60
+ </StackListOption>
75
61
  </StackList>
76
62
  </ModalBody>
77
63
  <ModalFooter>
@@ -0,0 +1,44 @@
1
+ <script lang="ts">
2
+ import StepNumber from "../../ui/StepNumber.svelte";
3
+ import { HStack, Stack, Text } from "@immich/ui";
4
+ import OnboardingStepLayout, {
5
+ type OnboardingStepAction,
6
+ } from "./OnboardingStepLayout.svelte";
7
+
8
+ type Props = {
9
+ onContinue: () => void;
10
+ onImportKey?: () => void;
11
+ };
12
+
13
+ const { onContinue, onImportKey }: Props = $props();
14
+
15
+ const steps = [
16
+ "Connect FUTO account",
17
+ "Save your recovery key",
18
+ "Start your first backup",
19
+ ];
20
+
21
+ const actions: OnboardingStepAction[] = [
22
+ { label: "Continue", onClick: () => onContinue() },
23
+ {
24
+ label: "Import key",
25
+ onClick: () => onImportKey?.(),
26
+ $if: () => !!onImportKey,
27
+ },
28
+ ];
29
+ </script>
30
+
31
+ <OnboardingStepLayout
32
+ title="Finish setting up FUTO Backups"
33
+ description="Your subscription is active. Finish setup to create your recovery key and start backing up your Immich library."
34
+ {actions}
35
+ >
36
+ <Stack gap={3}>
37
+ {#each steps as step, index (step)}
38
+ <HStack gap={3} class="items-center">
39
+ <StepNumber step={index + 1} size="small" />
40
+ <Text size="small">{step}</Text>
41
+ </HStack>
42
+ {/each}
43
+ </Stack>
44
+ </OnboardingStepLayout>
@@ -0,0 +1,7 @@
1
+ type Props = {
2
+ onContinue: () => void;
3
+ onImportKey?: () => void;
4
+ };
5
+ declare const OnboardingStep1FinishSetup: import("svelte").Component<Props, {}, "">;
6
+ type OnboardingStep1FinishSetup = ReturnType<typeof OnboardingStep1FinishSetup>;
7
+ export default OnboardingStep1FinishSetup;
@@ -0,0 +1,50 @@
1
+ <script lang="ts">
2
+ import { HStack, Icon, Stack, Text } from "@immich/ui";
3
+ import { mdiCheckCircle } from "@mdi/js";
4
+ import OnboardingStepLayout, {
5
+ type OnboardingStepAction,
6
+ } from "./OnboardingStepLayout.svelte";
7
+
8
+ type Props = {
9
+ onConnect: () => void;
10
+ onLocalStorage?: () => void;
11
+ };
12
+
13
+ const { onConnect, onLocalStorage }: Props = $props();
14
+
15
+ const outcomes = [
16
+ "Link this server to your FUTO Backups subscription",
17
+ "Allow Immich to upload encrypted backups",
18
+ "Return you here to finish setup",
19
+ ];
20
+
21
+ const actions: OnboardingStepAction[] = [
22
+ { label: "Connect account", onClick: () => onConnect() },
23
+ {
24
+ label: "Use local storage",
25
+ onClick: () => onLocalStorage?.(),
26
+ $if: () => !!onLocalStorage,
27
+ },
28
+ ];
29
+ </script>
30
+
31
+ <OnboardingStepLayout
32
+ title="Connect your FUTO account"
33
+ description="Connect your FUTO account to this Immich server so FUTO Backups can store your encrypted backups."
34
+ {actions}
35
+ >
36
+ <Stack gap={3}>
37
+ <Text fontWeight="semi-bold" size="small">This will:</Text>
38
+
39
+ {#each outcomes as outcome (outcome)}
40
+ <HStack gap={3} class="items-center">
41
+ <Icon
42
+ icon={mdiCheckCircle}
43
+ size="1.25rem"
44
+ class="text-primary shrink-0"
45
+ />
46
+ <Text size="small">{outcome}</Text>
47
+ </HStack>
48
+ {/each}
49
+ </Stack>
50
+ </OnboardingStepLayout>
@@ -0,0 +1,7 @@
1
+ type Props = {
2
+ onConnect: () => void;
3
+ onLocalStorage?: () => void;
4
+ };
5
+ declare const OnboardingStep2ConnectAccount: import("svelte").Component<Props, {}, "">;
6
+ type OnboardingStep2ConnectAccount = ReturnType<typeof OnboardingStep2ConnectAccount>;
7
+ export default OnboardingStep2ConnectAccount;
@@ -0,0 +1,45 @@
1
+ <script lang="ts">
2
+ import RecoveryKeyDisplay from "../RecoveryKeyDisplay.svelte";
3
+ import { Checkbox, HStack, Stack, Text } from "@immich/ui";
4
+ import OnboardingStepLayout, {
5
+ type OnboardingStepAction,
6
+ } from "./OnboardingStepLayout.svelte";
7
+
8
+ type Props = {
9
+ code: string;
10
+ onContinue: () => void;
11
+ loading?: boolean;
12
+ };
13
+
14
+ const { code, onContinue, loading = false }: Props = $props();
15
+
16
+ let saved = $state(false);
17
+
18
+ const actions = $derived<OnboardingStepAction[]>([
19
+ {
20
+ label: "Continue",
21
+ onClick: onContinue,
22
+ disabled: !saved,
23
+ loading,
24
+ },
25
+ ]);
26
+ </script>
27
+
28
+ <OnboardingStepLayout
29
+ title="Save your recovery key"
30
+ description="You'll need this key to restore your encrypted backups. Save it somewhere safe before continuing. FUTO cannot recover this key if it is lost."
31
+ {actions}
32
+ >
33
+ <Stack gap={4}>
34
+ <RecoveryKeyDisplay {code} />
35
+
36
+ <HStack gap={3} class="items-center">
37
+ <Checkbox bind:checked={saved} id="recovery-key-saved" />
38
+ <Text size="small">
39
+ <label for="recovery-key-saved">
40
+ I saved my recovery key somewhere safe.
41
+ </label>
42
+ </Text>
43
+ </HStack>
44
+ </Stack>
45
+ </OnboardingStepLayout>
@@ -0,0 +1,8 @@
1
+ type Props = {
2
+ code: string;
3
+ onContinue: () => void;
4
+ loading?: boolean;
5
+ };
6
+ declare const OnboardingStep3SaveRecoveryKey: import("svelte").Component<Props, {}, "">;
7
+ type OnboardingStep3SaveRecoveryKey = ReturnType<typeof OnboardingStep3SaveRecoveryKey>;
8
+ export default OnboardingStep3SaveRecoveryKey;