@futo-org/backups-orchestrator-ui 0.28.0 → 0.30.1

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 (24) hide show
  1. package/dist/components/backups/run-history/RepositoryRunHistory.svelte +2 -3
  2. package/dist/components/backups/run-history/RepositoryRunHistoryPage.svelte +4 -3
  3. package/dist/components/integrations/immich/ImmichBackupsCard.svelte +79 -43
  4. package/dist/components/integrations/immich/ImmichBackupsCard.svelte.d.ts +2 -1
  5. package/dist/components/integrations/immich/ImmichBackupsPage.svelte +2 -1
  6. package/dist/components/integrations/immich/ImmichBackupsPage.svelte.d.ts +2 -1
  7. package/dist/components/integrations/immich/ImmichBackupsSidebarItem.svelte +48 -12
  8. package/dist/components/integrations/immich/settings/ImmichSettingsStorage.svelte +1 -1
  9. package/dist/components/onboarding/upsell/UpsellFrequentlyAskedQuestions.svelte +7 -2
  10. package/dist/components/onboarding/upsell/UpsellFrequentlyAskedQuestions.svelte.d.ts +2 -1
  11. package/dist/components/onboarding/upsell/UpsellPage.svelte +2 -1
  12. package/dist/components/onboarding/upsell/UpsellPage.svelte.d.ts +2 -1
  13. package/dist/components/test/ImmichTestUi.svelte +23 -9
  14. package/dist/components/test/immich/MockImmichHideBackupsReminder.svelte +12 -0
  15. package/dist/components/test/immich/MockImmichHideBackupsReminder.svelte.d.ts +3 -0
  16. package/dist/components/test/immich/MockImmichServerStatus.svelte +9 -0
  17. package/dist/components/test/immich/MockImmichServerStatus.svelte.d.ts +18 -0
  18. package/dist/components/test/immich/MockImmichSidebar.svelte +20 -0
  19. package/dist/components/test/immich/MockImmichSidebar.svelte.d.ts +7 -0
  20. package/dist/components/test/immich/MockImmichStorageSpace.svelte +14 -26
  21. package/dist/components/test/immich/MockImmichStorageSpace.svelte.d.ts +2 -1
  22. package/dist/services/runHistory.service.d.ts +1 -1
  23. package/dist/services/runHistory.service.js +3 -2
  24. package/package.json +2 -2
@@ -1,8 +1,8 @@
1
1
  <script lang="ts">
2
2
  import StackList from "../../ui/StackList.svelte";
3
3
  import StackListPlaceholder from "../../ui/StackListPlaceholder.svelte";
4
- import Suspense from "../../util/Suspense.svelte";
5
4
  import OnEvents from "../../util/OnEvents.svelte";
5
+ import Suspense from "../../util/Suspense.svelte";
6
6
  import type { LocalRepositoryDto } from "../../../fetch-client";
7
7
  import {
8
8
  useRunEventHandler,
@@ -19,8 +19,7 @@
19
19
 
20
20
  let { repository, limit = 5, onViewAll }: Props = $props();
21
21
 
22
- // svelte-ignore state_referenced_locally
23
- const query = useRunHistory(repository.id);
22
+ const query = useRunHistory(() => repository.id);
24
23
  const { onRunCreate, onRunUpdate } = useRunEventHandler();
25
24
  </script>
26
25
 
@@ -21,8 +21,7 @@
21
21
 
22
22
  let { repository, pageSize = 10 }: Props = $props();
23
23
 
24
- // svelte-ignore state_referenced_locally
25
- const query = useRunHistory(repository.id);
24
+ const query = useRunHistory(() => repository.id);
26
25
  const { onRunCreate, onRunUpdate } = useRunEventHandler();
27
26
 
28
27
  let search = $state("");
@@ -101,7 +100,9 @@
101
100
  ),
102
101
  );
103
102
 
104
- const pageCount = $derived(Math.max(Math.ceil(filtered.length / pageSize), 1));
103
+ const pageCount = $derived(
104
+ Math.max(Math.ceil(filtered.length / pageSize), 1),
105
+ );
105
106
  const current = $derived(Math.min(page, pageCount));
