@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,55 @@
1
+ <script lang="ts">
2
+ import { Icon, Text } from "@immich/ui";
3
+ import { mdiCloudUpload, mdiCloudUploadOutline } from "@mdi/js";
4
+
5
+ type Props = {
6
+ configured: boolean;
7
+ active?: boolean;
8
+ href?: string;
9
+ onclick?: () => void;
10
+ class?: string;
11
+ };
12
+
13
+ const {
14
+ configured,
15
+ active = false,
16
+ href,
17
+ onclick,
18
+ class: className,
19
+ }: Props = $props();
20
+
21
+ const shell = $derived(
22
+ configured
23
+ ? `hover:bg-subtle hover:text-primary flex w-full cursor-pointer place-items-center gap-4 rounded-e-full ps-5 py-3 text-start ${active ? "bg-primary/10 text-primary" : ""}`
24
+ : "border-primary/40 from-white via-blue-100 to-purple-100 dark:from-transparent dark:via-blue-500/10 dark:to-purple-500/15 flex w-full cursor-pointer place-items-center gap-4 rounded-xl border bg-gradient-to-r px-4 py-2.5 text-start",
25
+ );
26
+ </script>
27
+
28
+ {#snippet body()}
29
+ <Icon
30
+ icon={configured ? mdiCloudUploadOutline : mdiCloudUpload}
31
+ size="1.375em"
32
+ class="{configured ? '' : 'text-primary'} shrink-0"
33
+ />
34
+
35
+ <Text
36
+ size="small"
37
+ fontWeight="medium"
38
+ color={configured && !active ? undefined : "primary"}
39
+ class="flex-1 truncate"
40
+ >
41
+ {configured ? "Backups" : "Set up Backups"}
42
+ </Text>
43
+ {/snippet}
44
+
45
+ <div class="{configured ? '' : 'mx-4 mb-2'} {className ?? ''}">
46
+ {#if href}
47
+ <a {href} {onclick} class={shell}>
48
+ {@render body()}
49
+ </a>
50
+ {:else}
51
+ <button type="button" {onclick} class={shell}>
52
+ {@render body()}
53
+ </button>
54
+ {/if}
55
+ </div>
@@ -0,0 +1,10 @@
1
+ type Props = {
2
+ configured: boolean;
3
+ active?: boolean;
4
+ href?: string;
5
+ onclick?: () => void;
6
+ class?: string;
7
+ };
8
+ declare const ImmichBackupsNavButton: import("svelte").Component<Props, {}, "">;
9
+ type ImmichBackupsNavButton = ReturnType<typeof ImmichBackupsNavButton>;
10
+ export default ImmichBackupsNavButton;
@@ -1,14 +1,26 @@
1
1
  <script lang="ts">
2
+ import UpsellPage from "../../onboarding/upsell/UpsellPage.svelte";
2
3
  import ImmichManageBackup from "./ImmichManageBackup.svelte";
3
4
  import ImmichOnboardingSetupFlow from "./ImmichOnboardingSetupFlow.svelte";
4
5
 
6
+ type Question = {
7
+ title: string;
8
+ answer: string;
9
+ };
10
+
5
11
  type Props = {
6
- onExit: () => void;
12
+ price: string;
13
+ includedStorage: string;
14
+ questions: Question[];
7
15
  };
8
16
 
9
- const { onExit }: Props = $props();
17
+ const { price, includedStorage, questions }: Props = $props();
10
18
  </script>
11
19
 
12
- <ImmichOnboardingSetupFlow {onExit}>
20
+ <ImmichOnboardingSetupFlow>
21
+ {#snippet fallback(onStart)}
22
+ <UpsellPage {price} {includedStorage} {questions} onGetStarted={onStart} />
23
+ {/snippet}
24
+
13
25
  <ImmichManageBackup />
14
26
  </ImmichOnboardingSetupFlow>
@@ -1,5 +1,11 @@
1
+ type Question = {
2
+ title: string;
3
+ answer: string;
4
+ };
1
5
  type Props = {
2
- onExit: () => void;
6
+ price: string;
7
+ includedStorage: string;
8
+ questions: Question[];
3
9
  };
4
10
  declare const ImmichBackupsPage: import("svelte").Component<Props, {}, "">;
5
11
  type ImmichBackupsPage = ReturnType<typeof ImmichBackupsPage>;
@@ -0,0 +1,66 @@
1
+ <script lang="ts">
2
+ import OnEvents from "../../util/OnEvents.svelte";
3
+ import {
4
+ useIntegrationEventHandler,
5
+ useIntegrations,
6
+ } from "../../../services/integrations.service";
7
+ import {
8
+ useRepositories,
9
+ useRepositoryEventHandler,
10
+ } from "../../../services/repository.service";
11
+ import ImmichBackupsCard from "./ImmichBackupsCard.svelte";
12
+
13
+ type Props = {
14
+ href?: string;
15
+ onclick?: () => void;
16
+ class?: string;
17
+ };
18
+
19
+ const { href = "/backups", onclick, class: className }: Props = $props();
20
+
21
+ const integrations = useIntegrations();
22
+ const repositories = useRepositories();
23
+
24
+ const { onIntegrationUpdate } = useIntegrationEventHandler();
25
+ const { onRepositoryCreate, onRepositoryUpdate, onRepositoryDelete } =
26
+ useRepositoryEventHandler();
27
+
28
+ const repository = $derived.by(() => {
29
+ const integration = integrations.data?.immichIntegration;
30
+
31
+ return integration
32
+ ? repositories.data?.find((entry) => entry.id === integration.id)
33
+ : undefined;
34
+ });
35
+
36
+ const lastBackup = $derived(repository?.metrics.lastBackup ?? undefined);
37
+
38
+ const failed = $derived(
39
+ Boolean(
40
+ lastBackup && lastBackup !== repository?.metrics.lastSuccessfulBackup,
41
+ ),
42
+ );
43
+
44
+ const sizeBytes = $derived(
45
+ repository?.meter?.sizeBytes ?? repository?.metrics.sizeBytes,
46
+ );
47
+ </script>
48
+
49
+ <OnEvents
50
+ {onIntegrationUpdate}
51
+ {onRepositoryCreate}
52
+ {onRepositoryUpdate}
53
+ {onRepositoryDelete}
54
+ />
55
+
56
+ {#if !integrations.isLoading && !repositories.isLoading}
57
+ <ImmichBackupsCard
58
+ configured={Boolean(repository)}
59
+ {lastBackup}
60
+ {failed}
61
+ {sizeBytes}
62
+ {href}
63
+ {onclick}
64
+ class={className}
65
+ />
66
+ {/if}
@@ -0,0 +1,8 @@
1
+ type Props = {
2
+ href?: string;
3
+ onclick?: () => void;
4
+ class?: string;
5
+ };
6
+ declare const ImmichBackupsSidebarItem: import("svelte").Component<Props, {}, "">;
7
+ type ImmichBackupsSidebarItem = ReturnType<typeof ImmichBackupsSidebarItem>;
8
+ export default ImmichBackupsSidebarItem;
@@ -17,6 +17,9 @@
17
17
  useScheduleEventHandler,
18
18
  useSchedules,
19
19
  } from "../../../services/schedule.service";
20
+ import RepositoryRunHistoryPage from "../../backups/run-history/RepositoryRunHistoryPage.svelte";
21
+ import RepositorySnapshotsPage from "../../backups/snapshots-list/RepositorySnapshotsPage.svelte";
22
+ import ImmichBackupSettings from "./ImmichBackupSettings.svelte";
20
23
  import { Container, Stack } from "@immich/ui";
21
24
  import ImmichManageBackupOverview from "./ImmichManageBackupOverview.svelte";
22
25
 
@@ -48,8 +51,12 @@
48
51
  : undefined,
49
52
  );
50
53
 
51
- const { ViewRecoveryKey, Configure, BackUpNow } = $derived(
52
- getBackupPageActions(repository?.id),
54
+ let view = $state<"overview" | "attempts" | "snapshots" | "settings">(
55
+ "overview",
56
+ );
57
+
58
+ const { ViewRecoveryKey, Configure } = $derived(
59
+ getBackupPageActions(repository?.id, () => (view = "settings")),
53
60
  );
54
61
  </script>
55
62
 
@@ -63,15 +70,47 @@
63
70
  {onIntegrationUpdate}
64
71
  />
65
72
 
66
- <PageLayout title="Backups" actions={[ViewRecoveryKey, Configure, BackUpNow]}>
67
- <Container size="large" center>
68
- {#if repository && schedule}
69
- <Stack class="mt-4" gap={8}>
70
- <ImmichManageBackupOverview {repository} {schedule} />
71
- <BackendsList {repository} />
72
- <RepositoryRunHistory {repository} />
73
- <RepositorySnapshotsList {repository} immich />
74
- </Stack>
75
- {/if}
76
- </Container>
77
- </PageLayout>
73
+ {#if view === "attempts" && repository}
74
+ <PageLayout title="Backup attempts" onBack={() => (view = "overview")}>
75
+ <Container size="medium" center>
76
+ <div class="mt-4">
77
+ <RepositoryRunHistoryPage {repository} />
78
+ </div>
79
+ </Container>
80
+ </PageLayout>
81
+ {:else if view === "settings"}
82
+ <PageLayout title="Backup settings" onBack={() => (view = "overview")}>
83
+ <div class="mt-4">
84
+ <ImmichBackupSettings />
85
+ </div>
86
+ </PageLayout>
87
+ {:else if view === "snapshots" && repository}
88
+ <PageLayout title="Snapshots" onBack={() => (view = "overview")}>
89
+ <Container size="medium" center>
90
+ <div class="mt-4">
91
+ <RepositorySnapshotsPage {repository} immich />
92
+ </div>
93
+ </Container>
94
+ </PageLayout>
95
+ {:else}
96
+ <PageLayout title="Backups" actions={[ViewRecoveryKey, Configure]}>
97
+ <Container size="medium" center>
98
+ {#if repository && schedule}
99
+ <Stack class="mt-4" gap={6}>
100
+ <ImmichManageBackupOverview {repository} {schedule} />
101
+ <BackendsList {repository} />
102
+ <RepositoryRunHistory
103
+ {repository}
104
+ onViewAll={() => (view = "attempts")}
105
+ />
106
+ <RepositorySnapshotsList
107
+ {repository}
108
+ immich
109
+ limit={5}
110
+ onViewAll={() => (view = "snapshots")}
111
+ />
112
+ </Stack>
113
+ {/if}
114
+ </Container>
115
+ </PageLayout>
116
+ {/if}
@@ -3,26 +3,14 @@
3
3
  import StackListItem from "../../ui/StackListItem.svelte";
4
4
  import RelativeTime from "../../util/RelativeTime.svelte";
5
5
  import type { LocalRepositoryDto, ScheduleDto } from "../../../fetch-client";
6
- import {
7
- handlePauseSchedule,
8
- handleResumeSchedule,
9
- } from "../../../services/schedule.service";
10
- import {
11
- FormatBytes,
12
- Heading,
13
- HStack,
14
- Icon,
15
- IconButton,
16
- Stack,
17
- Text,
18
- } from "@immich/ui";
6
+ import { handleCreateBackup } from "../../../services/repository.service";
7
+ import { Button, FormatBytes, Icon } from "@immich/ui";
19
8
  import {
20
9
  mdiAlert,
21
10
  mdiArchiveOutline,
22
11
  mdiCheck,
12
+ mdiCloudUploadOutline,
23
13
  mdiInformation,
24
- mdiPauseCircleOutline,
25
- mdiPlayCircleOutline,
26
14
  } from "@mdi/js";
27
15
  import cronstrue from "cronstrue";
28
16
 
@@ -33,72 +21,58 @@
33
21
 
34
22
  const { repository, schedule }: Props = $props();
35
23
 
36
- function togglePause() {
37
- if (schedule!.paused) {
38
- handleResumeSchedule(schedule!.id, "Immich");
39
- } else {
40
- handlePauseSchedule(schedule!.id, "Immich");
24
+ const status = $derived.by(() => {
25
+ if (!repository.metrics.lastBackup) {
26
+ return { color: "warning", icon: mdiInformation } as const;
41
27
  }
42
- }
28
+
29
+ return repository.metrics.lastBackup !==
30
+ repository.metrics.lastSuccessfulBackup
31
+ ? ({ color: "danger", icon: mdiAlert } as const)
32
+ : ({ color: "success", icon: mdiCheck } as const);
33
+ });
34
+
43
35
  </script>
44
36
 
45
37
  <StackList>
46
- <StackListItem>
38
+ <StackListItem title="Your library" footerColor={status.color}>
47
39
  {#snippet icon()}
48
- <Icon icon={mdiArchiveOutline} size="32px" />
40
+ <Icon icon={mdiArchiveOutline} />
49
41
  {/snippet}
50
42
 
51
- <HStack gap={4}>
52
- <Stack gap={0}>
53
- <Heading size="tiny">Your library</Heading>
54
- <Text>
55
- {#if repository.meter}
56
- <FormatBytes bytes={repository.meter.sizeBytes} />
57
- {:else}
58
- Estimated <FormatBytes bytes={repository.metrics.sizeBytes} />
59
- {/if} &middot;
60
- <span class="lowercase"
61
- >{cronstrue.toString(schedule.cron, {
62
- verbose: true,
63
- })}</span
64
- >
65
- </Text>
66
- </Stack>
67
- </HStack>
43
+ {#if repository.meter}
44
+ <FormatBytes bytes={repository.meter.sizeBytes} />
45
+ {:else}
46
+ Estimated <FormatBytes bytes={repository.metrics.sizeBytes} />
47
+ {/if} &middot;
48
+ <span class="lowercase">
49
+ {cronstrue.toString(schedule.cron, { verbose: true })}
50
+ </span>
68
51
 
69
52
  {#snippet trailing()}
70
- <IconButton
53
+ <Button
71
54
  variant="ghost"
72
- onclick={togglePause}
73
- aria-label={schedule.paused ? "Resume backups" : "Pause backups"}
74
- icon={schedule.paused ? mdiPlayCircleOutline : mdiPauseCircleOutline}
75
- />
55
+ size="small"
56
+ class="whitespace-nowrap"
57
+ leadingIcon={mdiCloudUploadOutline}
58
+ onclick={() => void handleCreateBackup(repository.id)}
59
+ >
60
+ Back up now
61
+ </Button>
76
62
  {/snippet}
77
- </StackListItem>
78
63
 
79
- {#if repository.metrics.lastBackup}
80
- {#if repository.metrics.lastBackup !== repository.metrics.lastSuccessfulBackup}
81
- <StackListItem class="bg-danger-100">
82
- <HStack>
83
- <Icon icon={mdiAlert} /> Last backup failed <RelativeTime
84
- time={repository.metrics.lastBackup}
85
- />
86
- </HStack>
87
- </StackListItem>
88
- {:else}
89
- <StackListItem class="bg-success-50">
90
- <HStack>
91
- <Icon icon={mdiCheck} /> Last backup successful <RelativeTime
92
- time={repository.metrics.lastBackup}
93
- />
94
- </HStack>
95
- </StackListItem>
96
- {/if}
97
- {:else}
98
- <StackListItem class="bg-warning-50">
99
- <HStack>
100
- <Icon icon={mdiInformation} /> Backup is yet to run.
101
- </HStack>
102
- </StackListItem>
103
- {/if}
64
+ {#snippet footer()}
65
+ <Icon icon={status.icon} />
66
+
67
+ {#if !repository.metrics.lastBackup}
68
+ Backup is yet to run.
69
+ {:else if status.color === "danger"}
70
+ Last backup failed <RelativeTime time={repository.metrics.lastBackup} />
71
+ {:else}
72
+ Last backup successful <RelativeTime
73
+ time={repository.metrics.lastBackup}
74
+ />
75
+ {/if}
76
+ {/snippet}
77
+ </StackListItem>
104
78
  </StackList>
@@ -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 OnboardingStageWelcome from "../../onboarding/stages/OnboardingStageWelcome.svelte";
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
- import ImmichConfigureBackup from "./ImmichConfigureBackup.svelte";
19
- import ImmichConfirmDefaultBackup from "./ImmichConfirmDefaultBackup.svelte";
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
- onExit: () => void;
38
+ fallback: Snippet<[() => void]>;
23
39
  children: Snippet;
24
40
  };
25
41
 
26
- const { onExit, children }: Props = $props();
42
+ const { fallback, children }: Props = $props();
27
43
 
28
44
  let code = $state("");
29
- let backendId = $state("");
45
+ let storageLocation = $state("FUTO Backups");
30
46
  let status: OnboardingStatusResponseDto | undefined = $state();
31
- let stage:
32
- | `welcome`
33
- | `telemetry`
34
- | `key-${"intro" | "save" | "confirm" | "import"}`
35
- | `backup-${"service" | "confirm" | "create"}`
36
- | `finished` = $state("welcome");
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
- stage =
48
- data.hasTelemetry === "none"
49
- ? "telemetry"
50
- : data.hasBackup
51
- ? "finished"
52
- : "backup-service";
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 onConfirmKey = async () => {
61
- await handleConfirmRecoveryKey();
62
- stage = "backup-service";
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 onSelectBackend = (id: string) => {
66
- backendId = id;
67
- stage = "backup-confirm";
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
- <LoadingSpinner />
73
- {:else if status.status === "error"}
74
- <OnboardingBootstrapError error={status.error} onQuit={onExit} />
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 if stage === "welcome"}
78
- <OnboardingStageWelcome
79
- onNext={() => (stage = status?.hasTelemetry === "none" ? "telemetry" : "key-intro")}
80
- onImportKey={() => (stage = "key-import")}
81
- onCancel={onExit}
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
- onContinue={() =>
86
- (stage = status?.hasOnboardedKey
87
- ? status.hasBackup
88
- ? "finished"
89
- : "backup-service"
90
- : "key-intro")}
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 = "welcome")}
96
- onImported={() => (stage = "key-confirm")}
97
- onCancel={onExit}
98
- />
99
- {:else if stage === "key-intro"}
100
- <OnboardingStageKeyIntro
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}