@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.
Files changed (163) 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 +23 -12
  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 +136 -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 +109 -0
  38. package/dist/components/integrations/immich/ImmichBackupsCard.svelte.d.ts +13 -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 +16 -3
  42. package/dist/components/integrations/immich/ImmichBackupsPage.svelte.d.ts +8 -1
  43. package/dist/components/integrations/immich/ImmichBackupsSidebarItem.svelte +102 -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 +32 -0
  73. package/dist/components/onboarding/upsell/UpsellFrequentlyAskedQuestions.svelte.d.ts +11 -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 +40 -0
  81. package/dist/components/onboarding/upsell/UpsellPage.svelte.d.ts +14 -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 +143 -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/MockImmichHideBackupsReminder.svelte +12 -0
  99. package/dist/components/test/immich/MockImmichHideBackupsReminder.svelte.d.ts +3 -0
  100. package/dist/components/test/immich/MockImmichPhotos.svelte +36 -0
  101. package/dist/components/test/immich/MockImmichPhotos.svelte.d.ts +18 -0
  102. package/dist/components/test/immich/MockImmichPurchaseInfo.svelte +29 -0
  103. package/dist/components/test/immich/MockImmichPurchaseInfo.svelte.d.ts +18 -0
  104. package/dist/components/test/immich/MockImmichServerStatus.svelte +9 -0
  105. package/dist/components/test/immich/MockImmichServerStatus.svelte.d.ts +18 -0
  106. package/dist/components/test/immich/MockImmichSidebar.svelte +20 -0
  107. package/dist/components/test/immich/MockImmichSidebar.svelte.d.ts +7 -0
  108. package/dist/components/test/immich/MockImmichStorageSpace.svelte +25 -0
  109. package/dist/components/test/immich/MockImmichStorageSpace.svelte.d.ts +7 -0
  110. package/dist/components/ui/Accordion.svelte +67 -0
  111. package/dist/components/ui/Accordion.svelte.d.ts +11 -0
  112. package/dist/components/ui/ListToolbar.svelte +60 -0
  113. package/dist/components/ui/ListToolbar.svelte.d.ts +16 -0
  114. package/dist/components/ui/PageLayout.svelte +21 -9
  115. package/dist/components/ui/PageLayout.svelte.d.ts +1 -0
  116. package/dist/components/ui/Pagination.svelte +80 -0
  117. package/dist/components/ui/Pagination.svelte.d.ts +8 -0
  118. package/dist/components/ui/PathListField.svelte +17 -14
  119. package/dist/components/ui/StackList.svelte +18 -18
  120. package/dist/components/ui/StackList.svelte.d.ts +6 -27
  121. package/dist/components/ui/StackListItem.svelte +47 -25
  122. package/dist/components/ui/StackListItem.svelte.d.ts +5 -2
  123. package/dist/components/ui/StackListOption.svelte +33 -0
  124. package/dist/components/ui/StackListOption.svelte.d.ts +11 -0
  125. package/dist/components/ui/StackListPlaceholder.svelte +12 -0
  126. package/dist/components/ui/StackListPlaceholder.svelte.d.ts +7 -0
  127. package/dist/components/ui/StepNumber.svelte +20 -0
  128. package/dist/components/ui/StepNumber.svelte.d.ts +7 -0
  129. package/dist/components/util/Suspense.svelte +7 -3
  130. package/dist/components/util/YuccaContext.svelte +1 -1
  131. package/dist/events.js +1 -1
  132. package/dist/fetch-client.d.ts +2 -0
  133. package/dist/index.d.ts +6 -1
  134. package/dist/index.js +6 -1
  135. package/dist/services/backend.service.js +3 -0
  136. package/dist/services/immich.integration.service.d.ts +1 -1
  137. package/dist/services/immich.integration.service.js +2 -3
  138. package/dist/services/integrations.service.d.ts +1 -0
  139. package/dist/services/integrations.service.js +2 -1
  140. package/dist/services/log.service.svelte.d.ts +12 -0
  141. package/dist/services/log.service.svelte.js +15 -1
  142. package/dist/services/repository.service.js +9 -3
  143. package/dist/services/runHistory.service.d.ts +1 -1
  144. package/dist/services/runHistory.service.js +5 -4
  145. package/dist/utils/format.d.ts +1 -1
  146. package/dist/utils/format.js +2 -2
  147. package/package.json +3 -3
  148. package/dist/components/backups/dialogs/ViewLogModal.svelte +0 -208
  149. package/dist/components/backups/dialogs/ViewLogModal.svelte.d.ts +0 -7
  150. package/dist/components/integrations/immich/ImmichConfigureBackup.svelte +0 -403
  151. package/dist/components/integrations/immich/ImmichConfigureBackup.svelte.d.ts +0 -9
  152. package/dist/components/integrations/immich/ImmichConfirmDefaultBackup.svelte +0 -80
  153. package/dist/components/integrations/immich/ImmichConfirmDefaultBackup.svelte.d.ts +0 -8
  154. package/dist/components/onboarding/stages/OnboardingStageKeyConfirm.svelte +0 -56
  155. package/dist/components/onboarding/stages/OnboardingStageKeyConfirm.svelte.d.ts +0 -9
  156. package/dist/components/onboarding/stages/OnboardingStageKeyIntro.svelte +0 -50
  157. package/dist/components/onboarding/stages/OnboardingStageKeyIntro.svelte.d.ts +0 -7
  158. package/dist/components/onboarding/stages/OnboardingStageKeySave.svelte +0 -44
  159. package/dist/components/onboarding/stages/OnboardingStageKeySave.svelte.d.ts +0 -8
  160. package/dist/components/onboarding/stages/OnboardingStageWelcome.svelte +0 -56
  161. package/dist/components/onboarding/stages/OnboardingStageWelcome.svelte.d.ts +0 -9
  162. package/dist/components/util/TimedButton.svelte +0 -37
  163. package/dist/components/util/TimedButton.svelte.d.ts +0 -7