106
107
  const visible = $derived(
107
108
  filtered.slice((current - 1) * pageSize, current * pageSize),
@@ -1,13 +1,22 @@
1
1
  <script lang="ts">
2
2
  import RelativeTime from "../../util/RelativeTime.svelte";
3
- import { FormatBytes, Icon, Stack, Text } from "@immich/ui";
4
- import { mdiChevronRight, mdiCloudAlertOutline, mdiCloudUpload } from "@mdi/js";
3
+ import { Icon, Text } from "@immich/ui";
4
+ import {
5
+ mdiChevronRight,
6
+ mdiCloudAlertOutline,
7
+ mdiCloudCheckVariantOutline,
8
+ mdiCloudOffOutline,
9
+ mdiCloudUploadOutline,
10
+ } from "@mdi/js";
11
+
12
+ type Color = "primary" | "success" | "warning" | "danger";
5
13
 
6
14
  type Props = {
7
15
  configured: boolean;
8
16
  lastBackup?: string;
9
17
  failed?: boolean;
10
- sizeBytes?: number;
18
+ paused?: boolean;
19
+ running?: boolean;
11
20
  href?: string;
12
21
  onclick?: () => void;
13
22
  class?: string;
@@ -17,57 +26,84 @@
17
26
  configured,
18
27
  lastBackup,
19
28
  failed = false,
20
- sizeBytes,
29
+ paused = false,
30
+ running = false,
21
31
  href,
22
32
  onclick,
23
33
  class: className,
24
34
  }: Props = $props();
25
35
 
26
- const shell =
27
- "border-primary/25 hover:bg-primary/5 flex w-full items-center gap-3 rounded-xl border px-3 py-2.5 text-start transition-colors";
36
+ const status = $derived.by(() => {
37
+ if (running) {
38
+ return { color: "primary", icon: mdiCloudUploadOutline } as const;
39
+ }
40
+
41
+ if (failed) {
42
+ return { color: "danger", icon: mdiCloudOffOutline } as const;
43
+ }
44
+
45
+ if (paused) {
46
+ return { color: "warning", icon: mdiCloudAlertOutline } as const;
47
+ }
48
+
49
+ if (!configured || !lastBackup) {
50
+ return { color: "warning", icon: mdiCloudOffOutline } as const;
51
+ }
52
+
53
+ return { color: "success", icon: mdiCloudCheckVariantOutline } as const;
54
+ });
55
+
56
+ const tints: Record<Color, string> = {
57
+ primary: "bg-primary-50 text-primary",
58
+ success: "bg-success-50 text-success-700",
59
+ warning: "bg-warning-50 text-warning-800",
60
+ danger: "bg-danger-50 text-danger-700",
61
+ };
62
+
63
+ const shell = $derived(
64
+ `flex w-full cursor-pointer items-center gap-2 px-3 py-3 text-start text-sm ${tints[status.color]} ${className ?? ""}`,
65
+ );
28
66
  </script>
29
67
 
30
68
  {#snippet body()}
31
- <Icon
32
- icon={failed ? mdiCloudAlertOutline : mdiCloudUpload}
33
- size="2em"
34
- class="{failed ? 'text-danger' : 'text-primary'} shrink-0"
35
- />
69
+ <Icon icon={status.icon} size="1.25em" class="shrink-0" />
70
+
71
+ <Text
72
+ size="small"
73
+ color={status.color}
74
+ fontWeight="medium"
75
+ class="flex-1 truncate"
76
+ >
77
+ {#if running}
78
+ Backing up now
79
+ {:else if failed}
80
+ Backup failed
81
+ {:else if paused}
82
+ Backups paused
83
+ {:else if !configured}
84
+ Not backed up
85
+ {:else if lastBackup}
86
+ Last backup <RelativeTime time={lastBackup} />
87
+ {:else}
88
+ Finish setting up backups
89
+ {/if}
90
+ </Text>
36
91
 
37
92
  {#if !configured}
38
- <Text color="primary" fontWeight="medium" class="flex-1 whitespace-nowrap">
39
- Set up Backups
93
+ <Text size="small" color="primary" fontWeight="medium" class="shrink-0">
94
+ Set up
40
95
  </Text>
41
- {:else}
42
- <Stack gap={0} class="min-w-0 flex-1">
43
- <Text fontWeight="medium" color={failed ? "danger" : "primary"}>
44
- {failed ? "Backup failed" : "Last Backup"}
45
- </Text>
46
-
47
- <Text size="small" color="muted" class="truncate">
48
- {#if lastBackup}
49
- <RelativeTime time={lastBackup} />
50
- {#if sizeBytes !== undefined}
51
- &nbsp;&middot; <FormatBytes bytes={sizeBytes} />
52
- {/if}
53
- {:else}
54
- No backups yet
55
- {/if}
56
- </Text>
57
- </Stack>
58
-
59
- <Icon icon={mdiChevronRight} size="1.4em" class="text-muted shrink-0" />
60
96
  {/if}
97
+
98
+ <Icon icon={mdiChevronRight} size="1.25em" class="shrink-0" />
61
99
  {/snippet}
62
100
 
63
- <div class={className ?? ""}>
64
- {#if href}
65
- <a {href} {onclick} class={shell}>
66
- {@render body()}
67
- </a>
68
- {:else}
69
- <button type="button" {onclick} class={shell}>
70
- {@render body()}
71
- </button>
72
- {/if}
73
- </div>
101
+ {#if href}
102
+ <a {href} {onclick} class={shell}>
103
+ {@render body()}
104
+ </a>
105
+ {:else}
106
+ <button type="button" {onclick} class={shell}>
107
+ {@render body()}
108
+ </button>
109
+ {/if}
@@ -2,7 +2,8 @@ type Props = {
2
2
  configured: boolean;
3
3
  lastBackup?: string;
4
4
  failed?: boolean;
5
- sizeBytes?: number;
5
+ paused?: boolean;
6
+ running?: boolean;
6
7
  href?: string;
7
8
  onclick?: () => void;
8
9
  class?: string;
@@ -1,11 +1,12 @@
1
1
  <script lang="ts">
2
+ import type { Snippet } from "svelte";
2
3
  import UpsellPage from "../../onboarding/upsell/UpsellPage.svelte";
3
4
  import ImmichManageBackup from "./ImmichManageBackup.svelte";
4
5
  import ImmichOnboardingSetupFlow from "./ImmichOnboardingSetupFlow.svelte";
5
6
 
6
7
  type Question = {
7
8
  title: string;
8
- answer: string;
9
+ answer: string | Snippet;
9
10
  };
10
11
 
11
12
  type Props = {
@@ -1,6 +1,7 @@
1
+ import type { Snippet } from "svelte";
1
2
  type Question = {
2
3
  title: string;
3
- answer: string;
4
+ answer: string | Snippet;
4
5
  };
5
6
  type Props = {
6
7
  price: string;
@@ -8,6 +8,14 @@
8
8
  useRepositories,
9
9
  useRepositoryEventHandler,
10
10
  } from "../../../services/repository.service";
11
+ import {
12
+ useRunEventHandler,
13
+ useRunHistory,
14
+ } from "../../../services/runHistory.service";
15
+ import {
16
+ useScheduleEventHandler,
17
+ useSchedules,
18
+ } from "../../../services/schedule.service";
11
19
  import ImmichBackupsCard from "./ImmichBackupsCard.svelte";
12
20
 
13
21
  type Props = {
@@ -18,20 +26,42 @@
18
26
 
19
27
  const { href = "/backups", onclick, class: className }: Props = $props();
20
28
 
29
+ // TODO: this should probably be condensed into one big request - since this loads on every initial Immich page load (in the future)
30
+
21
31
  const integrations = useIntegrations();
22
32
  const repositories = useRepositories();
33
+ const schedules = useSchedules();
23
34
 
24
35
  const { onIntegrationUpdate } = useIntegrationEventHandler();
25
36
  const { onRepositoryCreate, onRepositoryUpdate, onRepositoryDelete } =
26
37
  useRepositoryEventHandler();
38
+ const { onScheduleCreate, onScheduleUpdate, onScheduleDelete } =
39
+ useScheduleEventHandler();
40
+ const { onRunCreate, onRunUpdate } = useRunEventHandler();
27
41
 
28
- const repository = $derived.by(() => {
29
- const integration = integrations.data?.immichIntegration;
42
+ const integration = $derived(integrations.data?.immichIntegration);
30
43
 
31
- return integration
44
+ const repository = $derived(
45
+ integration
32
46
  ? repositories.data?.find((entry) => entry.id === integration.id)
33
- : undefined;
34
- });
47
+ : undefined,
48
+ );
49
+
50
+ const runHistory = useRunHistory(() => repository?.id);
51
+
52
+ const latestBackupRun = $derived(
53
+ runHistory.data?.find(
54
+ (run) => run.type === "backup" || run.type === "schedule",
55
+ ),
56
+ );
57
+
58
+ const paused = $derived(
59
+ Boolean(
60
+ integration &&
61
+ schedules.data?.find((entry) => entry.id === integration.scheduleId)
62
+ ?.paused,
63
+ ),
64
+ );
35
65
 
36
66
  const lastBackup = $derived(repository?.metrics.lastBackup ?? undefined);
37
67
 
@@ -41,8 +71,8 @@
41
71
  ),
42
72
  );
43
73
 
44
- const sizeBytes = $derived(
45
- repository?.meter?.sizeBytes ?? repository?.metrics.sizeBytes,
74
+ const loading = $derived(
75
+ integrations.isLoading || repositories.isLoading || schedules.isLoading,
46
76
  );
47
77
  </script>
48
78
 
@@ -51,16 +81,22 @@
51
81
  {onRepositoryCreate}
52
82
  {onRepositoryUpdate}
53
83
  {onRepositoryDelete}
84
+ {onScheduleCreate}
85
+ {onScheduleUpdate}
86
+ {onScheduleDelete}
87
+ {onRunCreate}
88
+ {onRunUpdate}
54
89
  />
55
90
 
56
- {#if !integrations.isLoading && !repositories.isLoading}
91
+ {#if !loading}
57
92
  <ImmichBackupsCard
58
- configured={Boolean(repository)}
59
- {lastBackup}
60
- {failed}
61
- {sizeBytes}
62
93
  {href}
94
+ {failed}
95
+ {paused}
63
96
  {onclick}
97
+ {lastBackup}
64
98
  class={className}
99
+ configured={Boolean(repository)}
100
+ running={latestBackupRun?.status === "incomplete"}
65
101
  />
66
102
  {/if}
@@ -158,7 +158,7 @@
158
158
  description="Once written, backups can't be removed. This can't be turned off again."
159
159
  readOnly // @immich/ui forces enabled={false} when disabled={true}
160
160
  >
161
- <Switch checked class="grayscale brightness-90" />
161
+ <Switch checked class="cursor-not-allowed opacity-38" />
162
162
  </Field>
163
163
  {:else}
164
164
  <Field
@@ -1,10 +1,11 @@
1
1
  <script lang="ts">
2
2
  import Accordion from "../../ui/Accordion.svelte";
3
3
  import { Stack, Text } from "@immich/ui";
4
+ import type { Snippet } from "svelte";
4
5
 
5
6
  type Question = {
6
7
  title: string;
7
- answer: string;
8
+ answer: string | Snippet;
8
9
  };
9
10
 
10
11
  type Props = {
@@ -20,7 +21,11 @@
20
21
  <Stack gap={0}>
21
22
  {#each questions as { title, answer } (title)}
22
23
  <Accordion {title}>
23
- <Text size="small" color="muted">{answer}</Text>
24
+ {#if typeof answer === "string"}
25
+ <Text size="small" color="muted">{answer}</Text>
26
+ {:else}
27
+ {@render answer()}
28
+ {/if}
24
29
  </Accordion>
25
30
  {/each}
26
31
  </Stack>
@@ -1,6 +1,7 @@
1
+ import type { Snippet } from "svelte";
1
2
  type Question = {
2
3
  title: string;
3
- answer: string;
4
+ answer: string | Snippet;
4
5
  };
5
6
  type Props = {
6
7
  questions: Question[];
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { Card, CardBody, Container, HStack, Stack } from "@immich/ui";
3
+ import type { Snippet } from "svelte";
3
4
  import UpsellBackupFeaturesGrid from "./UpsellBackupFeaturesGrid.svelte";
4
5
  import UpsellExplainFutoBackups from "./UpsellExplainFutoBackups.svelte";
5
6
  import UpsellFrequentlyAskedQuestions from "./UpsellFrequentlyAskedQuestions.svelte";
@@ -7,7 +8,7 @@
7
8
 
8
9
  type Question = {
9
10
  title: string;
10
- answer: string;
11
+ answer: string | Snippet;
11
12
  };
12
13
 
13
14
  type Props = {
@@ -1,6 +1,7 @@
1
+ import type { Snippet } from "svelte";
1
2
  type Question = {
2
3
  title: string;
3
- answer: string;
4
+ answer: string | Snippet;
4
5
  };
5
6
  type Props = {
6
7
  price: string;
@@ -6,14 +6,12 @@
6
6
  AppShellSidebar,
7
7
  Avatar,
8
8
  Button,
9
- HStack,
10
9
  IconButton,
11
10
  Input,
12
11
  Logo,
13
12
  NavbarGroup,
14
13
  NavbarItem,
15
14
  Stack,
16
- Text,
17
15
  ThemeSwitcher,
18
16
  } from "@immich/ui";
19
17
  import {
@@ -39,12 +37,15 @@
39
37
  import { options } from "../../options";
40
38
  import { onDestroy } from "svelte";
41
39
  import ImmichBackupsNavButton from "../integrations/immich/ImmichBackupsNavButton.svelte";
40
+ import ImmichBackupsCard from "../integrations/immich/ImmichBackupsCard.svelte";
42
41
  import MockImmichPhotos from "./immich/MockImmichPhotos.svelte";
42
+ import MockImmichServerStatus from "./immich/MockImmichServerStatus.svelte";
43
43
  import MockImmichPurchaseInfo from "./immich/MockImmichPurchaseInfo.svelte";
44
44
  import MockImmichStorageSpace from "./immich/MockImmichStorageSpace.svelte";
45
45
  import ImmichBackupsPage from "../integrations/immich/ImmichBackupsPage.svelte";
46
46
  import ImmichOnboardingRestoreFlow from "../integrations/immich/ImmichOnboardingRestoreFlow.svelte";
47
47
  import OnEvents from "../util/OnEvents.svelte";
48
+ import MockImmichHideBackupsReminder from "./immich/MockImmichHideBackupsReminder.svelte";
48
49
  import {
49
50
  useIntegrationEventHandler,
50
51
  useIntegrations,
@@ -62,6 +63,7 @@
62
63
  const integrations = useIntegrations();
63
64
  const { onIntegrationUpdate } = useIntegrationEventHandler();
64
65
  const configured = $derived(Boolean(integrations.data?.immichIntegration));
66
+ const twoHoursAgo = new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString();
65
67
 
66
68
  const sampleQuestions = [
67
69
  {
@@ -87,6 +89,10 @@
87
89
 
88
90
  <OnEvents {onIntegrationUpdate} />
89
91
 
92
+ {#snippet hideReminder()}
93
+ <MockImmichHideBackupsReminder />
94
+ {/snippet}
95
+
90
96
  <AppShell class="h-full">
91
97
  <AppShellHeader>
92
98
  <div class="grid grid-cols-[--spacing(64)_auto] items-center w-full py-2">
@@ -253,17 +259,19 @@
253
259
  {/if}
254
260
 
255
261
  <Stack gap={4} class="mt-auto p-4">
256
- <MockImmichStorageSpace onBackups={() => (route = "settings")} />
262
+ <MockImmichStorageSpace>
263
+ <ImmichBackupsCard
264
+ {configured}
265
+ lastBackup={configured ? twoHoursAgo : undefined}
266
+ onclick={() => (route = "settings")}
267
+ />
268
+ </MockImmichStorageSpace>
257
269
 
258
270
  {#if route !== "settings"}
259
271
  <MockImmichPurchaseInfo />
260
272
  {/if}
261
273
 
262
- <HStack gap={2}>
263
- <span class="bg-success size-2 rounded-full"></span>
264
- <Text size="small" class="flex-1">Server Online</Text>
265
- <Text size="small" color="muted">v3.0.3</Text>
266
- </HStack>
274
+ <MockImmichServerStatus />
267
275
  </Stack>
268
276
  </div>
269
277
  </AppShellSidebar>
@@ -277,7 +285,13 @@
277
285
  <ImmichBackupsPage
278
286
  price="$1"
279
287
  includedStorage="50 GB"
280
- questions={sampleQuestions}
288
+ questions={[
289
+ {
290
+ title: "Already back up your library elsewhere?",
291
+ answer: hideReminder,
292
+ },
293
+ ...sampleQuestions,
294
+ ]}
281
295
  />
282
296
  {:else}
283
297
  <MockImmichPhotos />
@@ -0,0 +1,12 @@
1
+ <script lang="ts">
2
+ import { Field, Switch } from "@immich/ui";
3
+
4
+ let hidden = $state(false);
5
+ </script>
6
+
7
+ <Field
8
+ label="Hide backups reminder"
9
+ description="If you already manage backups outside of Immich, you can hide the backup setup reminder."
10
+ >
11
+ <Switch bind:checked={hidden} />
12
+ </Field>
@@ -0,0 +1,3 @@
1
+ declare const MockImmichHideBackupsReminder: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type MockImmichHideBackupsReminder = ReturnType<typeof MockImmichHideBackupsReminder>;
3
+ export default MockImmichHideBackupsReminder;
@@ -0,0 +1,9 @@
1
+ <script lang="ts">
2
+ import { HStack, Text } from "@immich/ui";
3
+ </script>
4
+
5
+ <HStack gap={2}>
6
+ <span class="bg-success size-2 rounded-full"></span>
7
+ <Text size="small" class="flex-1">Server Online</Text>
8
+ <Text size="small" color="muted">v3.0.3</Text>
9
+ </HStack>
@@ -0,0 +1,18 @@
1
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
+ $$bindings?: Bindings;
4
+ } & Exports;
5
+ (internal: unknown, props: {
6
+ $$events?: Events;
7
+ $$slots?: Slots;
8
+ }): Exports & {
9
+ $set?: any;
10
+ $on?: any;
11
+ };
12
+ z_$$bindings?: Bindings;
13
+ }
14
+ declare const MockImmichServerStatus: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
+ [evt: string]: CustomEvent<any>;
16
+ }, {}, {}, string>;
17
+ type MockImmichServerStatus = InstanceType<typeof MockImmichServerStatus>;
18
+ export default MockImmichServerStatus;
@@ -0,0 +1,20 @@
1
+ <script lang="ts">
2
+ import { Stack } from "@immich/ui";
3
+ import type { Snippet } from "svelte";
4
+ import MockImmichServerStatus from "./MockImmichServerStatus.svelte";
5
+ import MockImmichStorageSpace from "./MockImmichStorageSpace.svelte";
6
+
7
+ type Props = {
8
+ children: Snippet;
9
+ };
10
+
11
+ const { children }: Props = $props();
12
+ </script>
13
+
14
+ <Stack gap={4} class="bg-light w-80 shrink-0 rounded-2xl border p-4">
15
+ <MockImmichStorageSpace>
16
+ {@render children()}
17
+ </MockImmichStorageSpace>
18
+
19
+ <MockImmichServerStatus />
20
+ </Stack>
@@ -0,0 +1,7 @@
1
+ import type { Snippet } from "svelte";
2
+ type Props = {
3
+ children: Snippet;
4
+ };
5
+ declare const MockImmichSidebar: import("svelte").Component<Props, {}, "">;
6
+ type MockImmichSidebar = ReturnType<typeof MockImmichSidebar>;
7
+ export default MockImmichSidebar;
@@ -1,37 +1,25 @@
1
1
  <script lang="ts">
2
- import { Icon, Meter, Text } from "@immich/ui";
3
- import { mdiChevronRight, mdiCloudCheckOutline } from "@mdi/js";
2
+ import { ProgressBar, Stack, Text } from "@immich/ui";
3
+ import type { Snippet } from "svelte";
4
4
 
5
5
  type Props = {
6
- onBackups: () => void;
6
+ children?: Snippet;
7
7
  };
8
8
 
9
- const { onBackups }: Props = $props();
9
+ const { children }: Props = $props();
10
10
  </script>
11
11
 
12
- <div class="bg-light-100 overflow-hidden rounded-lg text-sm">
13
- <div class="p-4">
14
- <Meter
12
+ <div class="bg-subtle overflow-hidden rounded-xl border">
13
+ <Stack gap={1} class="p-4 border-b">
14
+ <Text fontWeight="medium">Storage space</Text>
15
+ <Text size="small" color="muted">22.1 GiB of 100 GiB used</Text>
16
+ <ProgressBar
17
+ progress={0.221}
18
+ shape="round"
15
19
  size="tiny"
16
- class="bg-light-200"
17
- containerClass="gap-2 leading-6"
18
- label="Storage"
19
- valueLabel="22.1 GiB of 100 GiB used"
20
- value={0.221}
20
+ class="mt-2 border-none"
21
21
  />
22
- </div>
22
+ </Stack>
23
23
 
24
- <button
25
- type="button"
26
- onclick={onBackups}
27
- class="bg-success-50 text-success-700 flex w-full cursor-pointer items-center gap-2 px-3 py-3 text-start"
28
- >
29
- <Icon icon={mdiCloudCheckOutline} size="1em" class="shrink-0" />
30
-
31
- <Text size="small" color="success" class="flex-1 truncate">
32
- Backed up 2 hours ago
33
- </Text>
34
-
35
- <Icon icon={mdiChevronRight} size="1em" class="shrink-0" />
36
- </button>
24
+ {@render children?.()}
37
25
  </div>
@@ -1,5 +1,6 @@
1
+ import type { Snippet } from "svelte";
1
2
  type Props = {
2
- onBackups: () => void;
3
+ children?: Snippet;
3
4
  };
4
5
  declare const MockImmichStorageSpace: import("svelte").Component<Props, {}, "">;
5
6
  type MockImmichStorageSpace = ReturnType<typeof MockImmichStorageSpace>;
@@ -8,7 +8,7 @@ export declare const runHistoryKeys: {
8
8
  export declare const runKeys: {
9
9
  byId: (id: string) => readonly ["run", string];
10
10
  };
11
- export declare const useRunHistory: (repositoryId: string) => import("@tanstack/svelte-query").CreateQueryResult<sdk.RunDto[], Error>;
11
+ export declare const useRunHistory: (repositoryId: () => string | undefined) => import("@tanstack/svelte-query").CreateQueryResult<sdk.RunDto[], Error>;
12
12
  export declare const useRun: (logId: string) => import("@tanstack/svelte-query").CreateQueryResult<sdk.RunDto, Error>;
13
13
  export declare const useRunEventHandler: () => {
14
14
  onRunCreate(event: SocketEvent<{
@@ -13,8 +13,9 @@ export const runKeys = {
13
13
  byId: (id) => ['run', id],
14
14
  };
15
15
  export const useRunHistory = (repositoryId) => createQuery(() => ({
16
- queryKey: runHistoryKeys.byRepository(repositoryId),
17
- queryFn: () => getRunHistory(repositoryId).then(({ runs }) => runs.toSorted((a, b) => b.start.localeCompare(a.start))),
16
+ queryKey: runHistoryKeys.byRepository(repositoryId() ?? ''),
17
+ queryFn: () => getRunHistory(repositoryId()).then(({ runs }) => runs.toSorted((a, b) => b.start.localeCompare(a.start))),
18
+ enabled: repositoryId() !== undefined,
18
19
  }), () => queryClient);
19
20
  export const useRun = (logId) => createQuery(() => ({
20
21
  queryKey: runKeys.byId(logId),
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@futo-org/backups-orchestrator-ui",
3
3
  "repository": "https://github.com/immich-app/yucca",
4
4
  "description": "Backups orchestrator (UI library)",
5
- "version": "0.28.0",
5
+ "version": "0.30.1",
6
6
  "license": "Source First License 1.1",
7
7
  "files": [
8
8
  "dist",
@@ -64,7 +64,7 @@
64
64
  "lodash.debounce": "^4.0.8",
65
65
  "luxon": "^3.7.2",
66
66
  "socket.io-client": "^4.8.3",
67
- "@futo-org/backups-api-client": "^0.28.0"
67
+ "@futo-org/backups-api-client": "^0.30.1"
68
68
  },
69
69
  "scripts": {
70
70
  "dev": "vite dev --port 5174",