@futo-org/backups-orchestrator-ui 0.1.72 → 0.4.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 (46) hide show
  1. package/dist/components/backends/BackendItem.svelte +7 -3
  2. package/dist/components/backends/BackendItem.svelte.d.ts +2 -1
  3. package/dist/components/backends/BackendsList.svelte +7 -6
  4. package/dist/components/backends/{CreateLocalBackend.svelte → dialogs/CreateLocalBackendModal.svelte} +2 -2
  5. package/dist/components/backends/dialogs/CreateLocalBackendModal.svelte.d.ts +7 -0
  6. package/dist/components/backends/{OAuthDeviceFlow.svelte → dialogs/OAuthDeviceFlowModal.svelte} +4 -4
  7. package/dist/components/backends/dialogs/OAuthDeviceFlowModal.svelte.d.ts +9 -0
  8. package/dist/components/backends/dialogs/SelectBackendModal.svelte +127 -0
  9. package/dist/components/backends/dialogs/SelectBackendModal.svelte.d.ts +11 -0
  10. package/dist/components/backups/BackupItem.svelte +13 -5
  11. package/dist/components/backups/dialogs/ImportRepositoryModal.svelte +16 -20
  12. package/dist/components/backups/dialogs/ReconfigureRepositoryBackendModal.svelte +40 -0
  13. package/dist/components/backups/dialogs/ReconfigureRepositoryBackendModal.svelte.d.ts +8 -0
  14. package/dist/components/backups/dialogs/RestoreSnapshotModal.svelte +1 -2
  15. package/dist/components/backups/dialogs/SelectRepositoryModal.svelte +101 -0
  16. package/dist/components/backups/dialogs/SelectRepositoryModal.svelte.d.ts +12 -0
  17. package/dist/components/backups/snapshots-list/RepositorySnapshotsListItem.svelte +2 -1
  18. package/dist/components/dashboard/DashboardTotalStored.svelte +12 -6
  19. package/dist/components/integrations/immich/ImmichConfigureBackup.svelte +2 -1
  20. package/dist/components/integrations/immich/ImmichManageBackupOverview.svelte +5 -1
  21. package/dist/components/integrations/immich/ImmichOnboardingRestoreFlow.svelte +44 -34
  22. package/dist/components/integrations/immich/ImmichOnboardingSetupFlow.svelte +72 -54
  23. package/dist/components/onboarding/OnboardingBootstrapError.svelte +49 -0
  24. package/dist/components/onboarding/OnboardingBootstrapError.svelte.d.ts +7 -0
  25. package/dist/components/onboarding/OnboardingGate.svelte +20 -17
  26. package/dist/components/onboarding/SampleOnboarding.svelte +22 -6
  27. package/dist/components/onboarding/restore-point-flow/RestorePointFlow.svelte +13 -68
  28. package/dist/components/onboarding/stages/OnboardingStageTelemetry.svelte +60 -0
  29. package/dist/components/onboarding/stages/OnboardingStageTelemetry.svelte.d.ts +7 -0
  30. package/dist/components/schedules/dialogs/ConfigureScheduleModal.svelte +5 -2
  31. package/dist/components/schedules/dialogs/CreateScheduleModal.svelte +5 -2
  32. package/dist/components/ui/StackListItem.svelte +4 -1
  33. package/dist/components/ui/StackListItem.svelte.d.ts +1 -0
  34. package/dist/fetch-client.d.ts +22 -1
  35. package/dist/fetch-client.js +23 -2
  36. package/dist/services/backend.service.d.ts +8 -2
  37. package/dist/services/backend.service.js +33 -5
  38. package/dist/services/integrations.service.js +0 -4
  39. package/dist/services/onboarding.service.d.ts +2 -0
  40. package/dist/services/onboarding.service.js +9 -1
  41. package/dist/services/repository.service.d.ts +11 -4
  42. package/dist/services/repository.service.js +16 -24
  43. package/dist/services/schedule.service.js +0 -2
  44. package/package.json +6 -4
  45. package/dist/components/backends/CreateLocalBackend.svelte.d.ts +0 -7
  46. package/dist/components/backends/OAuthDeviceFlow.svelte.d.ts +0 -9