@@ -11,14 +11,17 @@
11
11
  Logo,
12
12
  NavbarGroup,
13
13
  NavbarItem,
14
+ Stack,
14
15
  ThemeSwitcher,
15
16
  } from "@immich/ui";
16
17
  import {
17
18
  mdiAccountMultipleOutline,
18
19
  mdiArchiveArrowDownOutline,
19
- mdiBackupRestore,
20
20
  mdiBellOutline,
21
21
  mdiCastVariant,
22
+ mdiChartBoxOutline,
23
+ mdiCogOutline,
24
+ mdiFormatListBulletedType,
22
25
  mdiHeartOutline,
23
26
  mdiImageAlbum,
24
27
  mdiImageMultipleOutline,
@@ -33,9 +36,20 @@
33
36
  } from "@mdi/js";
34
37
  import { options } from "../../options";
35
38
  import { onDestroy } from "svelte";
36
- import ImmichManageBackup from "../integrations/immich/ImmichManageBackup.svelte";
39
+ import ImmichBackupsNavButton from "../integrations/immich/ImmichBackupsNavButton.svelte";
40
+ import ImmichBackupsCard from "../integrations/immich/ImmichBackupsCard.svelte";
41
+ import MockImmichPhotos from "./immich/MockImmichPhotos.svelte";
42
+ import MockImmichServerStatus from "./immich/MockImmichServerStatus.svelte";
43
+ import MockImmichPurchaseInfo from "./immich/MockImmichPurchaseInfo.svelte";
44
+ import MockImmichStorageSpace from "./immich/MockImmichStorageSpace.svelte";
45
+ import ImmichBackupsPage from "../integrations/immich/ImmichBackupsPage.svelte";
37
46
  import ImmichOnboardingRestoreFlow from "../integrations/immich/ImmichOnboardingRestoreFlow.svelte";
