@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,117 @@
1
+ <script lang="ts">
2
+ import type { LocalRepositoryDto } from "../../../fetch-client";
3
+ import { formatDuration } from "../../../utils/format";
4
+ import { Card, CardBody, getByteUnitString, Icon, Stack } from "@immich/ui";
5
+ import { mdiArrowDown, mdiArrowUp } from "@mdi/js";
6
+ import Gauge from "../../ui/VisualisationGauge.svelte";
7
+
8
+ type Props = {
9
+ repositories: LocalRepositoryDto[];
10
+ compact?: boolean;
11
+ };
12
+
13
+ const { repositories, compact = true }: Props = $props();
14
+
15
+ const durations = $derived(
16
+ repositories
17
+ .map((repo) => repo.metrics?.lastBackupDuration)
18
+ .filter((duration): duration is number => duration != null),
19
+ );
20
+
21
+ const avgBackupTime = $derived(
22
+ durations.length > 0
23
+ ? durations.reduce((sum, duration) => sum + duration, 0) /
24
+ durations.length
25
+ : undefined,
26
+ );
27
+
28
+ const dailyBackupTime = $derived(
29
+ durations.length > 0
30
+ ? durations.reduce((sum, duration) => sum + duration, 0)
31
+ : undefined,
32
+ );
33
+
34
+ const totalStored = $derived(
35
+ repositories.reduce((sum, repo) => sum + (repo.metrics?.sizeBytes ?? 0), 0),
36
+ );
37
+ </script>
38
+
39
+ {#snippet costSubtitle()}
40
+ Since March 1
41
+ {/snippet}
42
+
43
+ {#snippet estimateSubtitle()}
44
+ <Icon icon={mdiArrowDown} class="inline text-success-500" /> $2.52 less than last
45
+ month
46
+ {/snippet}
47
+
48
+ {#snippet totalStoredSubtitle()}
49
+ <Icon icon={mdiArrowUp} class="inline text-info-500" />
50
+ {getByteUnitString(320_000_000_000)} in 24h
51
+ {/snippet}
52
+
53
+ {#if compact}
54
+ <Card>
55
+ <CardBody>
56
+ <div class="grid grid-cols-2 gap-5 sm:grid-cols-3">
57
+ <Gauge
58
+ title="Avg. Backup Time"
59
+ content={avgBackupTime != null ? formatDuration(avgBackupTime) : "—"}
60
+ />
61
+ <Gauge
62
+ title="Daily Backup Time"
63
+ content={dailyBackupTime != null
64
+ ? formatDuration(dailyBackupTime)
65
+ : "—"}
66
+ />
67
+ <Gauge title="Total Stored" content={getByteUnitString(totalStored)} />
68
+ </div>
69
+ </CardBody>
70
+ </Card>
71
+ {:else}
72
+ <Card>
73
+ <CardBody>
74
+ <Stack>
75
+ <div class="grid grid-cols-2 gap-5 sm:grid-cols-3">
76
+ <Gauge
77
+ title="Cost This Period"
78
+ content="$2.43"
79
+ subtitle={costSubtitle}
80
+ />
81
+ <Gauge
82
+ title="Monthly Estimate"
83
+ content="$13.52 /mo"
84
+ subtitle={estimateSubtitle}
85
+ />
86
+ <Gauge
87
+ title="Total Stored"
88
+ content={getByteUnitString(totalStored)}
89
+ subtitle={totalStoredSubtitle}
90
+ />
91
+ </div>
92
+
93
+ <hr
94
+ style="border: none; border-top: 1px solid var(--immich-ui-default-border);"
95
+ />
96
+
97
+ <div class="grid grid-cols-2 gap-5 opacity-80 sm:grid-cols-3">
98
+ <Gauge
99
+ compact
100
+ title="Avg. Backup Time"
101
+ content={avgBackupTime != null
102
+ ? formatDuration(avgBackupTime)
103
+ : "—"}
104
+ />
105
+ <Gauge
106
+ compact
107
+ title="Daily Backup Time"
108
+ content={dailyBackupTime != null
109
+ ? formatDuration(dailyBackupTime)
110
+ : "—"}
111
+ />
112
+ <Gauge compact title="Space Saved" content="4.7x" />
113
+ </div>
114
+ </Stack>
115
+ </CardBody>
116
+ </Card>
117
+ {/if}
@@ -0,0 +1,8 @@
1
+ import type { LocalRepositoryDto } from "../../../fetch-client";
2
+ type Props = {
3
+ repositories: LocalRepositoryDto[];
4
+ compact?: boolean;
5
+ };
6
+ declare const BackupStats: import("svelte").Component<Props, {}, "">;
7
+ type BackupStats = ReturnType<typeof BackupStats>;
8
+ export default BackupStats;
@@ -0,0 +1,76 @@
1
+ <script lang="ts">
2
+ import { options } from "../../../options";
3
+ import { getReadableErrorMessage } from "../../../utils/handle-error";
4
+ import {
5
+ Alert,
6
+ Button,
7
+ Card,
8
+ CardBody,
9
+ Icon,
10
+ LoadingSpinner,
11
+ Stack,
12
+ Text,
13
+ } from "@immich/ui";
14
+ import { mdiBackupRestore } from "@mdi/js";
15
+
16
+ import {
17
+ useRepositories,
18
+ useRepositoryEventHandler,
19
+ } from "../../../services/repository.service";
20
+ import OnEvents from "../../util/OnEvents.svelte";
21
+ import ActiveJobs from "./ActiveJobs.svelte";
22
+ import BackupHealth from "./BackupHealth.svelte";
23
+ import BackupStats from "./BackupStats.svelte";
24
+ import RecentBackups from "./RecentBackups.svelte";
25
+
26
+ type Props = {
27
+ onNavigate?: (route: string) => void;
28
+ };
29
+
30
+ const { advanced } = options;
31
+
32
+ const { onNavigate }: Props = $props();
33
+
34
+ const query = useRepositories();
35
+ const { onRepositoryCreate, onRepositoryUpdate } =
36
+ useRepositoryEventHandler();
37
+ </script>
38
+
39
+ <OnEvents {onRepositoryCreate} {onRepositoryUpdate} />
40
+
41
+ {#if query.isLoading}
42
+ <LoadingSpinner />
43
+ {:else if query.isError}
44
+ <Alert color="danger">{getReadableErrorMessage(query.error)}</Alert>
45
+ {:else if query.isSuccess && query.data.length === 0}
46
+ <Card>
47
+ <CardBody>
48
+ <div class="flex flex-col items-center gap-4 py-8">
49
+ <Icon icon={mdiBackupRestore} size="48" color="muted" />
50
+ <Stack class="items-center gap-1">
51
+ <Text size="large">No backups configured yet</Text>
52
+ <Text color="secondary" class="text-center max-w-md"
53
+ >Once you set up a backup, this dashboard will show its health,
54
+ schedule, and storage usage at a glance.</Text
55
+ >
56
+ </Stack>
57
+ {#if onNavigate}
58
+ <Button color="primary" onclick={() => onNavigate("backups")}
59
+ >Set up your first backup</Button
60
+ >
61
+ {/if}
62
+ </div>
63
+ </CardBody>
64
+ </Card>
65
+ {:else if query.isSuccess}
66
+ <BackupHealth repositories={query.data} />
67
+
68
+ <BackupStats repositories={query.data} />
69
+
70
+ <ActiveJobs />
71
+
72
+ <RecentBackups
73
+ repositories={query.data}
74
+ onNavigate={onNavigate ? () => onNavigate("backups") : undefined}
75
+ />
76
+ {/if}
@@ -0,0 +1,6 @@
1
+ type Props = {
2
+ onNavigate?: (route: string) => void;
3
+ };
4
+ declare const Dashboard: import("svelte").Component<Props, {}, "">;
5
+ type Dashboard = ReturnType<typeof Dashboard>;
6
+ export default Dashboard;
@@ -0,0 +1,96 @@
1
+ <script lang="ts">
2
+ import RelativeTime from "../../util/RelativeTime.svelte";
3
+ import type { LocalRepositoryDto } from "../../../fetch-client";
4
+ import { formatDuration } from "../../../utils/format";
5
+ import {
6
+ Button,
7
+ Card,
8
+ CardBody,
9
+ HStack,
10
+ Icon,
11
+ Stack,
12
+ Text,
13
+ } from "@immich/ui";
14
+ import {
15
+ mdiAlertCircleOutline,
16
+ mdiCheckCircleOutline,
17
+ mdiChevronRight,
18
+ } from "@mdi/js";
19
+
20
+ type Props = {
21
+ repositories: LocalRepositoryDto[];
22
+ onNavigate?: () => void;
23
+ };
24
+
25
+ const { repositories, onNavigate }: Props = $props();
26
+
27
+ const recentBackups = $derived(
28
+ repositories
29
+ .filter((repo) => repo.metrics.lastBackup)
30
+ .toSorted(
31
+ (a, b) =>
32
+ +new Date(b.metrics.lastBackup!) - +new Date(a.metrics.lastBackup!),
33
+ )
34
+ .slice(0, 5),
35
+ );
36
+ </script>
37
+
38
+ <Card>
39
+ <CardBody>
40
+ <Stack>
41
+ <HStack class="items-center justify-between">
42
+ <Text size="large">Recent Backups</Text>
43
+ {#if onNavigate && recentBackups.length > 0}
44
+ <Button size="small" variant="outline" onclick={onNavigate}>
45
+ View all
46
+ <Icon icon={mdiChevronRight} size="16" />
47
+ </Button>
48
+ {/if}
49
+ </HStack>
50
+
51
+ {#if recentBackups.length === 0}
52
+ <Text color="secondary">
53
+ Completed backups will appear here once your first backup runs.
54
+ </Text>
55
+ {:else}
56
+ <div>
57
+ {#each recentBackups as repo, index (repo.id)}
58
+ {#if index > 0}
59
+ <hr
60
+ style="border: none; border-top: 1px solid var(--immich-ui-default-border);"
61
+ />
62
+ {/if}
63
+ <HStack class="items-center justify-between py-2">
64
+ <HStack class="items-center gap-2">
65
+ {#if repo.metrics.lastBackup === repo.metrics.lastSuccessfulBackup}
66
+ <Icon
67
+ icon={mdiCheckCircleOutline}
68
+ size="16"
69
+ class="text-green-500"
70
+ />
71
+ {:else}
72
+ <Icon
73
+ icon={mdiAlertCircleOutline}
74
+ size="16"
75
+ class="text-danger-500"
76
+ />
77
+ {/if}
78
+ <Text class="text-sm">{repo.name}</Text>
79
+ </HStack>
80
+ <HStack class="items-center gap-2">
81
+ {#if repo.metrics.lastBackupDuration != null}
82
+ <Text color="secondary" class="text-xs">
83
+ {formatDuration(repo.metrics.lastBackupDuration)}
84
+ </Text>
85
+ {/if}
86
+ <Text color="secondary" class="text-xs">
87
+ <RelativeTime time={repo.metrics.lastBackup!} />
88
+ </Text>
89
+ </HStack>
90
+ </HStack>
91
+ {/each}
92
+ </div>
93
+ {/if}
94
+ </Stack>
95
+ </CardBody>
96
+ </Card>
@@ -0,0 +1,8 @@
1
+ import type { LocalRepositoryDto } from "../../../fetch-client";
2
+ type Props = {
3
+ repositories: LocalRepositoryDto[];
4
+ onNavigate?: () => void;
5
+ };
6
+ declare const RecentBackups: import("svelte").Component<Props, {}, "">;
7
+ type RecentBackups = ReturnType<typeof RecentBackups>;
8
+ export default RecentBackups;
@@ -0,0 +1,67 @@
1
+ <script lang="ts">
2
+ import { options } from "../../options";
3
+ import {
4
+ Button,
5
+ ContextMenuButton,
6
+ HStack,
7
+ type ActionItem,
8
+ } from "@immich/ui";
9
+ import type { Snippet } from "svelte";
10
+
11
+ type Props = {
12
+ title?: string;
13
+ actions?: ActionItem[];
14
+ children?: Snippet;
15
+ };
16
+
17
+ const { title, actions = [], children }: Props = $props();
18
+ const { demoPadding } = options;
19
+ </script>
20
+
21
+ <main class="absolute inset-0">
22
+ {#if title || actions.length > 0}
23
+ <div
24
+ class="absolute top-0 left-0 flex h-16 w-full place-items-center justify-between border-b p-2 text-dark"
25
+ >
26
+ <div class="flex gap-2 items-center">
27
+ {#if title}
28
+ <div class="outline-none pe-8">{title}</div>
29
+ {/if}
30
+ </div>
31
+
32
+ {#if actions.length > 0}
33
+ <div class="hidden md:block">
34
+ <HStack gap={0}>
35
+ {#each actions as action, index (index)}
36
+ {#if !action.$if || action.$if?.()}
37
+ <Button
38
+ variant="ghost"
39
+ size="small"
40
+ color={action.color ?? "secondary"}
41
+ leadingIcon={action.icon}
42
+ onclick={() => action.onAction(action)}
43
+ >
44
+ {action.title}
45
+ </Button>
46
+ {/if}
47
+ {/each}
48
+ </HStack>
49
+ </div>
50
+
51
+ <ContextMenuButton
52
+ aria-label="Open"
53
+ items={actions}
54
+ class="md:hidden"
55
+ />
56
+ {/if}
57
+ </div>
58
+ {/if}
59
+
60
+ <div
61
+ class="absolute left-0 w-full overflow-y-auto {title || actions.length > 0
62
+ ? 'top-16 h-[calc(100%-4rem)]'
63
+ : 'top-0 h-full'} {$demoPadding ? 'p-4' : ''}"
64
+ >
65
+ {@render children?.()}
66
+ </div>
67
+ </main>
@@ -0,0 +1,10 @@
1
+ import { type ActionItem } from "@immich/ui";
2
+ import type { Snippet } from "svelte";
3
+ type Props = {
4
+ title?: string;
5
+ actions?: ActionItem[];
6
+ children?: Snippet;
7
+ };
8
+ declare const PageLayout: import("svelte").Component<Props, {}, "">;
9
+ type PageLayout = ReturnType<typeof PageLayout>;
10
+ export default PageLayout;
@@ -0,0 +1,83 @@
1
+ <script lang="ts">
2
+ import StackList from "./StackList.svelte";
3
+ import type { FilesystemListingResponseDto } from "../../fetch-client";
4
+ import {
5
+ Button,
6
+ HStack,
7
+ IconButton,
8
+ modalManager,
9
+ Text,
10
+ } from "@immich/ui";
11
+ import { mdiClose } from "@mdi/js";
12
+ import type { Snippet } from "svelte";
13
+ import type { SvelteSet } from "svelte/reactivity";
14
+ import PathPickerModal from "./PathPickerModal.svelte";
15
+
16
+ type Props = {
17
+ paths: SvelteSet<string>;
18
+ label?: Snippet;
19
+ empty?: Snippet;
20
+ addLabel?: string;
21
+ manageLabel?: string;
22
+ pickerTitle?: string;
23
+ pickerDescription?: string;
24
+ foldersOnly?: boolean;
25
+ handleGetListing?: (path?: string) => Promise<FilesystemListingResponseDto>;
26
+ };
27
+
28
+ let {
29
+ paths,
30
+ label,
31
+ empty,
32
+ addLabel = "Manage paths",
33
+ manageLabel = "Manage paths",
34
+ pickerTitle = "Choose paths",
35
+ pickerDescription,
36
+ foldersOnly = false,
37
+ handleGetListing,
38
+ }: Props = $props();
39
+
40
+ const openPicker = () =>
41
+ modalManager.show(PathPickerModal, {
42
+ title: pickerTitle,
43
+ description: pickerDescription,
44
+ foldersOnly,
45
+ initial: [...paths],
46
+ handleGetListing,
47
+ onSubmit: (next) => {
48
+ paths.clear();
49
+ for (const path of next) paths.add(path);
50
+ },
51
+ });
52
+ </script>
53
+
54
+ <StackList>
55
+ {#snippet title()}
56
+ {@render label?.()}
57
+ {/snippet}
58
+
59
+ {#if paths.size > 0}
60
+ {#each [...paths] as path (path)}
61
+ <HStack gap={2} class="px-4 py-3">
62
+ <Text class="grow truncate" title={path}>{path}</Text>
63
+ <IconButton
64
+ icon={mdiClose}
65
+ aria-label="Remove"
66
+ size="tiny"
67
+ variant="ghost"
68
+ onclick={() => paths.delete(path)}
69
+ />
70
+ </HStack>
71
+ {/each}
72
+ {:else if empty}
73
+ <HStack class="px-4 py-3">
74
+ <Text color="secondary">{@render empty()}</Text>
75
+ </HStack>
76
+ {/if}
77
+
78
+ <HStack class="px-4 py-2">
79
+ <Button size="small" variant="ghost" onclick={openPicker}>
80
+ {paths.size > 0 ? addLabel : manageLabel}
81
+ </Button>
82
+ </HStack>
83
+ </StackList>
@@ -0,0 +1,17 @@
1
+ import type { FilesystemListingResponseDto } from "../../fetch-client";
2
+ import type { Snippet } from "svelte";
3
+ import type { SvelteSet } from "svelte/reactivity";
4
+ type Props = {
5
+ paths: SvelteSet<string>;
6
+ label?: Snippet;
7
+ empty?: Snippet;
8
+ addLabel?: string;
9
+ manageLabel?: string;
10
+ pickerTitle?: string;
11
+ pickerDescription?: string;
12
+ foldersOnly?: boolean;
13
+ handleGetListing?: (path?: string) => Promise<FilesystemListingResponseDto>;
14
+ };
15
+ declare const PathListField: import("svelte").Component<Props, {}, "">;
16
+ type PathListField = ReturnType<typeof PathListField>;
17
+ export default PathListField;
@@ -0,0 +1,74 @@
1
+ <script lang="ts">
2
+ import PathPickerModal from "./PathPickerModal.svelte";
3
+ import type { FilesystemListingResponseDto } from "../../fetch-client";
4
+ import {
5
+ Heading,
6
+ HStack,
7
+ IconButton,
8
+ Input,
9
+ modalManager,
10
+ Stack,
11
+ Text,
12
+ } from "@immich/ui";
13
+ import { mdiFolder } from "@mdi/js";
14
+ import type { Snippet } from "svelte";
15
+
16
+ type Props = {
17
+ value: string;
18
+ title?: Snippet;
19
+ description?: Snippet;
20
+ placeholder?: string;
21
+ pickerTitle?: string;
22
+ pickerDescription?: string;
23
+ foldersOnly?: boolean;
24
+ handleGetListing?: (path?: string) => Promise<FilesystemListingResponseDto>;
25
+ };
26
+
27
+ let {
28
+ value = $bindable(""),
29
+ title,
30
+ description,
31
+ placeholder = "/path/to/folder",
32
+ pickerTitle = "Choose folder",
33
+ pickerDescription,
34
+ foldersOnly = true,
35
+ handleGetListing,
36
+ }: Props = $props();
37
+
38
+ const browse = () =>
39
+ modalManager.open(PathPickerModal, {
40
+ title: pickerTitle,
41
+ description: pickerDescription,
42
+ foldersOnly,
43
+ single: true,
44
+ handleGetListing,
45
+ onSubmit: ([path]) => {
46
+ if (path) {
47
+ value = path;
48
+ }
49
+ },
50
+ });
51
+ </script>
52
+
53
+ <Stack gap={2}>
54
+ {#if title || description}
55
+ <Stack gap={0}>
56
+ {#if title}
57
+ <Heading class="px-1" size="tiny">{@render title()}</Heading>
58
+ {/if}
59
+ {#if description}
60
+ <Text color="secondary" size="small">{@render description()}</Text>
61
+ {/if}
62
+ </Stack>
63
+ {/if}
64
+ <HStack gap={2}>
65
+ <Input bind:value {placeholder} />
66
+ <IconButton
67
+ icon={mdiFolder}
68
+ aria-label="Choose folder"
69
+ variant="outline"
70
+ color="secondary"
71
+ onclick={browse}
72
+ />
73
+ </HStack>
74
+ </Stack>
@@ -0,0 +1,15 @@
1
+ import type { FilesystemListingResponseDto } from "../../fetch-client";
2
+ import type { Snippet } from "svelte";
3
+ type Props = {
4
+ value: string;
5
+ title?: Snippet;
6
+ description?: Snippet;
7
+ placeholder?: string;
8
+ pickerTitle?: string;
9
+ pickerDescription?: string;
10
+ foldersOnly?: boolean;
11
+ handleGetListing?: (path?: string) => Promise<FilesystemListingResponseDto>;
12
+ };
13
+ declare const PathPickerField: import("svelte").Component<Props, {}, "value">;
14
+ type PathPickerField = ReturnType<typeof PathPickerField>;
15
+ export default PathPickerField;