@@ -1,6 +1,8 @@
1
1
  <script lang="ts">
2
+ import OnboardingBootstrapError from "../../onboarding/OnboardingBootstrapError.svelte";
2
3
  import OnboardingStageBackupServices from "../../onboarding/stages/OnboardingStageBackupServices.svelte";
3
4
  import OnboardingStageKeyImport from "../../onboarding/stages/OnboardingStageKeyImport.svelte";
5
+ import OnboardingStageTelemetry from "../../onboarding/stages/OnboardingStageTelemetry.svelte";
4
6
  import RestorePointFlow from "../../onboarding/restore-point-flow/RestorePointFlow.svelte";
5
7
  import type { OnboardingStatusResponseDto } from "../../../fetch-client";
6
8
  import {
@@ -19,6 +21,7 @@
19
21
 
20
22
  let status: OnboardingStatusResponseDto | undefined = $state();
21
23
  let stage:
24
+ | "telemetry"
22
25
  | "key-import"
23
26
  | "backup-service"
24
27
  | "restore-point"
@@ -28,48 +31,55 @@
28
31
  handleOnboardingStatus().then((data) => {
29
32
  status = data;
30
33
 
31
- if (data.hasOnboardedKey) {
32
- if (data.hasBackend) {
33
- stage = "restore-point";
34
- } else {
35
- stage = "backup-service";
36
- }
34
+ if (data.status !== "ready") {
35
+ return;
36
+ }
37
+
38
+ if (data.hasTelemetry === "none") {
39
+ stage = "telemetry";
40
+ } else if (data.hasOnboardedKey) {
41
+ stage = data.hasBackend ? "restore-point" : "backup-service";
37
42
  }
38
43
  });
39
44
  });
40
45
 
41
46
  const onKeyImported = async () => {
42
47
  await handleConfirmRecoveryKey();
43
-
44
- if (status?.hasBackend) {
45
- stage = "restore-point";
46
- } else {
47
- stage = "backup-service";
48
- }
48
+ stage = status?.hasBackend ? "restore-point" : "backup-service";
49
49
  };
50
50
  </script>
51
51
 
