@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.
- package/dist/components/backends/BackendItem.svelte +7 -3
- package/dist/components/backends/BackendItem.svelte.d.ts +2 -1
- package/dist/components/backends/BackendsList.svelte +7 -6
- package/dist/components/backends/{CreateLocalBackend.svelte → dialogs/CreateLocalBackendModal.svelte} +2 -2
- package/dist/components/backends/dialogs/CreateLocalBackendModal.svelte.d.ts +7 -0
- package/dist/components/backends/{OAuthDeviceFlow.svelte → dialogs/OAuthDeviceFlowModal.svelte} +4 -4
- package/dist/components/backends/dialogs/OAuthDeviceFlowModal.svelte.d.ts +9 -0
- package/dist/components/backends/dialogs/SelectBackendModal.svelte +127 -0
- package/dist/components/backends/dialogs/SelectBackendModal.svelte.d.ts +11 -0
- package/dist/components/backups/BackupItem.svelte +13 -5
- package/dist/components/backups/dialogs/ImportRepositoryModal.svelte +16 -20
- package/dist/components/backups/dialogs/ReconfigureRepositoryBackendModal.svelte +40 -0
- package/dist/components/backups/dialogs/ReconfigureRepositoryBackendModal.svelte.d.ts +8 -0
- package/dist/components/backups/dialogs/RestoreSnapshotModal.svelte +1 -2
- package/dist/components/backups/dialogs/SelectRepositoryModal.svelte +101 -0
- package/dist/components/backups/dialogs/SelectRepositoryModal.svelte.d.ts +12 -0
- package/dist/components/backups/snapshots-list/RepositorySnapshotsListItem.svelte +2 -1
- package/dist/components/dashboard/DashboardTotalStored.svelte +12 -6
- package/dist/components/integrations/immich/ImmichConfigureBackup.svelte +2 -1
- package/dist/components/integrations/immich/ImmichManageBackupOverview.svelte +5 -1
- package/dist/components/integrations/immich/ImmichOnboardingRestoreFlow.svelte +44 -34
- package/dist/components/integrations/immich/ImmichOnboardingSetupFlow.svelte +72 -54
- package/dist/components/onboarding/OnboardingBootstrapError.svelte +49 -0
- package/dist/components/onboarding/OnboardingBootstrapError.svelte.d.ts +7 -0
- package/dist/components/onboarding/OnboardingGate.svelte +20 -17
- package/dist/components/onboarding/SampleOnboarding.svelte +22 -6
- package/dist/components/onboarding/restore-point-flow/RestorePointFlow.svelte +13 -68
- package/dist/components/onboarding/stages/OnboardingStageTelemetry.svelte +60 -0
- package/dist/components/onboarding/stages/OnboardingStageTelemetry.svelte.d.ts +7 -0
- package/dist/components/schedules/dialogs/ConfigureScheduleModal.svelte +5 -2
- package/dist/components/schedules/dialogs/CreateScheduleModal.svelte +5 -2
- package/dist/components/ui/StackListItem.svelte +4 -1
- package/dist/components/ui/StackListItem.svelte.d.ts +1 -0
- package/dist/fetch-client.d.ts +22 -1
- package/dist/fetch-client.js +23 -2
- package/dist/services/backend.service.d.ts +8 -2
- package/dist/services/backend.service.js +33 -5
- package/dist/services/integrations.service.js +0 -4
- package/dist/services/onboarding.service.d.ts +2 -0
- package/dist/services/onboarding.service.js +9 -1
- package/dist/services/repository.service.d.ts +11 -4
- package/dist/services/repository.service.js +16 -24
- package/dist/services/schedule.service.js +0 -2
- package/package.json +6 -4
- package/dist/components/backends/CreateLocalBackend.svelte.d.ts +0 -7
- 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.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
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>
|
|
@@ -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
|
|
17
|
+
let onboarding: OnboardingStatusResponseDto | undefined = $state();
|
|
17
18
|
|
|
18
19
|
onMount(() => {
|
|
19
|
-
handleOnboardingStatus().then((data) => (
|
|
20
|
+
handleOnboardingStatus().then((data) => (onboarding = data));
|
|
20
21
|
});
|
|
21
22
|
|
|
22
23
|
function onSkip() {
|
|
23
|
-
|
|
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
|
|
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
|
-
:
|
|
38
|
-
? "
|
|
39
|
-
: !status.
|
|
40
|
-
? "backup-
|
|
41
|
-
:
|
|
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
|
|
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
|
-
<
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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 ||
|
|
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 ||
|
|
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>
|