@futo-org/backups-orchestrator-ui 0.1.71

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 (192) hide show
  1. package/LICENSE +41 -0
  2. package/dist/components/backends/BackendItem.svelte +64 -0
  3. package/dist/components/backends/BackendItem.svelte.d.ts +10 -0
  4. package/dist/components/backends/BackendsList.svelte +73 -0
  5. package/dist/components/backends/BackendsList.svelte.d.ts +7 -0
  6. package/dist/components/backends/CreateLocalBackend.svelte +46 -0
  7. package/dist/components/backends/CreateLocalBackend.svelte.d.ts +7 -0
  8. package/dist/components/backends/OAuthDeviceFlow.svelte +78 -0
  9. package/dist/components/backends/OAuthDeviceFlow.svelte.d.ts +9 -0
  10. package/dist/components/backups/BackupItem.svelte +59 -0
  11. package/dist/components/backups/BackupItem.svelte.d.ts +7 -0
  12. package/dist/components/backups/BackupsList.svelte +82 -0
  13. package/dist/components/backups/BackupsList.svelte.d.ts +8 -0
  14. package/dist/components/backups/dialogs/ConfigureRepositoryModal.svelte +102 -0
  15. package/dist/components/backups/dialogs/ConfigureRepositoryModal.svelte.d.ts +8 -0
  16. package/dist/components/backups/dialogs/CreateRepositoryModal.svelte +59 -0
  17. package/dist/components/backups/dialogs/CreateRepositoryModal.svelte.d.ts +6 -0
  18. package/dist/components/backups/dialogs/ImportRepositoryModal.svelte +62 -0
  19. package/dist/components/backups/dialogs/ImportRepositoryModal.svelte.d.ts +8 -0
  20. package/dist/components/backups/dialogs/RestoreSnapshotModal.svelte +109 -0
  21. package/dist/components/backups/dialogs/RestoreSnapshotModal.svelte.d.ts +8 -0
  22. package/dist/components/backups/dialogs/ViewLogModal.svelte +208 -0
  23. package/dist/components/backups/dialogs/ViewLogModal.svelte.d.ts +7 -0
  24. package/dist/components/backups/metrics-history/MetricsHistoryModal.svelte +166 -0
  25. package/dist/components/backups/metrics-history/MetricsHistoryModal.svelte.d.ts +8 -0
  26. package/dist/components/backups/run-history/RepositoryRunHistory.svelte +39 -0
  27. package/dist/components/backups/run-history/RepositoryRunHistory.svelte.d.ts +7 -0
  28. package/dist/components/backups/run-history/RepositoryRunHistoryItem.svelte +45 -0
  29. package/dist/components/backups/run-history/RepositoryRunHistoryItem.svelte.d.ts +7 -0
  30. package/dist/components/backups/run-history/RunHistoryModal.svelte +18 -0
  31. package/dist/components/backups/run-history/RunHistoryModal.svelte.d.ts +8 -0
  32. package/dist/components/backups/snapshots-list/RepositorySnapshotsList.svelte +53 -0
  33. package/dist/components/backups/snapshots-list/RepositorySnapshotsList.svelte.d.ts +7 -0
  34. package/dist/components/backups/snapshots-list/RepositorySnapshotsListItem.svelte +41 -0
  35. package/dist/components/backups/snapshots-list/RepositorySnapshotsListItem.svelte.d.ts +8 -0
  36. package/dist/components/backups/snapshots-list/SnapshotsListModal.svelte +18 -0
  37. package/dist/components/backups/snapshots-list/SnapshotsListModal.svelte.d.ts +8 -0
  38. package/dist/components/dashboard/Dashboard.svelte +52 -0
  39. package/dist/components/dashboard/Dashboard.svelte.d.ts +9 -0
  40. package/dist/components/dashboard/DashboardAvgBackupTime.svelte +34 -0
  41. package/dist/components/dashboard/DashboardAvgBackupTime.svelte.d.ts +7 -0
  42. package/dist/components/dashboard/DashboardBackupHealth.svelte +91 -0
  43. package/dist/components/dashboard/DashboardBackupHealth.svelte.d.ts +9 -0
  44. package/dist/components/dashboard/DashboardCurrentUsage.svelte +10 -0
  45. package/dist/components/dashboard/DashboardCurrentUsage.svelte.d.ts +18 -0
  46. package/dist/components/dashboard/DashboardDailyBackupTime.svelte +31 -0
  47. package/dist/components/dashboard/DashboardDailyBackupTime.svelte.d.ts +7 -0
  48. package/dist/components/dashboard/DashboardInstall.svelte +15 -0
  49. package/dist/components/dashboard/DashboardInstall.svelte.d.ts +18 -0
  50. package/dist/components/dashboard/DashboardRecentBackups.svelte +104 -0
  51. package/dist/components/dashboard/DashboardRecentBackups.svelte.d.ts +8 -0
  52. package/dist/components/dashboard/DashboardTotalStored.svelte +27 -0
  53. package/dist/components/dashboard/DashboardTotalStored.svelte.d.ts +7 -0
  54. package/dist/components/integrations/immich/ImmichBackupsPage.svelte +14 -0
  55. package/dist/components/integrations/immich/ImmichBackupsPage.svelte.d.ts +6 -0
  56. package/dist/components/integrations/immich/ImmichConfigureBackup.svelte +402 -0
  57. package/dist/components/integrations/immich/ImmichConfigureBackup.svelte.d.ts +9 -0
  58. package/dist/components/integrations/immich/ImmichConfirmDefaultBackup.svelte +80 -0
  59. package/dist/components/integrations/immich/ImmichConfirmDefaultBackup.svelte.d.ts +8 -0
  60. package/dist/components/integrations/immich/ImmichManageBackup.svelte +77 -0
  61. package/dist/components/integrations/immich/ImmichManageBackup.svelte.d.ts +3 -0
  62. package/dist/components/integrations/immich/ImmichManageBackupOverview.svelte +100 -0
  63. package/dist/components/integrations/immich/ImmichManageBackupOverview.svelte.d.ts +8 -0
  64. package/dist/components/integrations/immich/ImmichOnboardingRestoreFlow.svelte +75 -0
  65. package/dist/components/integrations/immich/ImmichOnboardingRestoreFlow.svelte.d.ts +7 -0
  66. package/dist/components/integrations/immich/ImmichOnboardingSetupFlow.svelte +113 -0
  67. package/dist/components/integrations/immich/ImmichOnboardingSetupFlow.svelte.d.ts +8 -0
  68. package/dist/components/onboarding/OnboardingGate.svelte +48 -0
  69. package/dist/components/onboarding/OnboardingGate.svelte.d.ts +9 -0
  70. package/dist/components/onboarding/RecoveryKeyDisplay.svelte +103 -0
  71. package/dist/components/onboarding/RecoveryKeyDisplay.svelte.d.ts +6 -0
  72. package/dist/components/onboarding/SampleOnboarding.svelte +98 -0
  73. package/dist/components/onboarding/SampleOnboarding.svelte.d.ts +9 -0
  74. package/dist/components/onboarding/dialogs/BackupsRecoveryKeyModal.svelte +43 -0
  75. package/dist/components/onboarding/dialogs/BackupsRecoveryKeyModal.svelte.d.ts +6 -0
  76. package/dist/components/onboarding/restore-point-flow/RestorePointFlow.svelte +96 -0
  77. package/dist/components/onboarding/restore-point-flow/RestorePointFlow.svelte.d.ts +8 -0
  78. package/dist/components/onboarding/restore-point-flow/RestorePointFlow2SelectSnapshot.svelte +83 -0
  79. package/dist/components/onboarding/restore-point-flow/RestorePointFlow2SelectSnapshot.svelte.d.ts +9 -0
  80. package/dist/components/onboarding/restore-point-flow/RestorePointFlow3ConfirmRestore.svelte +118 -0
  81. package/dist/components/onboarding/restore-point-flow/RestorePointFlow3ConfirmRestore.svelte.d.ts +10 -0
  82. package/dist/components/onboarding/restore-point-flow/RestorePointFlow4Restore.svelte +59 -0
  83. package/dist/components/onboarding/restore-point-flow/RestorePointFlow4Restore.svelte.d.ts +8 -0
  84. package/dist/components/onboarding/stages/OnboardingStageBackupServices.svelte +82 -0
  85. package/dist/components/onboarding/stages/OnboardingStageBackupServices.svelte.d.ts +8 -0
  86. package/dist/components/onboarding/stages/OnboardingStageKeyConfirm.svelte +56 -0
  87. package/dist/components/onboarding/stages/OnboardingStageKeyConfirm.svelte.d.ts +9 -0
  88. package/dist/components/onboarding/stages/OnboardingStageKeyImport.svelte +57 -0
  89. package/dist/components/onboarding/stages/OnboardingStageKeyImport.svelte.d.ts +8 -0
  90. package/dist/components/onboarding/stages/OnboardingStageKeyIntro.svelte +50 -0
  91. package/dist/components/onboarding/stages/OnboardingStageKeyIntro.svelte.d.ts +7 -0
  92. package/dist/components/onboarding/stages/OnboardingStageKeySave.svelte +44 -0
  93. package/dist/components/onboarding/stages/OnboardingStageKeySave.svelte.d.ts +8 -0
  94. package/dist/components/onboarding/stages/OnboardingStageWelcome.svelte +56 -0
  95. package/dist/components/onboarding/stages/OnboardingStageWelcome.svelte.d.ts +9 -0
  96. package/dist/components/onboarding/stages/SampleCreateFirstBackup.svelte +43 -0
  97. package/dist/components/onboarding/stages/SampleCreateFirstBackup.svelte.d.ts +7 -0
  98. package/dist/components/onboarding/stages/SampleCreateFirstSchedule.svelte +49 -0
  99. package/dist/components/onboarding/stages/SampleCreateFirstSchedule.svelte.d.ts +7 -0
  100. package/dist/components/schedules/RepositoryPicker.svelte +105 -0
  101. package/dist/components/schedules/RepositoryPicker.svelte.d.ts +6 -0
  102. package/dist/components/schedules/ScheduleItem.svelte +47 -0
  103. package/dist/components/schedules/ScheduleItem.svelte.d.ts +8 -0
  104. package/dist/components/schedules/ScheduleList.svelte +51 -0
  105. package/dist/components/schedules/ScheduleList.svelte.d.ts +3 -0
  106. package/dist/components/schedules/dialogs/ConfigureScheduleModal.svelte +48 -0
  107. package/dist/components/schedules/dialogs/ConfigureScheduleModal.svelte.d.ts +8 -0
  108. package/dist/components/schedules/dialogs/CreateScheduleModal.svelte +43 -0
  109. package/dist/components/schedules/dialogs/CreateScheduleModal.svelte.d.ts +6 -0
  110. package/dist/components/test/ImmichTestUi.svelte +183 -0
  111. package/dist/components/test/ImmichTestUi.svelte.d.ts +6 -0
  112. package/dist/components/test/TestUi.svelte +134 -0
  113. package/dist/components/test/TestUi.svelte.d.ts +6 -0
  114. package/dist/components/test/dashboard/ActiveJobs.svelte +380 -0
  115. package/dist/components/test/dashboard/ActiveJobs.svelte.d.ts +3 -0
  116. package/dist/components/test/dashboard/BackupHealth.svelte +95 -0
  117. package/dist/components/test/dashboard/BackupHealth.svelte.d.ts +7 -0
  118. package/dist/components/test/dashboard/BackupStats.svelte +117 -0
  119. package/dist/components/test/dashboard/BackupStats.svelte.d.ts +8 -0
  120. package/dist/components/test/dashboard/Dashboard.svelte +76 -0
  121. package/dist/components/test/dashboard/Dashboard.svelte.d.ts +6 -0
  122. package/dist/components/test/dashboard/RecentBackups.svelte +96 -0
  123. package/dist/components/test/dashboard/RecentBackups.svelte.d.ts +8 -0
  124. package/dist/components/ui/PageLayout.svelte +67 -0
  125. package/dist/components/ui/PageLayout.svelte.d.ts +10 -0
  126. package/dist/components/ui/PathListField.svelte +83 -0
  127. package/dist/components/ui/PathListField.svelte.d.ts +17 -0
  128. package/dist/components/ui/PathPickerField.svelte +74 -0
  129. package/dist/components/ui/PathPickerField.svelte.d.ts +15 -0
  130. package/dist/components/ui/PathPickerModal.svelte +219 -0
  131. package/dist/components/ui/PathPickerModal.svelte.d.ts +14 -0
  132. package/dist/components/ui/StackList.svelte +30 -0
  133. package/dist/components/ui/StackList.svelte.d.ts +30 -0
  134. package/dist/components/ui/StackListItem.svelte +64 -0
  135. package/dist/components/ui/StackListItem.svelte.d.ts +13 -0
  136. package/dist/components/ui/VisualisationGauge.svelte +25 -0
  137. package/dist/components/ui/VisualisationGauge.svelte.d.ts +10 -0
  138. package/dist/components/ui/VisualisationSegmentedBar.svelte +48 -0
  139. package/dist/components/ui/VisualisationSegmentedBar.svelte.d.ts +14 -0
  140. package/dist/components/util/OnEvents.svelte +31 -0
  141. package/dist/components/util/OnEvents.svelte.d.ts +7 -0
  142. package/dist/components/util/RelativeTime.svelte +21 -0
  143. package/dist/components/util/RelativeTime.svelte.d.ts +6 -0
  144. package/dist/components/util/Suspense.svelte +21 -0
  145. package/dist/components/util/Suspense.svelte.d.ts +29 -0
  146. package/dist/components/util/TimedButton.svelte +37 -0
  147. package/dist/components/util/TimedButton.svelte.d.ts +7 -0
  148. package/dist/components/util/YuccaContext.svelte +26 -0
  149. package/dist/components/util/YuccaContext.svelte.d.ts +8 -0
  150. package/dist/events.d.ts +6 -0
  151. package/dist/events.js +47 -0
  152. package/dist/fetch-client.d.ts +289 -0
  153. package/dist/fetch-client.js +233 -0
  154. package/dist/index.d.ts +28 -0
  155. package/dist/index.js +28 -0
  156. package/dist/options.d.ts +5 -0
  157. package/dist/options.js +6 -0
  158. package/dist/providers.d.ts +11 -0
  159. package/dist/providers.js +35 -0
  160. package/dist/query-client.d.ts +2 -0
  161. package/dist/query-client.js +2 -0
  162. package/dist/services/backend.service.d.ts +18 -0
  163. package/dist/services/backend.service.js +61 -0
  164. package/dist/services/filesystem.service.d.ts +2 -0
  165. package/dist/services/filesystem.service.js +11 -0
  166. package/dist/services/immich.integration.service.d.ts +6 -0
  167. package/dist/services/immich.integration.service.js +24 -0
  168. package/dist/services/integrations.service.d.ts +13 -0
  169. package/dist/services/integrations.service.js +42 -0
  170. package/dist/services/log.service.svelte.d.ts +53 -0
  171. package/dist/services/log.service.svelte.js +93 -0
  172. package/dist/services/metricsHistory.service.d.ts +4 -0
  173. package/dist/services/metricsHistory.service.js +12 -0
  174. package/dist/services/onboarding.service.d.ts +11 -0
  175. package/dist/services/onboarding.service.js +56 -0
  176. package/dist/services/repository.service.d.ts +45 -0
  177. package/dist/services/repository.service.js +157 -0
  178. package/dist/services/runHistory.service.d.ts +26 -0
  179. package/dist/services/runHistory.service.js +54 -0
  180. package/dist/services/schedule.service.d.ts +35 -0
  181. package/dist/services/schedule.service.js +126 -0
  182. package/dist/services/snapshot.service.d.ts +29 -0
  183. package/dist/services/snapshot.service.js +108 -0
  184. package/dist/services/task.service.d.ts +3 -0
  185. package/dist/services/task.service.js +20 -0
  186. package/dist/utils/actions.d.ts +2 -0
  187. package/dist/utils/actions.js +3 -0
  188. package/dist/utils/format.d.ts +2 -0
  189. package/dist/utils/format.js +24 -0
  190. package/dist/utils/handle-error.d.ts +9 -0
  191. package/dist/utils/handle-error.js +42 -0
  192. package/package.json +79 -0