52
- {#if typeof status === "object"}
53
- {#if stage === "key-import"}
54
- <OnboardingStageKeyImport onImported={onKeyImported} onCancel={onExit} />
55
- {:else if stage === "backup-service"}
56
- <OnboardingStageBackupServices
57
- onNext={() => (stage = "restore-point")}
58
- onCancel={onExit}
59
- restore
60
- />
61
- {:else if stage === "key-reimport"}
62
- <OnboardingStageKeyImport
63
- onImported={() => (stage = "restore-point")}
64
- onCancel={() => (stage = "restore-point")}
65
- />
66
- {:else if stage === "restore-point"}
67
- <RestorePointFlow
68
- onImportKey={() => (stage = "key-reimport")}
69
- onCancel={onExit}
70
- {onFinish}
71
- />
72
- {/if}
73
- {:else}
52
+ {#if status === undefined || status.status === "not-ready"}
74
53
  <LoadingSpinner />
54
+ {:else if status.status === "error"}
55
+ <OnboardingBootstrapError error={status.error} onQuit={onExit} />
56
+ {:else if stage === "telemetry"}
57
+ <OnboardingStageTelemetry
58
+ onContinue={() =>
59
+ (stage = status?.hasOnboardedKey
60
+ ? status.hasBackend
61
+ ? "restore-point"
62
+ : "backup-service"
63
+ : "key-import")}
64
+ onCancel={onExit}
65
+ />
66
+ {:else if stage === "key-import"}
67
+ <OnboardingStageKeyImport onImported={onKeyImported} onCancel={onExit} />
68
+ {:else if stage === "backup-service"}
69
+ <OnboardingStageBackupServices
70
+ onNext={() => (stage = "restore-point")}
71
+ onCancel={onExit}
72
+ restore
73
+ />
74
+ {:else if stage === "key-reimport"}
75
+ <OnboardingStageKeyImport
76
+ onImported={() => (stage = "restore-point")}
77
+ onCancel={() => (stage = "restore-point")}
78
+ />
79
+ {:else if stage === "restore-point"}
80
+ <RestorePointFlow
81
+ onImportKey={() => (stage = "key-reimport")}
82
+ onCancel={onExit}
83
+ {onFinish}
84
+ />
75
85
  {/if}
@@ -1,9 +1,11 @@
1
1
  <script lang="ts">
2
+ import OnboardingBootstrapError from "../../onboarding/OnboardingBootstrapError.svelte";
2
3
  import OnboardingStageBackupServices from "../../onboarding/stages/OnboardingStageBackupServices.svelte";
3
4
  import OnboardingStageKeyConfirm from "../../onboarding/stages/OnboardingStageKeyConfirm.svelte";
4
5
  import OnboardingStageKeyImport from "../../onboarding/stages/OnboardingStageKeyImport.svelte";
5
6
  import OnboardingStageKeyIntro from "../../onboarding/stages/OnboardingStageKeyIntro.svelte";
6
7
  import OnboardingStageSaveKey from "../../onboarding/stages/OnboardingStageKeySave.svelte";
8
+ import OnboardingStageTelemetry from "../../onboarding/stages/OnboardingStageTelemetry.svelte";
7
9
  import OnboardingStageWelcome from "../../onboarding/stages/OnboardingStageWelcome.svelte";
8
10
  import type { OnboardingStatusResponseDto } from "../../../fetch-client";
9
11
  import {
@@ -28,6 +30,7 @@
28
30
  let status: OnboardingStatusResponseDto | undefined = $state();
29
31
  let stage:
30
32
  | `welcome`
33
+ | `telemetry`
31
34
  | `key-${"intro" | "save" | "confirm" | "import"}`
32
35
  | `backup-${"service" | "confirm" | "create"}`
33
36
  | `finished` = $state("welcome");
@@ -36,12 +39,17 @@
36
39
  handleOnboardingStatus().then(async (data) => {
37
40
  status = data;
38
41
 
42
+ if (data.status !== "ready") {
43
+ return;
44
+ }
45
+
39
46
  if (data.hasOnboardedKey) {
40
- if (data.hasBackup) {
41
- stage = "finished";
42
- } else {
43
- stage = "backup-service";
44
- }
47
+ stage =
48
+ data.hasTelemetry === "none"
49
+ ? "telemetry"
50
+ : data.hasBackup
51
+ ? "finished"
52
+ : "backup-service";
45
53
  } else {
46
54
  const { recoveryKey } = await handleCurrentRecoveryKey();
47
55
  code = recoveryKey;
@@ -60,54 +68,64 @@
60
68
  };
61
69
  </script>
62
70
 
63
- {#if typeof status === "object"}
64
- {#if stage === "finished"}
65
- {@render children()}
66
- {:else if stage === "welcome"}
67
- <OnboardingStageWelcome
68
- onNext={() => (stage = "key-intro")}
69
- onImportKey={() => (stage = "key-import")}
70
- onCancel={onExit}
71
- />
72
- {:else if stage === "key-import"}
73
- <OnboardingStageKeyImport
74
- onStart={() => (stage = "welcome")}
75
- onImported={() => (stage = "key-confirm")}
76
- onCancel={onExit}
77
- />
78
- {:else if stage === "key-intro"}
79
- <OnboardingStageKeyIntro
80
- onNext={() => (stage = "key-save")}
81
- onCancel={onExit}
82
- />
83
- {:else if stage === "key-save"}
84
- <OnboardingStageSaveKey
85
- {code}
86
- onNext={() => (stage = "key-confirm")}
87
- onCancel={onExit}
88
- />
89
- {:else if stage === "key-confirm"}
90
- <OnboardingStageKeyConfirm
91
- {code}
92
- onBack={() => (stage = "key-save")}
93
- onCancel={onExit}
94
- {onConfirmKey}
95
- />
96
- {:else if stage === "backup-service"}
97
- <OnboardingStageBackupServices onNext={onSelectBackend} onCancel={onExit} />
98
- {:else if stage === "backup-confirm"}
99
- <ImmichConfirmDefaultBackup
100
- onCustomize={() => (stage = "backup-create")}
101
- onConfirm={() => (stage = "finished")}
102
- onCancel={onExit}
103
- />
104
- {:else if stage === "backup-create"}
105
- <ImmichConfigureBackup
106
- onFinish={() => (stage = "finished")}
107
- onCancel={onExit}
108
- {backendId}
109
- />
110
- {/if}
111
- {:else}
71
+ {#if status === undefined || status.status === "not-ready"}
112
72
  <LoadingSpinner />
73
+ {:else if status.status === "error"}
74
+ <OnboardingBootstrapError error={status.error} onQuit={onExit} />
75
+ {:else if stage === "finished"}
76
+ {@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
+ />
83
+ {: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
+ />
93
+ {:else if stage === "key-import"}
94
+ <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}
130
+ />
113
131
  {/if}
@@ -0,0 +1,49 @@
1
+ <script lang="ts">
2
+ import {
3
+ Button,
4
+ HStack,
5
+ Modal,
6
+ ModalBody,
7
+ ModalFooter,
8
+ Stack,
9
+ Text,
10
+ } from "@immich/ui";
11
+ import { useReportError } from "../../services/onboarding.service";
12
+
13
+ type Props = {
14
+ error?: string;
15
+ onQuit: () => void;
16
+ };
17
+
18
+ const { error, onQuit }: Props = $props();
19
+
20
+ const mutation = useReportError();
21
+
22
+ const onReportAndQuit = () =>
23
+ mutation.mutate(undefined, { onSuccess: onQuit });
24
+ </script>
25
+
26
+ <Modal size="small" title="Something went wrong" onClose={onQuit} icon={false}>
27
+ <ModalBody>
28
+ <Stack>
29
+ <Text>We ran into an error setting up backups...</Text>
30
+ {#if error}
31
+ <Text size="small" color="danger" class="font-mono whitespace-pre-wrap"
32
+ >{error}</Text
33
+ >
34
+ {/if}
35
+ </Stack>
36
+ </ModalBody>
37
+ <ModalFooter>
38
+ <HStack>
39
+ <Button
40
+ color="danger"
41
+ onclick={onReportAndQuit}
42
+ loading={mutation.isPending}>Report error and quit</Button
43
+ >
44
+ <Button variant="ghost" onclick={onQuit} disabled={mutation.isPending}
45
+ >Go back</Button
46
+ >
47
+ </HStack>
48
+ </ModalFooter>
49
+ </Modal>
@@ -0,0 +1,7 @@
1
+ type Props = {
2
+ error?: string;
3
+ onQuit: () => void;
4
+ };
5
+ declare const OnboardingBootstrapError: import("svelte").Component<Props, {}, "">;
6
+ type OnboardingBootstrapError = ReturnType<typeof OnboardingBootstrapError>;
7
+ export default OnboardingBootstrapError;
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { LoadingSpinner } from "@immich/ui";
3
3
  import { onMount, type Snippet } from "svelte";
4
+ import OnboardingBootstrapError from "./OnboardingBootstrapError.svelte";
4
5
  import SampleOnboarding from "./SampleOnboarding.svelte";
5
6
  import type { OnboardingStatusResponseDto } from "../../fetch-client";
6
7
  import { handleOnboardingStatus } from "../../services/onboarding.service";
@@ -13,14 +14,16 @@
13
14
 
14
15
  const { onExit, onFinish, children }: Props = $props();
15
16
 
16
- let status: OnboardingStatusResponseDto | undefined = $state();
17
+ let onboarding: OnboardingStatusResponseDto | undefined = $state();
17
18
 
18
19
  onMount(() => {
19
- handleOnboardingStatus().then((data) => (status = data));
20
+ handleOnboardingStatus().then((data) => (onboarding = data));
20
21
  });
21
22
 
22
23
  function onSkip() {
23
- status = {
24
+ onboarding = {
25
+ status: "ready",
26
+ hasTelemetry: "full",
24
27
  hasBackend: true,
25
28
  hasOnboardedKey: true,
26
29
  hasBackup: true,
@@ -30,19 +33,19 @@
30
33
  }
31
34
  </script>
32
35
 
33
- {#if typeof status === "object"}
34
- {#if !(status.hasBackend && status.hasOnboardedKey && (status.hasSkippedExtraConfig || (status.hasBackup && status.hasSchedule)))}
35
- <SampleOnboarding
36
- {status}
37
- onFinish={() => (onFinish ? onFinish() : onSkip())}
38
- onCancel={() => {
39
- onSkip();
40
- onExit();
41
- }}
42
- />
43
- {:else}
44
- {@render children()}
45
- {/if}
46
- {:else}
36
+ {#if onboarding === undefined || onboarding.status === "not-ready"}
47
37
  <LoadingSpinner />
38
+ {:else if onboarding.status === "error"}
39
+ <OnboardingBootstrapError error={onboarding.error} onQuit={onExit} />
40
+ {:else if onboarding.hasTelemetry === "none" || !(onboarding.hasBackend && onboarding.hasOnboardedKey && (onboarding.hasSkippedExtraConfig || (onboarding.hasBackup && onboarding.hasSchedule)))}
41
+ <SampleOnboarding
42
+ status={onboarding}
43
+ onFinish={() => (onFinish ? onFinish() : onSkip())}
44
+ onCancel={() => {
45
+ onSkip();
46
+ onExit();
47
+ }}
48
+ />
49
+ {:else}
50
+ {@render children()}
48
51
  {/if}
@@ -13,6 +13,7 @@
13
13
  import ImportKey from "./stages/OnboardingStageKeyImport.svelte";
14
14
  import KeyIntro from "./stages/OnboardingStageKeyIntro.svelte";
15
15
  import SaveKey from "./stages/OnboardingStageKeySave.svelte";
16
+ import Telemetry from "./stages/OnboardingStageTelemetry.svelte";
16
17
  import Welcome from "./stages/OnboardingStageWelcome.svelte";
17
18
 
18
19
  type Props = {
@@ -28,17 +29,20 @@
28
29
  // svelte-ignore state_referenced_locally
29
30
  let stage:
30
31
  | "welcome"
32
+ | "telemetry"
31
33
  | `key-${"intro" | "save" | "confirm" | "import"}`
32
34
  | "backup-service"
33
35
  | "backup-create"
34
36
  | "schedule-create" = $state(
35
37
  !status.hasOnboardedKey
36
38
  ? "welcome"
37
- : !status.hasBackend
38
- ? "backup-service"
39
- : !status.hasBackup
40
- ? "backup-create"
41
- : "schedule-create",
39
+ : status.hasTelemetry === "none"
40
+ ? "telemetry"
41
+ : !status.hasBackend
42
+ ? "backup-service"
43
+ : !status.hasBackup
44
+ ? "backup-create"
45
+ : "schedule-create",
42
46
  );
43
47
 
44
48
  onMount(() => {
@@ -65,10 +69,22 @@
65
69
 
66
70
  {#if stage === "welcome"}
67
71
  <Welcome
68
- onNext={() => (stage = "key-intro")}
72
+ onNext={() => (stage = status.hasTelemetry === "none" ? "telemetry" : "key-intro")}
69
73
  onImportKey={() => (stage = "key-import")}
70
74
  {onCancel}
71
75
  />
76
+ {:else if stage === "telemetry"}
77
+ <Telemetry
78
+ onContinue={() =>
79
+ (stage = !status.hasOnboardedKey
80
+ ? "key-intro"
81
+ : !status.hasBackend
82
+ ? "backup-service"
83
+ : !status.hasBackup
84
+ ? "backup-create"
85
+ : "schedule-create")}
86
+ {onCancel}
87
+ />
72
88
  {:else if stage === "key-intro"}
73
89
  <KeyIntro onNext={() => (stage = "key-save")} {onCancel} />
74
90
  {:else if stage === "key-save"}
@@ -1,15 +1,7 @@
1
1
  <script lang="ts">
2
- import StackList from "../../ui/StackList.svelte";
2
+ import SelectRepositoryModal from "../../backups/dialogs/SelectRepositoryModal.svelte";
3
3
  import { useInspectRepositories } from "../../../services/repository.service";
4
- import {
5
- Button,
6
- HStack,
7
- Modal,
8
- ModalBody,
9
- ModalFooter,
10
- Stack,
11
- Text,
12
- } from "@immich/ui";
4
+ import { Button } from "@immich/ui";
13
5
  import RestorePointFlow2SelectSnapshot from "./RestorePointFlow2SelectSnapshot.svelte";
14
6
 
15
7
  type Props = {
@@ -22,22 +14,10 @@
22
14
 
23
15
  const query = useInspectRepositories();
24
16
 
25
- const sortedRepositories = $derived(
26
- query.data?.toSorted((a, b) => {
27
- const validA = a.snapshots !== undefined;
28
- const validB = b.snapshots !== undefined;
29
- return validA !== validB
30
- ? Number(validB) - Number(validA)
31
- : Number(b.name.includes("Immich")) - Number(a.name.includes("Immich"));
32
- }),
33
- );
34
-
35
17
  let selectedRepository: string | undefined = $state();
36
18
 
37
19
  const repository = $derived(
38
- query.data?.find((repository) => {
39
- return repository.id === selectedRepository;
40
- }),
20
+ query.data?.find((repository) => repository.id === selectedRepository),
41
21
  );
42
22
  </script>
43
23
 
@@ -48,49 +28,14 @@
48
28
  {onFinish}
49
29
  />
50
30
  {:else}
51
- <Modal title="Select Backup" size="small" onClose={onCancel}>
52
- <ModalBody>
53
- <StackList {query}>
54
- {#snippet children()}
55
- {#each sortedRepositories ?? [] as repo (repo.id)}
56
- {@const accessible = repo.snapshots !== undefined}
57
- <HStack gap={2} class="px-4 py-3">
58
- <Stack gap={0} class="grow min-w-0">
59
- <Text>{repo.name}</Text>
60
- <Text size="small" color={accessible ? "secondary" : "danger"}>
61
- {#if !accessible}
62
- Unable to access repository
63
- {:else if repo.snapshots.length}
64
- Last backup: {new Date(
65
- repo.snapshots[0].time,
66
- ).toLocaleDateString()}
67
- {:else}
68
- No backups yet
69
- {/if}
70
- </Text>
71
- </Stack>
72
- {#if accessible}
73
- <Button onclick={() => (selectedRepository = repo.id)}>
74
- Select
75
- </Button>
76
- {/if}
77
- </HStack>
78
- {/each}
79
- {#if (sortedRepositories ?? []).length === 0}
80
- <Text class="text-center py-6" color="muted">
81
- No repositories found.
82
- </Text>
83
- {/if}
84
- {/snippet}
85
- </StackList>
86
- </ModalBody>
87
- <ModalFooter>
88
- <HStack>
89
- <Button variant="ghost" onclick={onCancel}>Cancel</Button>
90
- <Button variant="ghost" onclick={onImportKey}
91
- >Import a different key</Button
92
- >
93
- </HStack>
94
- </ModalFooter>
95
- </Modal>
31
+ <SelectRepositoryModal
32
+ onSelect={(repositoryId) => (selectedRepository = repositoryId)}
33
+ {onCancel}
34
+ >
35
+ {#snippet footerContent()}
36
+ <Button variant="ghost" onclick={onImportKey}>
37
+ Import a different key
38
+ </Button>
39
+ {/snippet}
40
+ </SelectRepositoryModal>
96
41
  {/if}
@@ -0,0 +1,60 @@
1
+ <script lang="ts">
2
+ import {
3
+ Button,
4
+ HStack,
5
+ Icon,
6
+ Modal,
7
+ ModalBody,
8
+ ModalFooter,
9
+ Stack,
10
+ Text,
11
+ } from "@immich/ui";
12
+ import { useEnableTelemetry } from "../../../services/onboarding.service";
13
+ import { mdiChartBox, mdiEyeOff } from "@mdi/js";
14
+
15
+ type Props = {
16
+ onContinue: () => void;
17
+ onCancel: () => void;
18
+ };
19
+
20
+ const { onContinue, onCancel }: Props = $props();
21
+
22
+ const mutation = useEnableTelemetry();
23
+
24
+ const onConfirm = () => mutation.mutate(undefined, { onSuccess: onContinue });
25
+ </script>
26
+
27
+ <Modal
28
+ size="small"
29
+ title="Telemetry required for closed beta"
30
+ onClose={onCancel}
31
+ >
32
+ <ModalBody>
33
+ <Stack>
34
+ <HStack>
35
+ <Icon icon={mdiChartBox} class="shrink-0 place-self-start mt-1" />
36
+ <Text>
37
+ We collect usage and diagnostic data to understand how FUTO Backups is
38
+ used and to find problems.</Text
39
+ >
40
+ </HStack>
41
+ <HStack>
42
+ <Icon icon={mdiEyeOff} class="shrink-0 place-self-start mt-1" />
43
+ <Text>
44
+ Your photos, files, and recovery key are never collected and never
45
+ leave your device unencrypted.</Text
46
+ >
47
+ </HStack>
48
+ </Stack>
49
+ </ModalBody>
50
+ <ModalFooter>
51
+ <HStack>
52
+ <Button onclick={onConfirm} loading={mutation.isPending}>Continue</Button>
53
+ <Button
54
+ variant="ghost"
55
+ onclick={onCancel}
56
+ disabled={mutation.isPending}>Cancel</Button
57
+ >
58
+ </HStack>
59
+ </ModalFooter>
60
+ </Modal>
@@ -0,0 +1,7 @@
1
+ type Props = {
2
+ onContinue: () => void;
3
+ onCancel: () => void;
4
+ };
5
+ declare const OnboardingStageTelemetry: import("svelte").Component<Props, {}, "">;
6
+ type OnboardingStageTelemetry = ReturnType<typeof OnboardingStageTelemetry>;
7
+ export default OnboardingStageTelemetry;
@@ -1,8 +1,8 @@
1
1
  <script lang="ts">
2
2
  import type { ScheduleDto } from "../../../fetch-client";
3
+ import { useUpdateSchedule } from "../../../services/schedule.service";
3
4
  import { Field, FormModal, Input, Stack } from "@immich/ui";
4
5
  import validate from "cron-validate";
5
- import { useUpdateSchedule } from "../../../services/schedule.service";
6
6
  import RepositoryPicker from "../RepositoryPicker.svelte";
7
7
 
8
8
  type Props = {
@@ -31,7 +31,10 @@
31
31
  <FormModal
32
32
  title={`Edit ${schedule.name}`}
33
33
  size="large"
34
- disabled={name.length === 0 || validate(cron).isError() || mutation.isPending}
34
+ disabled={name.length === 0 ||
35
+ validate(cron).isError() ||
36
+ repositories.length === 0 ||
37
+ mutation.isPending}
35
38
  {onSubmit}
36
39
  {onClose}
37
40
  >
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
+ import { useCreateSchedule } from "../../../services/schedule.service";
2
3
  import { Field, FormModal, Input, Stack } from "@immich/ui";
3
4
  import validate from "cron-validate";
4
- import { useCreateSchedule } from "../../../services/schedule.service";
5
5
  import RepositoryPicker from "../RepositoryPicker.svelte";
6
6
 
7
7
  type Props = {
@@ -26,7 +26,10 @@
26
26
  <FormModal
27
27
  title="Create A New Schedule"
28
28
  size="large"
29
- disabled={name.length === 0 || validate(cron).isError() || mutation.isPending}
29
+ disabled={name.length === 0 ||
30
+ validate(cron).isError() ||
31
+ repositories.length === 0 ||
32
+ mutation.isPending}
30
33
  {onSubmit}
31
34
  {onClose}
32
35
  >
@@ -10,6 +10,7 @@
10
10
  children: Snippet;
11
11
  trailing?: Snippet;
12
12
  actions?: ActionItem[];
13
+ disabled?: boolean;
13
14
  onclick?: () => void;
14
15
  };
15
16
 
@@ -19,6 +20,7 @@
19
20
  children,
20
21
  trailing,
21
22
  actions = [],
23
+ disabled = false,
22
24
  onclick,
23
25
  }: Props = $props();
24
26
  </script>
@@ -54,8 +56,9 @@
54
56
  {#if onclick}
55
57
  <button
56
58
  {onclick}
59
+ {disabled}
57
60
  type="button"
58
- class="block w-full text-left rounded-lg hover:bg-subtle focus-visible:outline-2 focus-visible:outline-primary-500 cursor-pointer"
61
+ class="block w-full text-left rounded-lg hover:bg-subtle focus-visible:outline-2 focus-visible:outline-primary-500 cursor-pointer disabled:cursor-not-allowed disabled:opacity-60"
59
62
  >
60
63
  {@render body()}
61
64
  </button>