38
- import ImmichOnboardingSetupFlow from "../integrations/immich/ImmichOnboardingSetupFlow.svelte";
47
+ import OnEvents from "../util/OnEvents.svelte";
48
+ import MockImmichHideBackupsReminder from "./immich/MockImmichHideBackupsReminder.svelte";
49
+ import {
50
+ useIntegrationEventHandler,
51
+ useIntegrations,
52
+ } from "../../services/integrations.service";
39
53
 
40
54
  type Props = {
41
55
  onExit: () => void;
@@ -44,6 +58,28 @@
44
58
  const { onExit }: Props = $props();
45
59
  const { testUiRestore, demoPadding } = options;
46
60
 
61
+ let route = $state<"photos" | "settings">("photos");
62
+
63
+ const integrations = useIntegrations();
64
+ const { onIntegrationUpdate } = useIntegrationEventHandler();
65
+ const configured = $derived(Boolean(integrations.data?.immichIntegration));
66
+ const twoHoursAgo = new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString();
67
+
68
+ const sampleQuestions = [
69
+ {
70
+ title: "Is FUTO Backups the same as an Immich product key?",
71
+ answer: "Sample answer copy for the FAQ preview.",
72
+ },
73
+ {
74
+ title: "How is FUTO Backups priced?",
75
+ answer: "Sample answer copy for the FAQ preview.",
76
+ },
77
+ {
78
+ title: "Can I use local storage instead?",
79
+ answer: "Sample answer copy for the FAQ preview.",
80
+ },
81
+ ];
82
+
47
83
  demoPadding.set(true);
48
84
  onDestroy(() => demoPadding.set(false));
49
85
 
@@ -51,7 +87,13 @@
51
87
  setProvider(orchestrationApiProvider);
52
88
  </script>
53
89
 
54
- <AppShell>
90
+ <OnEvents {onIntegrationUpdate} />
91
+
92
+ {#snippet hideReminder()}
93
+ <MockImmichHideBackupsReminder />
94
+ {/snippet}
95
+
96
+ <AppShell class="h-full">
55
97
  <AppShellHeader>
56
98
  <div class="grid grid-cols-[--spacing(64)_auto] items-center w-full py-2">
57
99
  <div class="flex flex-row gap-1 mx-4 items-center">
@@ -134,6 +176,17 @@
134
176
  aria-label="Cast"
135
177
  />
136
178
 
179
+ <IconButton
180
+ shape="round"
181
+ color="secondary"
182
+ variant={route === "settings" ? "filled" : "ghost"}
183
+ size="medium"
184
+ icon={mdiCogOutline}
185
+ onclick={() =>
186
+ (route = route === "settings" ? "photos" : "settings")}
187
+ aria-label="Settings"
188
+ />
189
+
137
190
  <button
138
191
  type="button"
139
192
  class="flex ps-2"
@@ -147,26 +200,79 @@
147
200
  </div>
148
201
  </AppShellHeader>
149
202
 
150
- <AppShellSidebar>
151
- <div class="pt-4 pr-2">
152
- <NavbarItem href="#" title="Photos" icon={mdiImageMultipleOutline} />
153
- <NavbarItem href="#" title="Explore" icon={mdiMagnify} />
154
- <NavbarItem href="#" title="Map" icon={mdiMapOutline} />
155
- <NavbarItem href="#" title="Sharing" icon={mdiAccountMultipleOutline} />
156
-
157
- <NavbarGroup title="Library" size="tiny" />
158
-
159
- <NavbarItem href="#" title="Favorites" icon={mdiHeartOutline} />
160
- <NavbarItem
161
- href="#"
162
- title="Albums"
163
- icon={{ icon: mdiImageAlbum, flipped: true }}
164
- />
165
- <NavbarItem href="#" title="Backups" icon={mdiBackupRestore} active />
166
- <NavbarItem href="#" title="Utilities" icon={mdiToolboxOutline} />
167
- <NavbarItem href="#" title="Archive" icon={mdiArchiveArrowDownOutline} />
168
- <NavbarItem href="#" title="Locked folder" icon={mdiLockOutline} />
169
- <NavbarItem href="#" title="Trash" icon={mdiTrashCanOutline} />
203
+ <AppShellSidebar class="relative">
204
+ <div class="flex h-full flex-col pt-4 pr-2">
205
+ {#if route === "settings"}
206
+ <ImmichBackupsNavButton
207
+ {configured}
208
+ active
209
+ onclick={() => {}}
210
+ />
211
+
212
+ <NavbarItem
213
+ href="#"
214
+ title="Users"
215
+ icon={mdiAccountMultipleOutline}
216
+ isActive={() => false}
217
+ />
218
+ <NavbarItem
219
+ href="#"
220
+ title="Job Queues"
221
+ icon={mdiFormatListBulletedType}
222
+ isActive={() => false}
223
+ />
224
+ <NavbarItem
225
+ href="#"
226
+ title="Server Stats"
227
+ icon={mdiChartBoxOutline}
228
+ isActive={() => false}
229
+ />
230
+ <NavbarItem
231
+ href="#"
232
+ title="Settings"
233
+ icon={mdiCogOutline}
234
+ isActive={() => false}
235
+ />
236
+ {:else}
237
+ <NavbarItem
238
+ href="#"
239
+ title="Photos"
240
+ icon={mdiImageMultipleOutline}
241
+ active
242
+ />
243
+ <NavbarItem href="#" title="Explore" icon={mdiMagnify} />
244
+ <NavbarItem href="#" title="Map" icon={mdiMapOutline} />
245
+ <NavbarItem href="#" title="Sharing" icon={mdiAccountMultipleOutline} />
246
+
247
+ <NavbarGroup title="Library" size="tiny" />
248
+
249
+ <NavbarItem href="#" title="Favorites" icon={mdiHeartOutline} />
250
+ <NavbarItem
251
+ href="#"
252
+ title="Albums"
253
+ icon={{ icon: mdiImageAlbum, flipped: true }}
254
+ />
255
+ <NavbarItem href="#" title="Utilities" icon={mdiToolboxOutline} />
256
+ <NavbarItem href="#" title="Archive" icon={mdiArchiveArrowDownOutline} />
257
+ <NavbarItem href="#" title="Locked folder" icon={mdiLockOutline} />
258
+ <NavbarItem href="#" title="Trash" icon={mdiTrashCanOutline} />
259
+ {/if}
260
+
261
+ <Stack gap={4} class="mt-auto p-4">
262
+ <MockImmichStorageSpace>
263
+ <ImmichBackupsCard
264
+ {configured}
265
+ lastBackup={configured ? twoHoursAgo : undefined}
266
+ onclick={() => (route = "settings")}
267
+ />
268
+ </MockImmichStorageSpace>
269
+
270
+ {#if route !== "settings"}
271
+ <MockImmichPurchaseInfo />
272
+ {/if}
273
+
274
+ <MockImmichServerStatus />
275
+ </Stack>
170
276
  </div>
171
277
  </AppShellSidebar>
172
278
 
@@ -175,9 +281,19 @@
175
281
  {onExit}
176
282
  onFinish={() => testUiRestore.set(false)}
177
283
  />
284
+ {:else if route === "settings"}
285
+ <ImmichBackupsPage
286
+ price="$1"
287
+ includedStorage="50 GB"
288
+ questions={[
289
+ {
290
+ title: "Already back up your library elsewhere?",
291
+ answer: hideReminder,
292
+ },
293
+ ...sampleQuestions,
294
+ ]}
295
+ />
178
296
  {:else}
179
- <ImmichOnboardingSetupFlow {onExit}>
180
- <ImmichManageBackup />
181
- </ImmichOnboardingSetupFlow>
297
+ <MockImmichPhotos />
182
298
  {/if}
183
299
  </AppShell>
@@ -19,7 +19,7 @@
19
19
  mdiCog,
20
20
  mdiViewDashboard,
21
21
  } from "@mdi/js";
22
- import BackendsList from "../backends/BackendsList.svelte";
22
+ import GlobalSettings from "../settings/GlobalSettings.svelte";
23
23
  import BackupsList from "../backups/BackupsList.svelte";
24
24
  import Dashboard from "../dashboard/Dashboard.svelte";
25
25
  import ScheduleList from "../schedules/ScheduleList.svelte";
@@ -37,7 +37,7 @@
37
37
  let route = $state("dashboard");
38
38
  </script>
39
39
 
40
- <AppShell>
40
+ <AppShell class="h-full">
41
41
  <AppShellHeader>
42
42
  <div class="flex items-center justify-between w-full px-4 py-2">
43
43
  <Heading>
@@ -118,12 +118,12 @@
118
118
 
119
119
  <div class="p-4 flex flex-col gap-2 max-w-6xl m-auto">
120
120
  {#if route === "dashboard"}
121
- <Dashboard local onNavigate={(target) => (route = target)} />
121
+ <Dashboard local onViewBackups={() => (route = "backups")} />
122
122
  {:else if route === "backups"}
123
123
  <BackupsList local />
124
124
  {:else if route === "config"}
125
125
  {#if !mock}
126
- <BackendsList />
126
+ <GlobalSettings />
127
127
  {/if}
128
128
  {:else if route === "schedules"}
129
129
  {#if !mock}
@@ -34,7 +34,7 @@
34
34
  import { SvelteMap } from "svelte/reactivity";
35
35
  import OnEvents from "../../util/OnEvents.svelte";
36
36
  import RelativeTime from "../../util/RelativeTime.svelte";
37
- import ViewLogModal from "../../backups/dialogs/ViewLogModal.svelte";
37
+ import ViewStatusModal from "../../backups/dialogs/ViewStatusModal.svelte";
38
38
 
39
39
  const LINGER_MS = 3000;
40
40
  const FADE_MS = 600;
@@ -171,7 +171,7 @@
171
171
  };
172
172
 
173
173
  const openLog = (logId: string) => {
174
- modalManager.open(ViewLogModal, { logId });
174
+ modalManager.open(ViewStatusModal, { logId });
175
175
  };
176
176
 
177
177
  const onCancel = (parentId: string) => handleCancelTask(parentId);
@@ -0,0 +1,12 @@
1
+ <script lang="ts">
2
+ import { Field, Switch } from "@immich/ui";
3
+
4
+ let hidden = $state(false);
5
+ </script>
6
+
7
+ <Field
8
+ label="Hide backups reminder"
9
+ description="If you already manage backups outside of Immich, you can hide the backup setup reminder."
10
+ >
11
+ <Switch bind:checked={hidden} />
12
+ </Field>
@@ -0,0 +1,3 @@
1
+ declare const MockImmichHideBackupsReminder: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type MockImmichHideBackupsReminder = ReturnType<typeof MockImmichHideBackupsReminder>;
3
+ export default MockImmichHideBackupsReminder;
@@ -0,0 +1,36 @@
1
+ <script lang="ts">
2
+ import { Stack, Text } from "@immich/ui";
3
+
4
+ const groups = [
5
+ { title: "Friday, 31 July 2026", count: 13 },
6
+ { title: "Thursday, 30 July 2026", count: 8 },
7
+ { title: "Sunday, 26 July 2026", count: 17 },
8
+ ];
9
+
10
+ const aspects = [1.5, 0.75, 1.33, 1, 0.7, 1.78, 1.2, 0.8, 1.4, 1];
11
+ const greys = [88, 82, 91, 78, 85, 93, 80, 87];
12
+
13
+ const seedOf = (group: number, index: number) => group * 7 + index * 3;
14
+ const aspect = (seed: number) => aspects[seed % aspects.length];
15
+ const grey = (seed: number) => greys[seed % greys.length];
16
+ </script>
17
+
18
+ <Stack gap={8} class="p-4">
19
+ {#each groups as group, groupIndex (group.title)}
20
+ <Stack gap={2}>
21
+ <Text size="small" fontWeight="medium" color="muted">{group.title}</Text>
22
+
23
+ <div class="flex flex-wrap gap-1">
24
+ {#each Array.from({ length: group.count }, (_, index) => index) as index (index)}
25
+ {@const seed = seedOf(groupIndex, index)}
26
+ <div
27
+ class="h-36 grow rounded-sm"
28
+ style="background-color: hsl(0 0% {grey(seed)}%); flex-basis: {aspect(
29
+ seed,
30
+ ) * 9}rem"
31
+ ></div>
32
+ {/each}
33
+ </div>
34
+ </Stack>
35
+ {/each}
36
+ </Stack>
@@ -0,0 +1,18 @@
1
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
+ $$bindings?: Bindings;
4
+ } & Exports;
5
+ (internal: unknown, props: {
6
+ $$events?: Events;
7
+ $$slots?: Slots;
8
+ }): Exports & {
9
+ $set?: any;
10
+ $on?: any;
11
+ };
12
+ z_$$bindings?: Bindings;
13
+ }
14
+ declare const MockImmichPhotos: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
+ [evt: string]: CustomEvent<any>;
16
+ }, {}, {}, string>;
17
+ type MockImmichPhotos = InstanceType<typeof MockImmichPhotos>;
18
+ export default MockImmichPhotos;
@@ -0,0 +1,29 @@
1
+ <script lang="ts">
2
+ import { Icon, Logo } from "@immich/ui";
3
+ import { mdiInformationOutline } from "@mdi/js";
4
+ </script>
5
+
6
+ <div class="license-status text-sm">
7
+ <button
8
+ type="button"
9
+ onclick={() => {}}
10
+ class="flex w-full cursor-pointer place-content-center place-items-center justify-between rounded-lg border border-primary/20 p-2 shadow-md dark:border-primary/10 dark:bg-primary/10"
11
+ >
12
+ <div
13
+ class="flex w-full place-content-center place-items-center justify-between"
14
+ >
15
+ <div class="flex place-content-center place-items-center gap-1">
16
+ <Logo variant="icon" size="tiny" />
17
+ <p class="flex font-medium text-primary">Buy Immich</p>
18
+ </div>
19
+
20
+ <div>
21
+ <Icon
22
+ icon={mdiInformationOutline}
23
+ class="flex font-medium text-primary"
24
+ size="18"
25
+ />
26
+ </div>
27
+ </div>
28
+ </button>
29
+ </div>
@@ -0,0 +1,18 @@
1
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
+ $$bindings?: Bindings;
4
+ } & Exports;
5
+ (internal: unknown, props: {
6
+ $$events?: Events;
7
+ $$slots?: Slots;
8
+ }): Exports & {
9
+ $set?: any;
10
+ $on?: any;
11
+ };
12
+ z_$$bindings?: Bindings;
13
+ }
14
+ declare const MockImmichPurchaseInfo: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
+ [evt: string]: CustomEvent<any>;
16
+ }, {}, {}, string>;
17
+ type MockImmichPurchaseInfo = InstanceType<typeof MockImmichPurchaseInfo>;
18
+ export default MockImmichPurchaseInfo;
@@ -0,0 +1,9 @@
1
+ <script lang="ts">
2
+ import { HStack, Text } from "@immich/ui";
3
+ </script>
4
+
5
+ <HStack gap={2}>
6
+ <span class="bg-success size-2 rounded-full"></span>
7
+ <Text size="small" class="flex-1">Server Online</Text>
8
+ <Text size="small" color="muted">v3.0.3</Text>
9
+ </HStack>
@@ -0,0 +1,18 @@
1
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
+ $$bindings?: Bindings;
4
+ } & Exports;
5
+ (internal: unknown, props: {
6
+ $$events?: Events;
7
+ $$slots?: Slots;
8
+ }): Exports & {
9
+ $set?: any;
10
+ $on?: any;
11
+ };
12
+ z_$$bindings?: Bindings;
13
+ }
14
+ declare const MockImmichServerStatus: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
+ [evt: string]: CustomEvent<any>;
16
+ }, {}, {}, string>;
17
+ type MockImmichServerStatus = InstanceType<typeof MockImmichServerStatus>;
18
+ export default MockImmichServerStatus;
@@ -0,0 +1,20 @@
1
+ <script lang="ts">
2
+ import { Stack } from "@immich/ui";
3
+ import type { Snippet } from "svelte";
4
+ import MockImmichServerStatus from "./MockImmichServerStatus.svelte";
5
+ import MockImmichStorageSpace from "./MockImmichStorageSpace.svelte";
6
+
7
+ type Props = {
8
+ children: Snippet;
9
+ };
10
+
11
+ const { children }: Props = $props();
12
+ </script>
13
+
14
+ <Stack gap={4} class="bg-light w-80 shrink-0 rounded-2xl border p-4">
15
+ <MockImmichStorageSpace>
16
+ {@render children()}
17
+ </MockImmichStorageSpace>
18
+
19
+ <MockImmichServerStatus />
20
+ </Stack>
@@ -0,0 +1,7 @@
1
+ import type { Snippet } from "svelte";
2
+ type Props = {
3
+ children: Snippet;
4
+ };
5
+ declare const MockImmichSidebar: import("svelte").Component<Props, {}, "">;
6
+ type MockImmichSidebar = ReturnType<typeof MockImmichSidebar>;
7
+ export default MockImmichSidebar;
@@ -0,0 +1,25 @@
1
+ <script lang="ts">
2
+ import { ProgressBar, Stack, Text } from "@immich/ui";
3
+ import type { Snippet } from "svelte";
4
+
5
+ type Props = {
6
+ children?: Snippet;
7
+ };
8
+
9
+ const { children }: Props = $props();
10
+ </script>
11
+
12
+ <div class="bg-subtle overflow-hidden rounded-xl border">
13
+ <Stack gap={1} class="p-4 border-b">
14
+ <Text fontWeight="medium">Storage space</Text>
15
+ <Text size="small" color="muted">22.1 GiB of 100 GiB used</Text>
16
+ <ProgressBar
17
+ progress={0.221}
18
+ shape="round"
19
+ size="tiny"
20
+ class="mt-2 border-none"
21
+ />
22
+ </Stack>
23
+
24
+ {@render children?.()}
25
+ </div>
@@ -0,0 +1,7 @@
1
+ import type { Snippet } from "svelte";
2
+ type Props = {
3
+ children?: Snippet;
4
+ };
5
+ declare const MockImmichStorageSpace: import("svelte").Component<Props, {}, "">;
6
+ type MockImmichStorageSpace = ReturnType<typeof MockImmichStorageSpace>;
7
+ export default MockImmichStorageSpace;
@@ -0,0 +1,67 @@
1
+ <script lang="ts">
2
+ import { Icon, Text } from "@immich/ui";
3
+ import { mdiChevronDown } from "@mdi/js";
4
+ import type { Snippet } from "svelte";
5
+ import { slide } from "svelte/transition";
6
+
7
+ type Props = {
8
+ title: string;
9
+ subtitle?: string;
10
+ icon?: string;
11
+ isOpen?: boolean;
12
+ children?: Snippet;
13
+ };
14
+
15
+ let {
16
+ title,
17
+ subtitle,
18
+ icon,
19
+ isOpen = $bindable(false),
20
+ children,
21
+ }: Props = $props();
22
+ </script>
23
+
24
+ <div
25
+ class="border-primary/20 mt-4 rounded-2xl border-2 px-6 py-4 transition-all"
26
+ class:border-primary={isOpen}
27
+ class:shadow-md={isOpen}
28
+ >
29
+ <button
30
+ type="button"
31
+ aria-expanded={isOpen}
32
+ onclick={() => (isOpen = !isOpen)}
33
+ class="flex w-full place-items-center justify-between gap-4 text-start"
34
+ >
35
+ <div>
36
+ <div class="flex place-items-center gap-2">
37
+ {#if icon}
38
+ <Icon {icon} class="text-primary" size="1.5rem" aria-hidden="true" />
39
+ {/if}
40
+ <Text fontWeight="medium" color="primary">{title}</Text>
41
+ </div>
42
+
43
+ {#if subtitle}
44
+ <Text size="small" color="muted" class="mt-1">{subtitle}</Text>
45
+ {/if}
46
+ </div>
47
+
48
+ <div
49
+ class="hover:bg-primary/10 flex shrink-0 place-content-center place-items-center rounded-full p-3 transition-all"
50
+ >
51
+ <Icon
52
+ icon={mdiChevronDown}
53
+ size="1.25rem"
54
+ class="transition-transform duration-200 ease-in {isOpen
55
+ ? 'rotate-180'
56
+ : ''}"
57
+ aria-hidden="true"
58
+ />
59
+ </div>
60
+ </button>
61
+
62
+ {#if isOpen}
63
+ <div transition:slide={{ duration: 150 }} class="mb-2 ms-4">
64
+ {@render children?.()}
65
+ </div>
66
+ {/if}
67
+ </div>
@@ -0,0 +1,11 @@
1
+ import type { Snippet } from "svelte";
2
+ type Props = {
3
+ title: string;
4
+ subtitle?: string;
5
+ icon?: string;
6
+ isOpen?: boolean;
7
+ children?: Snippet;
8
+ };
9
+ declare const Accordion: import("svelte").Component<Props, {}, "isOpen">;
10
+ type Accordion = ReturnType<typeof Accordion>;
11
+ export default Accordion;
@@ -0,0 +1,60 @@
1
+ <script lang="ts">
2
+ import {
3
+ Button,
4
+ HStack,
5
+ Input,
6
+ menuManager,
7
+ type ActionItem,
8
+ } from "@immich/ui";
9
+ import { mdiFilterVariant, mdiMagnify, mdiSortVariant } from "@mdi/js";
10
+
11
+ type Menu = {
12
+ label: string;
13
+ items: ActionItem[];
14
+ active?: boolean;
15
+ };
16
+
17
+ type Props = {
18
+ placeholder: string;
19
+ search: string;
20
+ onSearch: (value: string) => void;
21
+ sort?: Menu;
22
+ filters?: Menu;
23
+ };
24
+
25
+ const { placeholder, search, onSearch, sort, filters }: Props = $props();
26
+
27
+ const openMenu = (items: ActionItem[]) => (event: Event) =>
28
+ void menuManager.show({
29
+ target: event.currentTarget as HTMLElement,
30
+ position: "bottom-left",
31
+ items,
32
+ });
33
+ </script>
34
+
35
+ <HStack class="items-center justify-between">
36
+ <Input
37
+ class="max-w-72"
38
+ leadingIcon={mdiMagnify}
39
+ {placeholder}
40
+ value={search}
41
+ oninput={(event) => onSearch(event.currentTarget.value)}
42
+ />
43
+
44
+ <HStack gap={2}>
45
+ {#each [{ menu: sort, icon: mdiSortVariant }, { menu: filters, icon: mdiFilterVariant }] as { menu, icon } (icon)}
46
+ {#if menu}
47
+ <Button
48
+ variant="outline"
49
+ size="small"
50
+ color={menu.active ? "primary" : "secondary"}
51
+ class="whitespace-nowrap"
52
+ leadingIcon={icon}
53
+ onclick={openMenu(menu.items)}
54
+ >
55
+ {menu.label}
56
+ </Button>
57
+ {/if}
58
+ {/each}
59
+ </HStack>
60
+ </HStack>
@@ -0,0 +1,16 @@
1
+ import { type ActionItem } from "@immich/ui";
2
+ type Menu = {
3
+ label: string;
4
+ items: ActionItem[];
5
+ active?: boolean;
6
+ };
7
+ type Props = {
8
+ placeholder: string;
9
+ search: string;
10
+ onSearch: (value: string) => void;
11
+ sort?: Menu;
12
+ filters?: Menu;
13
+ };
14
+ declare const ListToolbar: import("svelte").Component<Props, {}, "">;
15
+ type ListToolbar = ReturnType<typeof ListToolbar>;
16
+ export default ListToolbar;