@@ -0,0 +1,62 @@
1
+ <script lang="ts">
2
+ import { type LocalRepositoryDto } from "../../../fetch-client";
3
+ import {
4
+ handleCheckImportRepository,
5
+ useImportRepository,
6
+ } from "../../../services/repository.service";
7
+ import { FormModal, LoadingSpinner, modalManager, Text } from "@immich/ui";
8
+ import { onMount } from "svelte";
9
+ import ConfigureRepositoryModal from "./ConfigureRepositoryModal.svelte";
10
+
11
+ type Props = {
12
+ onClose: () => void;
13
+ repository: LocalRepositoryDto;
14
+ };
15
+
16
+ let { onClose, repository }: Props = $props();
17
+ let readable: boolean | undefined = $state();
18
+
19
+ onMount(async () => {
20
+ const check = await handleCheckImportRepository(
21
+ repository.id,
22
+ repository.backends!.primary.id,
23
+ );
24
+
25
+ readable = check.readable;
26
+ });
27
+
28
+ const mutation = useImportRepository();
29
+
30
+ const onSubmit = () =>
31
+ mutation.mutate(
32
+ { id: repository.id, backendId: repository.backends!.primary.id },
33
+ {
34
+ onSuccess: ({ repository: created }) => {
35
+ onClose();
36
+
37
+ modalManager.open(ConfigureRepositoryModal, {
38
+ repository: {
39
+ ...created,
40
+ configuration: created.configuration!,
41
+ },
42
+ });
43
+ },
44
+ },
45
+ );
46
+ </script>
47
+
48
+ <FormModal
49
+ title={`Import ${repository.name}`}
50
+ submitText="Import"
51
+ disabled={readable !== true || mutation.isPending}
52
+ {onSubmit}
53
+ {onClose}
54
+ >
55
+ {#if readable === undefined}
56
+ <LoadingSpinner />
57
+ {:else if readable}
58
+ <Text>Repository is readable and accessible!</Text>
59
+ {:else}
60
+ <Text>Can't read repository.</Text>
61
+ {/if}
62
+ </FormModal>
@@ -0,0 +1,8 @@
1
+ import { type LocalRepositoryDto } from "../../../fetch-client";
2
+ type Props = {
3
+ onClose: () => void;
4
+ repository: LocalRepositoryDto;
5
+ };
6
+ declare const ImportRepositoryModal: import("svelte").Component<Props, {}, "">;
7
+ type ImportRepositoryModal = ReturnType<typeof ImportRepositoryModal>;
8
+ export default ImportRepositoryModal;
@@ -0,0 +1,109 @@
1
+ <script lang="ts">
2
+ import PathListField from "../../ui/PathListField.svelte";
3
+ import PathPickerField from "../../ui/PathPickerField.svelte";
4
+ import type { RepositorySnapshotRestoreRequestDto } from "../../../fetch-client";
5
+ import {
6
+ handleGetSnapshotListing,
7
+ useRestoreSnapshot,
8
+ } from "../../../services/snapshot.service";
9
+ import {
10
+ FormModal,
11
+ Heading,
12
+ HStack,
13
+ modalManager,
14
+ Stack,
15
+ Switch,
16
+ Text,
17
+ } from "@immich/ui";
18
+ import { SvelteSet } from "svelte/reactivity";
19
+ import ViewLogModal from "./ViewLogModal.svelte";
20
+
21
+ type Props = {
22
+ repository: string;
23
+ snapshot: string;
24
+ onClose: () => void;
25
+ };
26
+
27
+ let { repository, snapshot, onClose }: Props = $props();
28
+
29
+ let inPlace = $state(true);
30
+ let target = $state("");
31
+ let include = new SvelteSet<string>();
32
+
33
+ const mutation = useRestoreSnapshot();
34
+
35
+ const onSubmit = () => {
36
+ const dto: RepositorySnapshotRestoreRequestDto = {
37
+ include: [...include],
38
+ };
39
+
40
+ if (!inPlace) {
41
+ dto.target = target;
42
+ }
43
+
44
+ mutation.mutate(
45
+ { repositoryId: repository, snapshotId: snapshot, options: dto },
46
+ {
47
+ onSuccess: ({ logId }) => {
48
+ onClose();
49
+
50
+ modalManager.open(ViewLogModal, {
51
+ logId,
52
+ });
53
+ },
54
+ },
55
+ );
56
+ };
57
+
58
+ </script>
59
+
60
+ <FormModal
61
+ title="Restore Backup"
62
+ submitText="Restore"
63
+ disabled={mutation.isPending}
64
+ {onSubmit}
65
+ {onClose}
66
+ >
67
+ <Stack gap={5}>
68
+ <PathListField
69
+ paths={include}
70
+ addLabel="Add more files"
71
+ manageLabel="Select files instead"
72
+ pickerTitle="Files to restore"
73
+ pickerDescription="Pick the files and folders to restore. Leave empty to restore everything."
74
+ handleGetListing={(path) =>
75
+ handleGetSnapshotListing(repository, snapshot, path)}
76
+ >
77
+ {#snippet label()}Files to restore{/snippet}
78
+ {#snippet empty()}Restoring all files and folders.{/snippet}
79
+ </PathListField>
80
+
81
+ <Stack gap={4}>
82
+ <Heading class="px-1" size="tiny">Options</Heading>
83
+
84
+ <HStack gap={4}>
85
+ <Stack gap={0}>
86
+ <Text>In-place restore</Text>
87
+ <Text color="secondary" size="small">
88
+ Restore files to where they were originally.
89
+ </Text>
90
+ </Stack>
91
+ <Switch bind:checked={inPlace} />
92
+ </HStack>
93
+
94
+ {#if !inPlace}
95
+ <PathPickerField
96
+ bind:value={target}
97
+ placeholder="/path/to/restore/into"
98
+ pickerTitle="Choose target folder"
99
+ pickerDescription="Pick the folder to restore files into."
100
+ >
101
+ {#snippet title()}Target{/snippet}
102
+ {#snippet description()}
103
+ Where do you want this backup restored to?
104
+ {/snippet}
105
+ </PathPickerField>
106
+ {/if}
107
+ </Stack>
108
+ </Stack>
109
+ </FormModal>
@@ -0,0 +1,8 @@
1
+ type Props = {
2
+ repository: string;
3
+ snapshot: string;
4
+ onClose: () => void;
5
+ };
6
+ declare const RestoreSnapshotModal: import("svelte").Component<Props, {}, "">;
7
+ type RestoreSnapshotModal = ReturnType<typeof RestoreSnapshotModal>;
8
+ export default RestoreSnapshotModal;
@@ -0,0 +1,208 @@
1
+ <script lang="ts">
2
+ import OnEvents from "../../util/OnEvents.svelte";
3
+ import RelativeTime from "../../util/RelativeTime.svelte";
4
+ import { options } from "../../../options";
5
+ import { createLogObserver } from "../../../services/log.service.svelte";
6
+ import { useRun, useRunEventHandler } from "../../../services/runHistory.service";
7
+ import { formatDuration } from "../../../utils/format";
8
+ import {
9
+ Alert,
10
+ FormatBytes,
11
+ Heading,
12
+ Modal,
13
+ ModalBody,
14
+ ProgressBar,
15
+ Scrollable,
16
+ Stack,
17
+ Text,
18
+ } from "@immich/ui";
19
+ import { DateTime } from "luxon";
20
+ import { onDestroy } from "svelte";
21
+
22
+ type Props = {
23
+ logId: string;
24
+ onClose: () => void;
25
+ };
26
+
27
+ let { logId, onClose }: Props = $props();
28
+
29
+ const advanced = options.advanced;
30
+ // svelte-ignore state_referenced_locally
31
+ const log = createLogObserver(logId);
32
+ // svelte-ignore state_referenced_locally
33
+ const runQuery = useRun(logId);
34
+ const run = $derived(runQuery.data);
35
+ const { onRunUpdate } = useRunEventHandler();
36
+
37
+ let now = $state(DateTime.now());
38
+ const tick = setInterval(() => (now = DateTime.now()), 1000);
39
+
40
+ onDestroy(() => {
41
+ clearInterval(tick);
42
+ log.destroy();
43
+ });
44
+
45
+ const running = $derived(run?.status === "incomplete");
46
+ const duration = $derived(
47
+ run
48
+ ? formatDuration(
49
+ (run.end ? DateTime.fromISO(run.end) : now).toMillis() -
50
+ DateTime.fromISO(run.start).toMillis(),
51
+ )
52
+ : "",
53
+ );
54
+ </script>
55
+
56
+ <OnEvents {onRunUpdate} />
57
+
58
+ <Modal
59
+ title={run?.type === "restore"
60
+ ? "Restore Log"
61
+ : run?.type === "forget"
62
+ ? "Prune Log"
63
+ : "Backup Log"}
64
+ size="giant"
65
+ {onClose}
66
+ >
67
+ <ModalBody>
68
+ <Stack gap={5}>
69
+ <Stack gap={1}>
70
+ {#if !run}
71
+ <Heading size="medium">Connecting…</Heading>
72
+ {:else if run.status === "incomplete"}
73
+ <Heading size="medium">
74
+ {#if run.type === "restore"}Restoring{:else if run.type === "forget"}Pruning{:else}Backing
75
+ up{/if} &middot; {Math.round(log.status.progress * 100)}%
76
+ </Heading>
77
+ {:else if run.status === "failed"}
78
+ <Heading size="medium" color="danger">
79
+ {#if run.type === "restore"}Restore{:else if run.type === "forget"}Prune{:else}Backup{/if}
80
+ failed after {duration}
81
+ </Heading>
82
+ {:else if log.errors.length > 0}
83
+ <Heading size="medium" color="warning">
84
+ {#if run.type === "restore"}Restored{:else if run.type === "forget"}Pruned{:else}Backed
85
+ up{/if} in {duration} &middot; {log.errors.length} error{log
86
+ .errors.length === 1
87
+ ? ""
88
+ : "s"}
89
+ </Heading>
90
+ {:else}
91
+ <Heading size="medium" color="success">
92
+ {#if run.type === "restore"}Restored{:else if run.type === "forget"}Pruned{:else}Backed
93
+ up{/if} in {duration}
94
+ </Heading>
95
+ {/if}
96
+
97
+ {#if run}
98
+ <Text color="secondary">
99
+ Started <RelativeTime time={run.start} />
100
+ </Text>
101
+ {/if}
102
+ </Stack>
103
+
104
+ {#if running}
105
+ <ProgressBar progress={log.status.progress} size="large">
106
+ {#if log.status.text}
107
+ <Text
108
+ size="small"
109
+ class={log.status.progress > 0.5 ? "text-light" : "text-dark"}
110
+ >
111
+ {log.status.text}
112
+ </Text>
113
+ {/if}
114
+ </ProgressBar>
115
+ {/if}
116
+
117
+ {#if log.errors.length > 0}
118
+ <Stack gap={2}>
119
+ <Alert color="danger">{log.errors[0]}</Alert>
120
+ {#if log.errors.length > 1}
121
+ <Scrollable class="max-h-32">
122
+ <Stack gap={1}>
123
+ {#each log.errors.slice(1) as error}
124
+ <Alert color="danger">{error}</Alert>
125
+ {/each}
126
+ </Stack>
127
+ </Scrollable>
128
+ {/if}
129
+ </Stack>
130
+ {/if}
131
+
132
+ {#if log.summary && run?.type !== "forget"}
133
+ <Stack gap={1}>
134
+ {#if run?.type === "restore"}
135
+ <Text>
136
+ {(log.summary.files_restored ?? 0).toLocaleString()} restored
137
+ {#if log.summary.files_skipped}
138
+ &middot; {log.summary.files_skipped.toLocaleString()} skipped
139
+ {/if}
140
+ {#if log.summary.files_deleted}
141
+ &middot; {log.summary.files_deleted.toLocaleString()} deleted
142
+ {/if}
143
+ </Text>
144
+ {#if log.summary.bytes_restored !== undefined}
145
+ <Text color="secondary">
146
+ Restored <FormatBytes bytes={log.summary.bytes_restored} />
147
+ {#if log.summary.total_bytes !== undefined}
148
+ of
149
+ <FormatBytes
150
+ bytes={Math.max(
151
+ log.summary.bytes_restored,
152
+ log.summary.total_bytes,
153
+ )}
154
+ />
155
+ {/if}
156
+ </Text>
157
+ {/if}
158
+ {:else}
159
+ <Text>
160
+ {(log.summary.files_new ?? 0).toLocaleString()} new &middot;
161
+ {(log.summary.files_changed ?? 0).toLocaleString()} changed &middot;
162
+ {(log.summary.files_unmodified ?? 0).toLocaleString()} unchanged
163
+ </Text>
164
+ {#if log.summary.data_added !== undefined}
165
+ <Text color="secondary">
166
+ Added <FormatBytes bytes={log.summary.data_added} />
167
+ {#if log.summary.total_bytes_processed !== undefined}
168
+ of
169
+ <FormatBytes
170
+ bytes={Math.max(
171
+ log.summary.data_added,
172
+ log.summary.total_bytes_processed,
173
+ )}
174
+ /> processed
175
+ {/if}
176
+ </Text>
177
+ {/if}
178
+ {/if}
179
+ </Stack>
180
+ {/if}
181
+
182
+ {#if running}
183
+ <Stack gap={1} class="h-20 overflow-hidden">
184
+ {#each log.status.currentFiles.slice(0, 3) as file}
185
+ <Text size="small" color="secondary" class="truncate" title={file}>
186
+ {file}
187
+ </Text>
188
+ {/each}
189
+ </Stack>
190
+ {/if}
191
+
192
+ {#if $advanced}
193
+ <Stack gap={1}>
194
+ <Heading size="small">Event Log</Heading>
195
+ <Scrollable class="h-80 overflow-x-hidden">
196
+ <Stack gap={1}>
197
+ {#each log.events as event}
198
+ <Text size="tiny" class="font-mono select-all">
199
+ {JSON.stringify(event)}
200
+ </Text>
201
+ {/each}
202
+ </Stack>
203
+ </Scrollable>
204
+ </Stack>
205
+ {/if}
206
+ </Stack>
207
+ </ModalBody>
208
+ </Modal>
@@ -0,0 +1,7 @@
1
+ type Props = {
2
+ logId: string;
3
+ onClose: () => void;
4
+ };
5
+ declare const ViewLogModal: import("svelte").Component<Props, {}, "">;
6
+ type ViewLogModal = ReturnType<typeof ViewLogModal>;
7
+ export default ViewLogModal;
@@ -0,0 +1,166 @@
1
+ <script lang="ts">
2
+ import type { LocalRepositoryDto } from "../../../fetch-client";
3
+ import { useMetricsHistory } from "../../../services/metricsHistory.service";
4
+ import { formatDuration } from "../../../utils/format";
5
+ import { getReadableErrorMessage } from "../../../utils/handle-error";
6
+ import {
7
+ Alert,
8
+ Badge,
9
+ getByteUnitString,
10
+ HStack,
11
+ Icon,
12
+ LoadingSpinner,
13
+ Modal,
14
+ ModalBody,
15
+ Stack,
16
+ Text,
17
+ } from "@immich/ui";
18
+ import {
19
+ mdiAlertCircleOutline,
20
+ mdiCheckCircleOutline,
21
+ mdiDatabaseOutline,
22
+ mdiPlayCircleOutline,
23
+ mdiTimerOutline,
24
+ } from "@mdi/js";
25
+ import RelativeTime from "../../util/RelativeTime.svelte";
26
+
27
+ type Props = {
28
+ repository: LocalRepositoryDto;
29
+ onClose: () => void;
30
+ };
31
+
32
+ const { repository, onClose }: Props = $props();
33
+
34
+ // svelte-ignore state_referenced_locally
35
+ const query = useMetricsHistory(repository.id);
36
+
37
+ const entries = $derived(
38
+ query.data?.pages.flatMap((page) => page.items) ?? [],
39
+ );
40
+
41
+ const isoDate = (value: string) => new Date(value).toLocaleString();
42
+
43
+ let sentinel = $state<HTMLDivElement | null>(null);
44
+
45
+ $effect(() => {
46
+ if (!sentinel) return;
47
+ const observer = new IntersectionObserver(
48
+ ([entry]) => {
49
+ if (
50
+ entry.isIntersecting &&
51
+ query.hasNextPage &&
52
+ !query.isFetchingNextPage
53
+ ) {
54
+ void query.fetchNextPage();
55
+ }
56
+ },
57
+ { rootMargin: "200px" },
58
+ );
59
+ observer.observe(sentinel);
60
+ return () => observer.disconnect();
61
+ });
62
+ </script>
63
+
64
+ <Modal title={`Metrics history for ${repository.name}`} size="large" {onClose}>
65
+ <ModalBody>
66
+ {#if query.isLoading}
67
+ <LoadingSpinner />
68
+ {:else if query.isError}
69
+ <Alert color="danger">{getReadableErrorMessage(query.error)}</Alert>
70
+ {:else if entries.length === 0}
71
+ <Text color="secondary" class="text-center py-6">
72
+ No metrics history yet.
73
+ </Text>
74
+ {:else}
75
+ <Stack gap={0} class="divide-y rounded-2xl border overflow-hidden">
76
+ {#each entries as entry (entry.id)}
77
+ {@const isStart = entry.started != null}
78
+ {@const isEnd = entry.backup != null}
79
+ {@const isSize = entry.sizeBytes != null && !isStart && !isEnd}
80
+ {@const succeeded =
81
+ isEnd &&
82
+ entry.successfulBackup != null &&
83
+ entry.successfulBackup === entry.backup}
84
+ <Stack gap={1} class="px-4 py-3">
85
+ <HStack class="justify-between gap-4">
86
+ <HStack class="gap-2">
87
+ {#if isStart}
88
+ <Icon
89
+ icon={mdiPlayCircleOutline}
90
+ size="18"
91
+ class="text-info-500"
92
+ />
93
+ <Text>Backup started</Text>
94
+ {:else if isEnd && succeeded}
95
+ <Icon
96
+ icon={mdiCheckCircleOutline}
97
+ size="18"
98
+ class="text-success-500"
99
+ />
100
+ <Text>Backup finished</Text>
101
+ <Badge size="tiny" color="success">Success</Badge>
102
+ {:else if isEnd}
103
+ <Icon
104
+ icon={mdiAlertCircleOutline}
105
+ size="18"
106
+ class="text-danger-500"
107
+ />
108
+ <Text>Backup finished</Text>
109
+ <Badge size="tiny" color="danger">Failed</Badge>
110
+ {:else if isSize}
111
+ <Icon
112
+ icon={mdiDatabaseOutline}
113
+ size="18"
114
+ class="text-info-500"
115
+ />
116
+ <Text>Size updated</Text>
117
+ {:else}
118
+ <Icon icon={mdiTimerOutline} size="18" color="secondary" />
119
+ <Text>Event</Text>
120
+ {/if}
121
+ </HStack>
122
+ <Text
123
+ color="secondary"
124
+ size="small"
125
+ title={isoDate(entry.createdAt)}
126
+ >
127
+ <RelativeTime time={entry.createdAt} />
128
+ </Text>
129
+ </HStack>
130
+
131
+ <HStack class="gap-2 flex-wrap pl-7">
132
+ {#if entry.backupDuration != null}
133
+ <Badge size="tiny" color="secondary">
134
+ Duration {formatDuration(entry.backupDuration)}
135
+ </Badge>
136
+ {/if}
137
+ {#if entry.sizeBytes != null}
138
+ <Badge size="tiny" color="secondary">
139
+ Size {getByteUnitString(entry.sizeBytes)}
140
+ </Badge>
141
+ {/if}
142
+ {#if entry.started}
143
+ <Badge size="tiny" color="secondary">
144
+ Started {isoDate(entry.started)}
145
+ </Badge>
146
+ {/if}
147
+ {#if entry.backup}
148
+ <Badge size="tiny" color="secondary">
149
+ Ended {isoDate(entry.backup)}
150
+ </Badge>
151
+ {/if}
152
+ </HStack>
153
+ </Stack>
154
+ {/each}
155
+ </Stack>
156
+
157
+ {#if query.hasNextPage}
158
+ <div bind:this={sentinel} class="flex justify-center py-4">
159
+ {#if query.isFetchingNextPage}
160
+ <LoadingSpinner />
161
+ {/if}
162
+ </div>
163
+ {/if}
164
+ {/if}
165
+ </ModalBody>
166
+ </Modal>
@@ -0,0 +1,8 @@
1
+ import type { LocalRepositoryDto } from "../../../fetch-client";
2
+ type Props = {
3
+ repository: LocalRepositoryDto;
4
+ onClose: () => void;
5
+ };
6
+ declare const MetricsHistoryModal: import("svelte").Component<Props, {}, "">;
7
+ type MetricsHistoryModal = ReturnType<typeof MetricsHistoryModal>;
8
+ export default MetricsHistoryModal;
@@ -0,0 +1,39 @@
1
+ <script lang="ts">
2
+ import StackList from "../../ui/StackList.svelte";
3
+ import OnEvents from "../../util/OnEvents.svelte";
4
+ import type { LocalRepositoryDto } from "../../../fetch-client";
5
+ import {
6
+ useRunEventHandler,
7
+ useRunHistory,
8
+ } from "../../../services/runHistory.service";
9
+ import { Text } from "@immich/ui";
10
+ import RepositoryRunHistoryItem from "./RepositoryRunHistoryItem.svelte";
11
+
12
+ type Props = {
13
+ repository: LocalRepositoryDto;
14
+ };
15
+
16
+ let { repository }: Props = $props();
17
+
18
+ // svelte-ignore state_referenced_locally
19
+ const query = useRunHistory(repository.id);
20
+ const { onRunCreate, onRunUpdate } = useRunEventHandler();
21
+ </script>
22
+
23
+ <OnEvents {onRunCreate} {onRunUpdate} />
24
+
25
+ <StackList {query}>
26
+ {#snippet title()}
27
+ Recent backup attempts
28
+ {/snippet}
29
+
30
+ {#snippet children(runs)}
31
+ {#if runs.length === 0}
32
+ <Text class="text-center py-6" color="muted">No recent backups</Text>
33
+ {:else}
34
+ {#each runs.slice(0, 5) as run (run.id)}
35
+ <RepositoryRunHistoryItem {run} />
36
+ {/each}
37
+ {/if}
38
+ {/snippet}
39
+ </StackList>
@@ -0,0 +1,7 @@
1
+ import type { LocalRepositoryDto } from "../../../fetch-client";
2
+ type Props = {
3
+ repository: LocalRepositoryDto;
4
+ };
5
+ declare const RepositoryRunHistory: import("svelte").Component<Props, {}, "">;
6
+ type RepositoryRunHistory = ReturnType<typeof RepositoryRunHistory>;
7
+ export default RepositoryRunHistory;
@@ -0,0 +1,45 @@
1
+ <script lang="ts">
2
+ import StackListItem from "../../ui/StackListItem.svelte";
3
+ import RelativeTime from "../../util/RelativeTime.svelte";
4
+ import type { RunDto } from "../../../fetch-client";
5
+ import { getRunActions } from "../../../services/runHistory.service";
6
+ import { Icon } from "@immich/ui";
7
+ import {
8
+ mdiAlertCircleOutline,
9
+ mdiCheckCircleOutline,
10
+ mdiLoading,
11
+ } from "@mdi/js";
12
+
13
+ type Props = {
14
+ run: RunDto;
15
+ };
16
+
17
+ const { run }: Props = $props();
18
+ const { ViewLog } = $derived(getRunActions(run));
19
+ </script>
20
+
21
+ <StackListItem actions={[ViewLog]}>
22
+ {#snippet icon()}
23
+ {#if run.status === "complete"}
24
+ <Icon icon={mdiCheckCircleOutline} class="text-success-500" />
25
+ {:else if run.status === "failed"}
26
+ <Icon icon={mdiAlertCircleOutline} class="text-danger-500" />
27
+ {:else}
28
+ <Icon icon={mdiLoading} class="animate-spin opacity-60" />
29
+ {/if}
30
+ {/snippet}
31
+
32
+ {#if run.status === "incomplete"}
33
+ {#if run.type === "restore"}Restoring{:else if run.type === "forget"}Pruning{:else}Backing
34
+ up{/if} &middot; started
35
+ <RelativeTime time={run.start} />
36
+ {:else if run.status === "failed"}
37
+ {#if run.type === "restore"}Restore{:else if run.type === "forget"}Prune{:else}Backup{/if}
38
+ failed
39
+ {#if run.end}<RelativeTime time={run.end} />{/if}
40
+ {:else}
41
+ {#if run.type === "restore"}Restored{:else if run.type === "forget"}Pruned{:else}Backed
42
+ up{/if}
43
+ {#if run.end}<RelativeTime time={run.end} />{/if}
44
+ {/if}
45
+ </StackListItem>
@@ -0,0 +1,7 @@
1
+ import type { RunDto } from "../../../fetch-client";
2
+ type Props = {
3
+ run: RunDto;
4
+ };
5
+ declare const RepositoryRunHistoryItem: import("svelte").Component<Props, {}, "">;
6
+ type RepositoryRunHistoryItem = ReturnType<typeof RepositoryRunHistoryItem>;
7
+ export default RepositoryRunHistoryItem;