@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
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import type {
|
|
3
3
|
BackendDto,
|
|
4
4
|
BackendType,
|
|
5
|
+
LocalRepositoryDto,
|
|
5
6
|
RepositoryBackendDto,
|
|
6
7
|
} from "../../fetch-client";
|
|
7
8
|
import { getBackendActions } from "../../services/backend.service";
|
|
@@ -10,11 +11,12 @@
|
|
|
10
11
|
import StackListItem from "../ui/StackListItem.svelte";
|
|
11
12
|
|
|
12
13
|
type Props = {
|
|
14
|
+
repository?: LocalRepositoryDto;
|
|
13
15
|
backend: BackendDto;
|
|
14
16
|
repositoryBackend?: RepositoryBackendDto & { primary?: boolean };
|
|
15
17
|
};
|
|
16
18
|
|
|
17
|
-
const { backend, repositoryBackend }: Props = $props();
|
|
19
|
+
const { repository, backend, repositoryBackend }: Props = $props();
|
|
18
20
|
|
|
19
21
|
const BackendIcons: Record<BackendType, string> = {
|
|
20
22
|
yucca: mdiShieldCheckOutline,
|
|
@@ -28,10 +30,12 @@
|
|
|
28
30
|
s3: "S3 Server",
|
|
29
31
|
};
|
|
30
32
|
|
|
31
|
-
const { LoginAgain } = $derived(
|
|
33
|
+
const { LoginAgain, Reconfigure } = $derived(
|
|
34
|
+
getBackendActions(repository, backend, repositoryBackend),
|
|
35
|
+
);
|
|
32
36
|
</script>
|
|
33
37
|
|
|
34
|
-
<StackListItem actions={[LoginAgain]}>
|
|
38
|
+
<StackListItem actions={[LoginAgain, Reconfigure]}>
|
|
35
39
|
{#snippet icon()}
|
|
36
40
|
<Icon icon={BackendIcons[backend.type]} />
|
|
37
41
|
{/snippet}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { BackendDto, RepositoryBackendDto } from "../../fetch-client";
|
|
1
|
+
import type { BackendDto, LocalRepositoryDto, RepositoryBackendDto } from "../../fetch-client";
|
|
2
2
|
type Props = {
|
|
3
|
+
repository?: LocalRepositoryDto;
|
|
3
4
|
backend: BackendDto;
|
|
4
5
|
repositoryBackend?: RepositoryBackendDto & {
|
|
5
6
|
primary?: boolean;
|
|
@@ -49,12 +49,13 @@
|
|
|
49
49
|
{/snippet}
|
|
50
50
|
|
|
51
51
|
{#each query.data as backend (backend.id)}
|
|
52
|
-
|
|
53
|
-
{backend
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
{@const repositoryBackend = repositoryBackends.find(
|
|
53
|
+
({ id }) => backend.id === id,
|
|
54
|
+
)}
|
|
55
|
+
|
|
56
|
+
{#if typeof repository === typeof repositoryBackend}
|
|
57
|
+
<BackendItem {repository} {backend} {repositoryBackend} />
|
|
58
|
+
{/if}
|
|
58
59
|
{/each}
|
|
59
60
|
</StackList>
|
|
60
61
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import PathPickerField from "
|
|
3
|
-
import { useCreateLocalBackend } from "
|
|
2
|
+
import PathPickerField from "../../ui/PathPickerField.svelte";
|
|
3
|
+
import { useCreateLocalBackend } from "../../../services/backend.service";
|
|
4
4
|
import { FormModal, Stack } from "@immich/ui";
|
|
5
5
|
|
|
6
6
|
type Props = {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
type Props = {
|
|
2
|
+
onClose: () => void;
|
|
3
|
+
onCreate?: (backendId: string) => void;
|
|
4
|
+
};
|
|
5
|
+
declare const CreateLocalBackendModal: import("svelte").Component<Props, {}, "">;
|
|
6
|
+
type CreateLocalBackendModal = ReturnType<typeof CreateLocalBackendModal>;
|
|
7
|
+
export default CreateLocalBackendModal;
|
package/dist/components/backends/{OAuthDeviceFlow.svelte → dialogs/OAuthDeviceFlowModal.svelte}
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type { SocketEvent } from "
|
|
3
|
-
import type { BackendDto } from "
|
|
4
|
-
import { useBackendEventHandler } from "
|
|
2
|
+
import type { SocketEvent } from "../../../events";
|
|
3
|
+
import type { BackendDto } from "../../../fetch-client";
|
|
4
|
+
import { useBackendEventHandler } from "../../../services/backend.service";
|
|
5
5
|
import {
|
|
6
6
|
Button,
|
|
7
7
|
Code,
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
VStack,
|
|
17
17
|
} from "@immich/ui";
|
|
18
18
|
import { mdiContentCopy } from "@mdi/js";
|
|
19
|
-
import OnEvents from "
|
|
19
|
+
import OnEvents from "../../util/OnEvents.svelte";
|
|
20
20
|
|
|
21
21
|
type Props = {
|
|
22
22
|
userCode: string;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
type Props = {
|
|
2
|
+
userCode: string;
|
|
3
|
+
verificationUri: string;
|
|
4
|
+
onCreate?: (backendId: string) => void;
|
|
5
|
+
onClose: () => void;
|
|
6
|
+
};
|
|
7
|
+
declare const OAuthDeviceFlowModal: import("svelte").Component<Props, {}, "">;
|
|
8
|
+
type OAuthDeviceFlowModal = ReturnType<typeof OAuthDeviceFlowModal>;
|
|
9
|
+
export default OAuthDeviceFlowModal;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import StackList from "../../ui/StackList.svelte";
|
|
3
|
+
import StackListItem from "../../ui/StackListItem.svelte";
|
|
4
|
+
import Suspense from "../../util/Suspense.svelte";
|
|
5
|
+
import {
|
|
6
|
+
handleSetupLocalStorage,
|
|
7
|
+
handleYuccaLogin,
|
|
8
|
+
useBackends,
|
|
9
|
+
} from "../../../services/backend.service";
|
|
10
|
+
import {
|
|
11
|
+
Button,
|
|
12
|
+
HStack,
|
|
13
|
+
Icon,
|
|
14
|
+
Modal,
|
|
15
|
+
ModalBody,
|
|
16
|
+
ModalFooter,
|
|
17
|
+
Stack,
|
|
18
|
+
Text,
|
|
19
|
+
} from "@immich/ui";
|
|
20
|
+
import { mdiChevronRight, mdiHarddisk, mdiShieldCheck } from "@mdi/js";
|
|
21
|
+
import type { Snippet } from "svelte";
|
|
22
|
+
|
|
23
|
+
type Props = {
|
|
24
|
+
title?: string;
|
|
25
|
+
leadingContent?: Snippet;
|
|
26
|
+
disabled?: boolean;
|
|
27
|
+
onSelect: (backendId: string) => void;
|
|
28
|
+
onCancel: () => void;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const {
|
|
32
|
+
title,
|
|
33
|
+
leadingContent,
|
|
34
|
+
disabled = false,
|
|
35
|
+
onSelect,
|
|
36
|
+
onCancel,
|
|
37
|
+
}: Props = $props();
|
|
38
|
+
|
|
39
|
+
const backends = useBackends();
|
|
40
|
+
|
|
41
|
+
const onFutoBackups = () => {
|
|
42
|
+
const futoBackend = backends.data!.find(
|
|
43
|
+
(backend) => backend.type === "yucca" && backend.isOnline,
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
if (futoBackend) {
|
|
47
|
+
onSelect(futoBackend.id);
|
|
48
|
+
} else {
|
|
49
|
+
handleYuccaLogin(onSelect);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const onLocalBackups = () => {
|
|
54
|
+
handleSetupLocalStorage(onSelect);
|
|
55
|
+
onCancel();
|
|
56
|
+
};
|
|
57
|
+
</script>
|
|
58
|
+
|
|
59
|
+
<Modal size="small" {title} onClose={onCancel} icon={false}>
|
|
60
|
+
<ModalBody>
|
|
61
|
+
<Stack>
|
|
62
|
+
{@render leadingContent?.()}
|
|
63
|
+
|
|
64
|
+
<Suspense query={backends}>
|
|
65
|
+
<StackList>
|
|
66
|
+
<StackListItem {disabled} onclick={onFutoBackups}>
|
|
67
|
+
{#snippet icon()}
|
|
68
|
+
<Icon icon={mdiShieldCheck} size="36px" />
|
|
69
|
+
{/snippet}
|
|
70
|
+
|
|
71
|
+
<Stack gap={0}>
|
|
72
|
+
<Text class="font-bold">FUTO Backups</Text>
|
|
73
|
+
<Text>Simple, hosted backups.</Text>
|
|
74
|
+
</Stack>
|
|
75
|
+
|
|
76
|
+
{#snippet trailing()}
|
|
77
|
+
<Icon icon={mdiChevronRight} />
|
|
78
|
+
{/snippet}
|
|
79
|
+
</StackListItem>
|
|
80
|
+
<StackListItem {disabled} onclick={onLocalBackups}>
|
|
81
|
+
{#snippet icon()}
|
|
82
|
+
<Icon icon={mdiHarddisk} size="36px" />
|
|
83
|
+
{/snippet}
|
|
84
|
+
|
|
85
|
+
<Stack gap={0}>
|
|
86
|
+
<Text class="font-bold">Local Storage</Text>
|
|
87
|
+
<Text>A folder on this computer.</Text>
|
|
88
|
+
</Stack>
|
|
89
|
+
|
|
90
|
+
{#snippet trailing()}
|
|
91
|
+
<Icon icon={mdiChevronRight} />
|
|
92
|
+
{/snippet}
|
|
93
|
+
</StackListItem>
|
|
94
|
+
|
|
95
|
+
{#each backends.data as backend}
|
|
96
|
+
{#if backend.type === "local"}
|
|
97
|
+
<StackListItem
|
|
98
|
+
{disabled}
|
|
99
|
+
onclick={() => {
|
|
100
|
+
onSelect(backend.id);
|
|
101
|
+
}}
|
|
102
|
+
>
|
|
103
|
+
{#snippet icon()}
|
|
104
|
+
<Icon icon={mdiHarddisk} size="36px" />
|
|
105
|
+
{/snippet}
|
|
106
|
+
|
|
107
|
+
<Stack gap={0}>
|
|
108
|
+
<Text class="font-bold">Existing Local Storage</Text>
|
|
109
|
+
<Text>{backend.description}</Text>
|
|
110
|
+
</Stack>
|
|
111
|
+
|
|
112
|
+
{#snippet trailing()}
|
|
113
|
+
<Icon icon={mdiChevronRight} />
|
|
114
|
+
{/snippet}
|
|
115
|
+
</StackListItem>
|
|
116
|
+
{/if}
|
|
117
|
+
{/each}
|
|
118
|
+
</StackList>
|
|
119
|
+
</Suspense>
|
|
120
|
+
</Stack>
|
|
121
|
+
</ModalBody>
|
|
122
|
+
<ModalFooter>
|
|
123
|
+
<HStack>
|
|
124
|
+
<Button variant="ghost" {disabled} onclick={onCancel}>Cancel</Button>
|
|
125
|
+
</HStack>
|
|
126
|
+
</ModalFooter>
|
|
127
|
+
</Modal>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
type Props = {
|
|
3
|
+
title?: string;
|
|
4
|
+
leadingContent?: Snippet;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
onSelect: (backendId: string) => void;
|
|
7
|
+
onCancel: () => void;
|
|
8
|
+
};
|
|
9
|
+
declare const SelectBackendModal: import("svelte").Component<Props, {}, "">;
|
|
10
|
+
type SelectBackendModal = ReturnType<typeof SelectBackendModal>;
|
|
11
|
+
export default SelectBackendModal;
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import type { LocalRepositoryDto } from "../../fetch-client";
|
|
3
3
|
import { getRepositoryActions } from "../../services/repository.service";
|
|
4
4
|
import { Badge, FormatBytes, Text } from "@immich/ui";
|
|
5
|
-
import RelativeTime from "../util/RelativeTime.svelte";
|
|
6
5
|
import StackListItem from "../ui/StackListItem.svelte";
|
|
6
|
+
import RelativeTime from "../util/RelativeTime.svelte";
|
|
7
7
|
|
|
8
8
|
type Props = {
|
|
9
9
|
repository: LocalRepositoryDto;
|
|
@@ -39,9 +39,15 @@
|
|
|
39
39
|
<Badge size="tiny" color="info">WORM</Badge>
|
|
40
40
|
{/if}
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
<
|
|
44
|
-
|
|
42
|
+
{#if repository.meter}
|
|
43
|
+
<Badge size="tiny" color="secondary">
|
|
44
|
+
<FormatBytes bytes={repository.meter?.sizeBytes} />
|
|
45
|
+
</Badge>
|
|
46
|
+
{:else}
|
|
47
|
+
<Badge size="tiny" color="secondary">
|
|
48
|
+
Estimated <FormatBytes bytes={repository.metrics.sizeBytes} />
|
|
49
|
+
</Badge>
|
|
50
|
+
{/if}
|
|
45
51
|
|
|
46
52
|
{#snippet trailing()}
|
|
47
53
|
{#if repository.metrics.lastBackup && (!repository.metrics.lastSuccessfulBackup || +new Date(repository.metrics.lastBackup) > +new Date(repository.metrics.lastSuccessfulBackup))}
|
|
@@ -50,7 +56,9 @@
|
|
|
50
56
|
</Badge>
|
|
51
57
|
{:else if repository.metrics.lastSuccessfulBackup}
|
|
52
58
|
<Badge size="tiny" color="success">
|
|
53
|
-
Successful <RelativeTime
|
|
59
|
+
Successful <RelativeTime
|
|
60
|
+
time={repository.metrics.lastSuccessfulBackup}
|
|
61
|
+
/>
|
|
54
62
|
</Badge>
|
|
55
63
|
{:else}
|
|
56
64
|
<Badge size="tiny" color="warning">Never backed up</Badge>
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import Suspense from "../../util/Suspense.svelte";
|
|
2
3
|
import { type LocalRepositoryDto } from "../../../fetch-client";
|
|
3
4
|
import {
|
|
4
|
-
|
|
5
|
+
useCheckImportRepository,
|
|
5
6
|
useImportRepository,
|
|
6
7
|
} from "../../../services/repository.service";
|
|
7
|
-
import { FormModal,
|
|
8
|
-
import { onMount } from "svelte";
|
|
8
|
+
import { FormModal, modalManager, Text } from "@immich/ui";
|
|
9
9
|
import ConfigureRepositoryModal from "./ConfigureRepositoryModal.svelte";
|
|
10
10
|
|
|
11
11
|
type Props = {
|
|
@@ -14,16 +14,12 @@
|
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
let { onClose, repository }: Props = $props();
|
|
17
|
-
let readable: boolean | undefined = $state();
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
readable = check.readable;
|
|
26
|
-
});
|
|
18
|
+
// svelte-ignore state_referenced_locally
|
|
19
|
+
const check = useCheckImportRepository(
|
|
20
|
+
repository.id,
|
|
21
|
+
repository.backends!.primary.id,
|
|
22
|
+
);
|
|
27
23
|
|
|
28
24
|
const mutation = useImportRepository();
|
|
29
25
|
|
|
@@ -48,15 +44,15 @@
|
|
|
48
44
|
<FormModal
|
|
49
45
|
title={`Import ${repository.name}`}
|
|
50
46
|
submitText="Import"
|
|
51
|
-
disabled={readable !== true || mutation.isPending}
|
|
47
|
+
disabled={check.data?.readable !== true || mutation.isPending}
|
|
52
48
|
{onSubmit}
|
|
53
49
|
{onClose}
|
|
54
50
|
>
|
|
55
|
-
{
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
51
|
+
<Suspense query={check}>
|
|
52
|
+
{#if check.data?.readable}
|
|
53
|
+
<Text>Repository is readable and accessible!</Text>
|
|
54
|
+
{:else}
|
|
55
|
+
<Text>Can't read repository.</Text>
|
|
56
|
+
{/if}
|
|
57
|
+
</Suspense>
|
|
62
58
|
</FormModal>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import SelectBackendModal from "../../backends/dialogs/SelectBackendModal.svelte";
|
|
3
|
+
import type { LocalRepositoryDto } from "../../../fetch-client";
|
|
4
|
+
import { useReconfigureRepositoryPrimaryBackend } from "../../../services/repository.service";
|
|
5
|
+
import { Text } from "@immich/ui";
|
|
6
|
+
|
|
7
|
+
type Props = {
|
|
8
|
+
repository: LocalRepositoryDto;
|
|
9
|
+
onClose: () => void;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const { repository, onClose }: Props = $props();
|
|
13
|
+
|
|
14
|
+
const reconfigure = useReconfigureRepositoryPrimaryBackend();
|
|
15
|
+
|
|
16
|
+
function onSelectBackend(backendId: string) {
|
|
17
|
+
reconfigure.mutate(
|
|
18
|
+
{ id: repository.id, backendId },
|
|
19
|
+
{ onSuccess: onClose },
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<SelectBackendModal
|
|
25
|
+
title="Select new service"
|
|
26
|
+
disabled={reconfigure.isPending}
|
|
27
|
+
onSelect={onSelectBackend}
|
|
28
|
+
onCancel={onClose}
|
|
29
|
+
>
|
|
30
|
+
{#snippet leadingContent()}
|
|
31
|
+
{#if repository.backends?.secondary.length}
|
|
32
|
+
TODO: UI for promoting a secondary
|
|
33
|
+
{/if}
|
|
34
|
+
|
|
35
|
+
<Text
|
|
36
|
+
>You may reset and reconfigure your backups by selecting one of the
|
|
37
|
+
following.</Text
|
|
38
|
+
>
|
|
39
|
+
{/snippet}
|
|
40
|
+
</SelectBackendModal>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { LocalRepositoryDto } from "../../../fetch-client";
|
|
2
|
+
type Props = {
|
|
3
|
+
repository: LocalRepositoryDto;
|
|
4
|
+
onClose: () => void;
|
|
5
|
+
};
|
|
6
|
+
declare const ReconfigureRepositoryBackendModal: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type ReconfigureRepositoryBackendModal = ReturnType<typeof ReconfigureRepositoryBackendModal>;
|
|
8
|
+
export default ReconfigureRepositoryBackendModal;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import StackList from "../../ui/StackList.svelte";
|
|
3
|
+
import StackListItem from "../../ui/StackListItem.svelte";
|
|
4
|
+
import { useInspectRepositories } from "../../../services/repository.service";
|
|
5
|
+
import {
|
|
6
|
+
Button,
|
|
7
|
+
HStack,
|
|
8
|
+
Icon,
|
|
9
|
+
Modal,
|
|
10
|
+
ModalBody,
|
|
11
|
+
ModalFooter,
|
|
12
|
+
Stack,
|
|
13
|
+
Text,
|
|
14
|
+
} from "@immich/ui";
|
|
15
|
+
import { mdiChevronRight } from "@mdi/js";
|
|
16
|
+
import type { Snippet } from "svelte";
|
|
17
|
+
|
|
18
|
+
type Props = {
|
|
19
|
+
title?: string;
|
|
20
|
+
backendId?: string;
|
|
21
|
+
|
|
22
|
+
leadingContent?: Snippet;
|
|
23
|
+
footerContent?: Snippet;
|
|
24
|
+
|
|
25
|
+
onSelect: (repositoryId: string) => void;
|
|
26
|
+
onCancel: () => void;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const {
|
|
30
|
+
title = "Select Backup",
|
|
31
|
+
backendId,
|
|
32
|
+
leadingContent,
|
|
33
|
+
footerContent,
|
|
34
|
+
onSelect,
|
|
35
|
+
onCancel,
|
|
36
|
+
}: Props = $props();
|
|
37
|
+
|
|
38
|
+
// svelte-ignore state_referenced_locally
|
|
39
|
+
const query = useInspectRepositories(backendId);
|
|
40
|
+
|
|
41
|
+
const sortedRepositories = $derived(
|
|
42
|
+
query.data?.toSorted((a, b) => {
|
|
43
|
+
const validA = a.snapshots !== undefined;
|
|
44
|
+
const validB = b.snapshots !== undefined;
|
|
45
|
+
return validA !== validB
|
|
46
|
+
? Number(validB) - Number(validA)
|
|
47
|
+
: Number(b.name.includes("Immich")) - Number(a.name.includes("Immich"));
|
|
48
|
+
}),
|
|
49
|
+
);
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<Modal {title} size="small" onClose={onCancel}>
|
|
53
|
+
<ModalBody>
|
|
54
|
+
<Stack>
|
|
55
|
+
{@render leadingContent?.()}
|
|
56
|
+
|
|
57
|
+
<StackList {query}>
|
|
58
|
+
{#each sortedRepositories ?? [] as repository (repository.id)}
|
|
59
|
+
{@const accessible = repository.snapshots !== undefined}
|
|
60
|
+
|
|
61
|
+
<StackListItem
|
|
62
|
+
onclick={accessible ? () => onSelect(repository.id) : undefined}
|
|
63
|
+
>
|
|
64
|
+
<Stack gap={0} class="grow min-w-0">
|
|
65
|
+
<Text>{repository.name}</Text>
|
|
66
|
+
<Text size="small" color={accessible ? "secondary" : "danger"}>
|
|
67
|
+
{#if !accessible}
|
|
68
|
+
Can't access, is your recovery key correct?
|
|
69
|
+
{:else if repository.snapshots.length}
|
|
70
|
+
Last backup: {new Date(
|
|
71
|
+
repository.snapshots[0].time,
|
|
72
|
+
).toLocaleDateString()}
|
|
73
|
+
{:else}
|
|
74
|
+
No backups yet
|
|
75
|
+
{/if}
|
|
76
|
+
</Text>
|
|
77
|
+
</Stack>
|
|
78
|
+
|
|
79
|
+
{#snippet trailing()}
|
|
80
|
+
{#if accessible}
|
|
81
|
+
<Icon icon={mdiChevronRight} />
|
|
82
|
+
{/if}
|
|
83
|
+
{/snippet}
|
|
84
|
+
</StackListItem>
|
|
85
|
+
{/each}
|
|
86
|
+
|
|
87
|
+
{#if (sortedRepositories ?? []).length === 0}
|
|
88
|
+
<StackListItem>
|
|
89
|
+
<Text color="muted">No backups found.</Text>
|
|
90
|
+
</StackListItem>
|
|
91
|
+
{/if}
|
|
92
|
+
</StackList>
|
|
93
|
+
</Stack>
|
|
94
|
+
</ModalBody>
|
|
95
|
+
<ModalFooter>
|
|
96
|
+
<HStack>
|
|
97
|
+
<Button variant="ghost" onclick={onCancel}>Cancel</Button>
|
|
98
|
+
{@render footerContent?.()}
|
|
99
|
+
</HStack>
|
|
100
|
+
</ModalFooter>
|
|
101
|
+
</Modal>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
type Props = {
|
|
3
|
+
title?: string;
|
|
4
|
+
backendId?: string;
|
|
5
|
+
leadingContent?: Snippet;
|
|
6
|
+
footerContent?: Snippet;
|
|
7
|
+
onSelect: (repositoryId: string) => void;
|
|
8
|
+
onCancel: () => void;
|
|
9
|
+
};
|
|
10
|
+
declare const SelectRepositoryModal: import("svelte").Component<Props, {}, "">;
|
|
11
|
+
type SelectRepositoryModal = ReturnType<typeof SelectRepositoryModal>;
|
|
12
|
+
export default SelectRepositoryModal;
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
|
|
23
23
|
{#if snapshot.summary}
|
|
24
24
|
<Text color="secondary">
|
|
25
|
-
· {snapshot.summary.totalFiles.toLocaleString()}
|
|
25
|
+
· {snapshot.summary.totalFiles.toLocaleString()}
|
|
26
|
+
{snapshot.summary.totalFiles > 1 ? "files" : "file"} ·
|
|
26
27
|
<FormatBytes bytes={snapshot.summary.totalBytes} />
|
|
27
28
|
</Text>
|
|
28
29
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { LocalRepositoryDto } from "../../fetch-client";
|
|
3
|
-
import { Card, CardBody, getByteUnitString } from "@immich/ui";
|
|
3
|
+
import { Badge, Card, CardBody, getByteUnitString } from "@immich/ui";
|
|
4
4
|
import VisualisationGauge from "../ui/VisualisationGauge.svelte";
|
|
5
5
|
|
|
6
6
|
type Props = {
|
|
@@ -10,10 +10,11 @@
|
|
|
10
10
|
const { repositories }: Props = $props();
|
|
11
11
|
|
|
12
12
|
const totalStored = $derived(
|
|
13
|
-
repositories.reduce(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
repositories.reduce((sum, repo) => sum + (repo.meter?.sizeBytes ?? 0), 0),
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
const estimatedStored = $derived(
|
|
17
|
+
repositories.reduce((sum, repo) => sum + (repo.metrics?.sizeBytes ?? 0), 0),
|
|
17
18
|
);
|
|
18
19
|
</script>
|
|
19
20
|
|
|
@@ -22,6 +23,11 @@
|
|
|
22
23
|
<VisualisationGauge
|
|
23
24
|
title="Total Stored"
|
|
24
25
|
content={getByteUnitString(totalStored)}
|
|
25
|
-
|
|
26
|
+
>
|
|
27
|
+
{#snippet subtitle()}
|
|
28
|
+
<Badge size="tiny">Estimated {getByteUnitString(estimatedStored)}</Badge
|
|
29
|
+
>
|
|
30
|
+
{/snippet}
|
|
31
|
+
</VisualisationGauge>
|
|
26
32
|
</CardBody>
|
|
27
33
|
</Card>
|
|
@@ -203,7 +203,8 @@
|
|
|
203
203
|
<FormModal
|
|
204
204
|
title="Backup settings"
|
|
205
205
|
size="large"
|
|
206
|
-
disabled={
|
|
206
|
+
disabled={(scheduleMode === "custom" &&
|
|
207
|
+
!validateCron(effectiveCron).isError()) ||
|
|
207
208
|
name.length === 0 ||
|
|
208
209
|
mutation.isPending}
|
|
209
210
|
onClose={() => {
|
|
@@ -52,7 +52,11 @@
|
|
|
52
52
|
<Stack gap={0}>
|
|
53
53
|
<Heading size="tiny">Your library</Heading>
|
|
54
54
|
<Text>
|
|
55
|
-
|
|
55
|
+
{#if repository.meter}
|
|
56
|
+
Estimated <FormatBytes bytes={repository.meter.sizeBytes} />
|
|
57
|
+
{:else}
|
|
58
|
+
<FormatBytes bytes={repository.metrics.sizeBytes} />
|
|
59
|
+
{/if} ·
|
|
56
60
|
<span class="lowercase"
|
|
57
61
|
>{cronstrue.toString(schedule.cron, {
|
|
58
62
|
verbose: true,
|