@futo-org/backups-orchestrator-ui 0.30.0 → 0.31.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 +1 -1
- package/dist/components/backups/dialogs/RollbackSnapshotModal.svelte +55 -0
- package/dist/components/backups/dialogs/RollbackSnapshotModal.svelte.d.ts +8 -0
- package/dist/components/integrations/immich/ImmichBackupsAdminNavButton.svelte +52 -0
- package/dist/components/integrations/immich/ImmichBackupsAdminNavButton.svelte.d.ts +6 -0
- package/dist/components/integrations/immich/ImmichBackupsSidebarItem.svelte +79 -14
- package/dist/components/integrations/immich/ImmichBackupsSidebarItem.svelte.d.ts +1 -3
- package/dist/components/test/ImmichTestUi.svelte +4 -24
- package/dist/components/ui/PathListField.svelte +29 -28
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/services/snapshot.service.js +7 -4
- package/package.json +2 -2
- package/dist/components/integrations/immich/ImmichBackupsCard.svelte +0 -109
- package/dist/components/integrations/immich/ImmichBackupsCard.svelte.d.ts +0 -13
- package/dist/components/integrations/immich/ImmichBackupsNavButton.svelte +0 -55
- package/dist/components/integrations/immich/ImmichBackupsNavButton.svelte.d.ts +0 -10
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
{backend.description ?? BackendDescriptions[backend.type]}
|
|
65
65
|
|
|
66
66
|
{#snippet trailing()}
|
|
67
|
-
<Badge color={online ? "
|
|
67
|
+
<Badge color={online ? "success" : "danger"} size="small">
|
|
68
68
|
{#if !backend.isOnline}
|
|
69
69
|
Offline
|
|
70
70
|
{:else if repositoryBackend}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { useRollbackSnapshot } from "../../../services/snapshot.service";
|
|
3
|
+
import {
|
|
4
|
+
Button,
|
|
5
|
+
HStack,
|
|
6
|
+
Modal,
|
|
7
|
+
ModalBody,
|
|
8
|
+
ModalFooter,
|
|
9
|
+
Stack,
|
|
10
|
+
Text,
|
|
11
|
+
} from "@immich/ui";
|
|
12
|
+
|
|
13
|
+
type Props = {
|
|
14
|
+
repository: string;
|
|
15
|
+
snapshot: string;
|
|
16
|
+
onClose: () => void;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const { repository, snapshot, onClose }: Props = $props();
|
|
20
|
+
|
|
21
|
+
const mutation = useRollbackSnapshot();
|
|
22
|
+
|
|
23
|
+
const onConfirm = () =>
|
|
24
|
+
mutation.mutate(
|
|
25
|
+
{ repositoryId: repository, snapshotId: snapshot },
|
|
26
|
+
{ onSuccess: () => onClose() },
|
|
27
|
+
);
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<Modal title="Rollback to this snapshot?" size="small" {onClose}>
|
|
31
|
+
<ModalBody>
|
|
32
|
+
<Stack gap={4}>
|
|
33
|
+
<Text>
|
|
34
|
+
Your instance will return to how it was when this snapshot was taken.
|
|
35
|
+
</Text>
|
|
36
|
+
|
|
37
|
+
<Stack gap={2}>
|
|
38
|
+
<Text>This will:</Text>
|
|
39
|
+
<ul class="list-disc ps-6">
|
|
40
|
+
<li><Text>Restore files from the snapshot</Text></li>
|
|
41
|
+
<li><Text>Restore the Immich database</Text></li>
|
|
42
|
+
<li><Text>Restart the server during rollback</Text></li>
|
|
43
|
+
</ul>
|
|
44
|
+
</Stack>
|
|
45
|
+
</Stack>
|
|
46
|
+
</ModalBody>
|
|
47
|
+
<ModalFooter>
|
|
48
|
+
<HStack>
|
|
49
|
+
<Button color="danger" loading={mutation.isPending} onclick={onConfirm}>
|
|
50
|
+
Confirm rollback
|
|
51
|
+
</Button>
|
|
52
|
+
<Button variant="ghost" onclick={onClose}>Cancel</Button>
|
|
53
|
+
</HStack>
|
|
54
|
+
</ModalFooter>
|
|
55
|
+
</Modal>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
type Props = {
|
|
2
|
+
repository: string;
|
|
3
|
+
snapshot: string;
|
|
4
|
+
onClose: () => void;
|
|
5
|
+
};
|
|
6
|
+
declare const RollbackSnapshotModal: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type RollbackSnapshotModal = ReturnType<typeof RollbackSnapshotModal>;
|
|
8
|
+
export default RollbackSnapshotModal;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import OnEvents from "../../util/OnEvents.svelte";
|
|
3
|
+
import {
|
|
4
|
+
useIntegrationEventHandler,
|
|
5
|
+
useIntegrations,
|
|
6
|
+
} from "../../../services/integrations.service";
|
|
7
|
+
import { page } from "$app/state";
|
|
8
|
+
import { Icon, Text } from "@immich/ui";
|
|
9
|
+
import { mdiCloudUpload, mdiCloudUploadOutline } from "@mdi/js";
|
|
10
|
+
|
|
11
|
+
type Props = {
|
|
12
|
+
href: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const { href }: Props = $props();
|
|
16
|
+
|
|
17
|
+
const active = $derived(page.url.pathname.startsWith(href));
|
|
18
|
+
|
|
19
|
+
const integrations = useIntegrations();
|
|
20
|
+
const { onIntegrationUpdate } = useIntegrationEventHandler();
|
|
21
|
+
|
|
22
|
+
const configured = $derived(
|
|
23
|
+
integrations.data ? Boolean(integrations.data.immichIntegration) : true,
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const shell = $derived(
|
|
27
|
+
configured
|
|
28
|
+
? `hover:bg-subtle hover:text-primary flex w-full cursor-pointer place-items-center gap-4 rounded-e-full ps-5 py-3 text-start ${active ? "bg-primary/10 text-primary" : ""}`
|
|
29
|
+
: "border-primary/40 from-white via-blue-100 to-purple-100 dark:from-transparent dark:via-blue-500/10 dark:to-purple-500/15 flex w-full cursor-pointer place-items-center gap-4 rounded-xl border bg-gradient-to-r px-4 py-2.5 text-start",
|
|
30
|
+
);
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<OnEvents {onIntegrationUpdate} />
|
|
34
|
+
|
|
35
|
+
<div class={configured ? "" : "mx-4 mb-2"}>
|
|
36
|
+
<a {href} class={shell}>
|
|
37
|
+
<Icon
|
|
38
|
+
icon={configured ? mdiCloudUploadOutline : mdiCloudUpload}
|
|
39
|
+
size="1.375em"
|
|
40
|
+
class="{configured ? '' : 'text-primary'} shrink-0"
|
|
41
|
+
/>
|
|
42
|
+
|
|
43
|
+
<Text
|
|
44
|
+
size="small"
|
|
45
|
+
fontWeight="medium"
|
|
46
|
+
color={configured && !active ? undefined : "primary"}
|
|
47
|
+
class="flex-1 truncate"
|
|
48
|
+
>
|
|
49
|
+
{configured ? "Backups" : "Set up Backups"}
|
|
50
|
+
</Text>
|
|
51
|
+
</a>
|
|
52
|
+
</div>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import OnEvents from "../../util/OnEvents.svelte";
|
|
3
|
+
import RelativeTime from "../../util/RelativeTime.svelte";
|
|
3
4
|
import {
|
|
4
5
|
useIntegrationEventHandler,
|
|
5
6
|
useIntegrations,
|
|
@@ -16,15 +17,22 @@
|
|
|
16
17
|
useScheduleEventHandler,
|
|
17
18
|
useSchedules,
|
|
18
19
|
} from "../../../services/schedule.service";
|
|
19
|
-
import
|
|
20
|
+
import { HStack, Icon, Text } from "@immich/ui";
|
|
21
|
+
import {
|
|
22
|
+
mdiChevronRight,
|
|
23
|
+
mdiCloudAlertOutline,
|
|
24
|
+
mdiCloudCheckVariantOutline,
|
|
25
|
+
mdiCloudOffOutline,
|
|
26
|
+
mdiCloudUploadOutline,
|
|
27
|
+
} from "@mdi/js";
|
|
28
|
+
|
|
29
|
+
type Color = "primary" | "success" | "warning" | "danger";
|
|
20
30
|
|
|
21
31
|
type Props = {
|
|
22
|
-
href
|
|
23
|
-
onclick?: () => void;
|
|
24
|
-
class?: string;
|
|
32
|
+
href: string;
|
|
25
33
|
};
|
|
26
34
|
|
|
27
|
-
const { href
|
|
35
|
+
const { href }: Props = $props();
|
|
28
36
|
|
|
29
37
|
// TODO: this should probably be condensed into one big request - since this loads on every initial Immich page load (in the future)
|
|
30
38
|
|
|
@@ -74,6 +82,36 @@
|
|
|
74
82
|
const loading = $derived(
|
|
75
83
|
integrations.isLoading || repositories.isLoading || schedules.isLoading,
|
|
76
84
|
);
|
|
85
|
+
|
|
86
|
+
const configured = $derived(Boolean(repository));
|
|
87
|
+
const running = $derived(latestBackupRun?.status === "incomplete");
|
|
88
|
+
|
|
89
|
+
const status = $derived.by(() => {
|
|
90
|
+
if (running) {
|
|
91
|
+
return { color: "primary", icon: mdiCloudUploadOutline } as const;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (failed) {
|
|
95
|
+
return { color: "danger", icon: mdiCloudOffOutline } as const;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (paused) {
|
|
99
|
+
return { color: "warning", icon: mdiCloudAlertOutline } as const;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!configured || !lastBackup) {
|
|
103
|
+
return { color: "warning", icon: mdiCloudOffOutline } as const;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return { color: "success", icon: mdiCloudCheckVariantOutline } as const;
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const tints: Record<Color, string> = {
|
|
110
|
+
primary: "bg-primary-50 text-primary",
|
|
111
|
+
success: "bg-success-50 text-success-700",
|
|
112
|
+
warning: "bg-warning-50 text-warning-800",
|
|
113
|
+
danger: "bg-danger-50 text-danger-700",
|
|
114
|
+
};
|
|
77
115
|
</script>
|
|
78
116
|
|
|
79
117
|
<OnEvents
|
|
@@ -89,14 +127,41 @@
|
|
|
89
127
|
/>
|
|
90
128
|
|
|
91
129
|
{#if !loading}
|
|
92
|
-
<
|
|
130
|
+
<a
|
|
93
131
|
{href}
|
|
94
|
-
{
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
132
|
+
class="flex w-full cursor-pointer items-center gap-2 px-3 py-3 text-start text-sm {tints[
|
|
133
|
+
status.color
|
|
134
|
+
]}"
|
|
135
|
+
>
|
|
136
|
+
<Icon color={status.color} icon={status.icon} size="1.25em" class="shrink-0" />
|
|
137
|
+
|
|
138
|
+
<Text size="tiny" color={status.color} class="flex-1 truncate">
|
|
139
|
+
{#if running}
|
|
140
|
+
Backing up now
|
|
141
|
+
{:else if failed}
|
|
142
|
+
Backup failed
|
|
143
|
+
{:else if paused}
|
|
144
|
+
Backups paused
|
|
145
|
+
{:else if !configured}
|
|
146
|
+
Not backed up
|
|
147
|
+
{:else if lastBackup}
|
|
148
|
+
Last backup <RelativeTime time={lastBackup} />
|
|
149
|
+
{:else}
|
|
150
|
+
Finish setting up backups
|
|
151
|
+
{/if}
|
|
152
|
+
</Text>
|
|
153
|
+
|
|
154
|
+
<HStack gap={0}>
|
|
155
|
+
{#if !configured}
|
|
156
|
+
<Text size="tiny" color="primary" class="shrink-0">Set up</Text>
|
|
157
|
+
{/if}
|
|
158
|
+
|
|
159
|
+
<Icon
|
|
160
|
+
color={configured ? status.color : "primary5"}
|
|
161
|
+
icon={mdiChevronRight}
|
|
162
|
+
size="1.25em"
|
|
163
|
+
class="shrink-0"
|
|
164
|
+
/>
|
|
165
|
+
</HStack>
|
|
166
|
+
</a>
|
|
102
167
|
{/if}
|
|
@@ -36,20 +36,15 @@
|
|
|
36
36
|
} from "@mdi/js";
|
|
37
37
|
import { options } from "../../options";
|
|
38
38
|
import { onDestroy } from "svelte";
|
|
39
|
-
import
|
|
40
|
-
import
|
|
39
|
+
import ImmichBackupsAdminNavButton from "../integrations/immich/ImmichBackupsAdminNavButton.svelte";
|
|
40
|
+
import ImmichBackupsSidebarItem from "../integrations/immich/ImmichBackupsSidebarItem.svelte";
|
|
41
41
|
import MockImmichPhotos from "./immich/MockImmichPhotos.svelte";
|
|
42
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
|
-
import OnEvents from "../util/OnEvents.svelte";
|
|
48
47
|
import MockImmichHideBackupsReminder from "./immich/MockImmichHideBackupsReminder.svelte";
|
|
49
|
-
import {
|
|
50
|
-
useIntegrationEventHandler,
|
|
51
|
-
useIntegrations,
|
|
52
|
-
} from "../../services/integrations.service";
|
|
53
48
|
|
|
54
49
|
type Props = {
|
|
55
50
|
onExit: () => void;
|
|
@@ -60,11 +55,6 @@
|
|
|
60
55
|
|
|
61
56
|
let route = $state<"photos" | "settings">("photos");
|
|
62
57
|
|
|
63
|
-
const integrations = useIntegrations();
|
|
64
|
-
const { onIntegrationUpdate } = useIntegrationEventHandler();
|
|
65
|
-
const configured = $derived(Boolean(integrations.data?.immichIntegration));
|
|
66
|
-
const twoHoursAgo = new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString();
|
|
67
|
-
|
|
68
58
|
const sampleQuestions = [
|
|
69
59
|
{
|
|
70
60
|
title: "Is FUTO Backups the same as an Immich product key?",
|
|
@@ -87,8 +77,6 @@
|
|
|
87
77
|
setProvider(orchestrationApiProvider);
|
|
88
78
|
</script>
|
|
89
79
|
|
|
90
|
-
<OnEvents {onIntegrationUpdate} />
|
|
91
|
-
|
|
92
80
|
{#snippet hideReminder()}
|
|
93
81
|
<MockImmichHideBackupsReminder />
|
|
94
82
|
{/snippet}
|
|
@@ -203,11 +191,7 @@
|
|
|
203
191
|
<AppShellSidebar class="relative">
|
|
204
192
|
<div class="flex h-full flex-col pt-4 pr-2">
|
|
205
193
|
{#if route === "settings"}
|
|
206
|
-
<
|
|
207
|
-
{configured}
|
|
208
|
-
active
|
|
209
|
-
onclick={() => {}}
|
|
210
|
-
/>
|
|
194
|
+
<ImmichBackupsAdminNavButton href="#" />
|
|
211
195
|
|
|
212
196
|
<NavbarItem
|
|
213
197
|
href="#"
|
|
@@ -260,11 +244,7 @@
|
|
|
260
244
|
|
|
261
245
|
<Stack gap={4} class="mt-auto p-4">
|
|
262
246
|
<MockImmichStorageSpace>
|
|
263
|
-
<
|
|
264
|
-
{configured}
|
|
265
|
-
lastBackup={configured ? twoHoursAgo : undefined}
|
|
266
|
-
onclick={() => (route = "settings")}
|
|
267
|
-
/>
|
|
247
|
+
<ImmichBackupsSidebarItem href="#" />
|
|
268
248
|
</MockImmichStorageSpace>
|
|
269
249
|
|
|
270
250
|
{#if route !== "settings"}
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import StackList from "./StackList.svelte";
|
|
3
|
-
import StackListItem from "./StackListItem.svelte";
|
|
4
2
|
import type { FilesystemListingResponseDto } from "../../fetch-client";
|
|
5
3
|
import {
|
|
6
4
|
Button,
|
|
5
|
+
Heading,
|
|
6
|
+
HStack,
|
|
7
7
|
IconButton,
|
|
8
8
|
modalManager,
|
|
9
|
+
Stack,
|
|
9
10
|
Text,
|
|
10
11
|
} from "@immich/ui";
|
|
11
12
|
import { mdiClose } from "@mdi/js";
|
|
12
13
|
import type { Snippet } from "svelte";
|
|
13
14
|
import type { SvelteSet } from "svelte/reactivity";
|
|
14
15
|
import PathPickerModal from "./PathPickerModal.svelte";
|
|
16
|
+
import StackList from "./StackList.svelte";
|
|
17
|
+
import StackListItem from "./StackListItem.svelte";
|
|
15
18
|
|
|
16
19
|
type Props = {
|
|
17
20
|
paths: SvelteSet<string>;
|
|
@@ -51,36 +54,34 @@
|
|
|
51
54
|
});
|
|
52
55
|
</script>
|
|
53
56
|
|
|
54
|
-
<
|
|
55
|
-
{
|
|
56
|
-
{@render label?.()}
|
|
57
|
-
{/snippet}
|
|
57
|
+
<Stack>
|
|
58
|
+
<Heading size="tiny">{@render label?.()}</Heading>
|
|
58
59
|
|
|
59
60
|
{#if paths.size > 0}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
<
|
|
61
|
+
<StackList>
|
|
62
|
+
{#each [...paths] as path (path)}
|
|
63
|
+
<StackListItem>
|
|
64
|
+
<Text class="grow truncate" title={path}>{path}</Text>
|
|
63
65
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
66
|
+
{#snippet trailing()}
|
|
67
|
+
<IconButton
|
|
68
|
+
icon={mdiClose}
|
|
69
|
+
aria-label="Remove"
|
|
70
|
+
size="tiny"
|
|
71
|
+
variant="ghost"
|
|
72
|
+
onclick={() => paths.delete(path)}
|
|
73
|
+
/>
|
|
74
|
+
{/snippet}
|
|
75
|
+
</StackListItem>
|
|
76
|
+
{/each}
|
|
77
|
+
</StackList>
|
|
75
78
|
{:else if empty}
|
|
76
|
-
<
|
|
79
|
+
<HStack>
|
|
77
80
|
<Text color="secondary">{@render empty()}</Text>
|
|
78
|
-
</
|
|
81
|
+
</HStack>
|
|
79
82
|
{/if}
|
|
80
83
|
|
|
81
|
-
<
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
</StackListItem>
|
|
86
|
-
</StackList>
|
|
84
|
+
<Button class="w-fit" size="small" variant="ghost" onclick={openPicker}>
|
|
85
|
+
{paths.size > 0 ? addLabel : manageLabel}
|
|
86
|
+
</Button>
|
|
87
|
+
</Stack>
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { default as BackendsList } from './components/backends/BackendsList.svel
|
|
|
2
2
|
export { default as BackupsList } from './components/backups/BackupsList.svelte';
|
|
3
3
|
export { default as ViewStatusModal } from './components/backups/dialogs/ViewStatusModal.svelte';
|
|
4
4
|
export { default as Dashboard } from './components/dashboard/Dashboard.svelte';
|
|
5
|
-
export { default as
|
|
5
|
+
export { default as ImmichBackupsAdminNavButton } from './components/integrations/immich/ImmichBackupsAdminNavButton.svelte';
|
|
6
6
|
export { default as ImmichBackupsPage } from './components/integrations/immich/ImmichBackupsPage.svelte';
|
|
7
7
|
export { default as ImmichBackupsSidebarItem } from './components/integrations/immich/ImmichBackupsSidebarItem.svelte';
|
|
8
8
|
export { default as ImmichManageBackup } from './components/integrations/immich/ImmichManageBackup.svelte';
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ export { default as BackendsList } from './components/backends/BackendsList.svel
|
|
|
2
2
|
export { default as BackupsList } from './components/backups/BackupsList.svelte';
|
|
3
3
|
export { default as ViewStatusModal } from './components/backups/dialogs/ViewStatusModal.svelte';
|
|
4
4
|
export { default as Dashboard } from './components/dashboard/Dashboard.svelte';
|
|
5
|
-
export { default as
|
|
5
|
+
export { default as ImmichBackupsAdminNavButton } from './components/integrations/immich/ImmichBackupsAdminNavButton.svelte';
|
|
6
6
|
export { default as ImmichBackupsPage } from './components/integrations/immich/ImmichBackupsPage.svelte';
|
|
7
7
|
export { default as ImmichBackupsSidebarItem } from './components/integrations/immich/ImmichBackupsSidebarItem.svelte';
|
|
8
8
|
export { default as ImmichManageBackup } from './components/integrations/immich/ImmichManageBackup.svelte';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { sdk } from '..';
|
|
2
2
|
import RestoreSnapshotModal from '../components/backups/dialogs/RestoreSnapshotModal.svelte';
|
|
3
|
+
import RollbackSnapshotModal from '../components/backups/dialogs/RollbackSnapshotModal.svelte';
|
|
3
4
|
import { SocketEvent } from '../events';
|
|
4
5
|
import { getSnapshots, } from '../fetch-client';
|
|
5
6
|
import { queryClient } from '../query-client';
|
|
@@ -83,9 +84,8 @@ export const handleGetSnapshotListing = async (id, snapshotId, path) => {
|
|
|
83
84
|
};
|
|
84
85
|
export const getSnapshotActions = (repositoryId, snapshot, immich) => {
|
|
85
86
|
const removeSnapshot = useRemoveSnapshot(repositoryId);
|
|
86
|
-
const rollback = useRollbackSnapshot();
|
|
87
87
|
const Restore = {
|
|
88
|
-
title: 'Restore',
|
|
88
|
+
title: 'Restore files',
|
|
89
89
|
icon: mdiBackupRestore,
|
|
90
90
|
onAction: () => void modalManager.open(RestoreSnapshotModal, {
|
|
91
91
|
repository: repositoryId,
|
|
@@ -93,9 +93,12 @@ export const getSnapshotActions = (repositoryId, snapshot, immich) => {
|
|
|
93
93
|
}),
|
|
94
94
|
};
|
|
95
95
|
const Rollback = {
|
|
96
|
-
title: 'Rollback',
|
|
96
|
+
title: 'Rollback snapshot',
|
|
97
97
|
icon: mdiHistory,
|
|
98
|
-
onAction: () =>
|
|
98
|
+
onAction: () => void modalManager.open(RollbackSnapshotModal, {
|
|
99
|
+
repository: repositoryId,
|
|
100
|
+
snapshot: snapshot.id,
|
|
101
|
+
}),
|
|
99
102
|
$if: () => immich === true,
|
|
100
103
|
};
|
|
101
104
|
const Delete = {
|
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.
|
|
5
|
+
"version": "0.31.0",
|
|
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.
|
|
67
|
+
"@futo-org/backups-api-client": "^0.31.0"
|
|
68
68
|
},
|
|
69
69
|
"scripts": {
|
|
70
70
|
"dev": "vite dev --port 5174",
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import RelativeTime from "../../util/RelativeTime.svelte";
|
|
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";
|
|
13
|
-
|
|
14
|
-
type Props = {
|
|
15
|
-
configured: boolean;
|
|
16
|
-
lastBackup?: string;
|
|
17
|
-
failed?: boolean;
|
|
18
|
-
paused?: boolean;
|
|
19
|
-
running?: boolean;
|
|
20
|
-
href?: string;
|
|
21
|
-
onclick?: () => void;
|
|
22
|
-
class?: string;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const {
|
|
26
|
-
configured,
|
|
27
|
-
lastBackup,
|
|
28
|
-
failed = false,
|
|
29
|
-
paused = false,
|
|
30
|
-
running = false,
|
|
31
|
-
href,
|
|
32
|
-
onclick,
|
|
33
|
-
class: className,
|
|
34
|
-
}: Props = $props();
|
|
35
|
-
|
|
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
|
-
);
|
|
66
|
-
</script>
|
|
67
|
-
|
|
68
|
-
{#snippet body()}
|
|
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>
|
|
91
|
-
|
|
92
|
-
{#if !configured}
|
|
93
|
-
<Text size="small" color="primary" fontWeight="medium" class="shrink-0">
|
|
94
|
-
Set up
|
|
95
|
-
</Text>
|
|
96
|
-
{/if}
|
|
97
|
-
|
|
98
|
-
<Icon icon={mdiChevronRight} size="1.25em" class="shrink-0" />
|
|
99
|
-
{/snippet}
|
|
100
|
-
|
|
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}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
type Props = {
|
|
2
|
-
configured: boolean;
|
|
3
|
-
lastBackup?: string;
|
|
4
|
-
failed?: boolean;
|
|
5
|
-
paused?: boolean;
|
|
6
|
-
running?: boolean;
|
|
7
|
-
href?: string;
|
|
8
|
-
onclick?: () => void;
|
|
9
|
-
class?: string;
|
|
10
|
-
};
|
|
11
|
-
declare const ImmichBackupsCard: import("svelte").Component<Props, {}, "">;
|
|
12
|
-
type ImmichBackupsCard = ReturnType<typeof ImmichBackupsCard>;
|
|
13
|
-
export default ImmichBackupsCard;
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { Icon, Text } from "@immich/ui";
|
|
3
|
-
import { mdiCloudUpload, mdiCloudUploadOutline } from "@mdi/js";
|
|
4
|
-
|
|
5
|
-
type Props = {
|
|
6
|
-
configured: boolean;
|
|
7
|
-
active?: boolean;
|
|
8
|
-
href?: string;
|
|
9
|
-
onclick?: () => void;
|
|
10
|
-
class?: string;
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
const {
|
|
14
|
-
configured,
|
|
15
|
-
active = false,
|
|
16
|
-
href,
|
|
17
|
-
onclick,
|
|
18
|
-
class: className,
|
|
19
|
-
}: Props = $props();
|
|
20
|
-
|
|
21
|
-
const shell = $derived(
|
|
22
|
-
configured
|
|
23
|
-
? `hover:bg-subtle hover:text-primary flex w-full cursor-pointer place-items-center gap-4 rounded-e-full ps-5 py-3 text-start ${active ? "bg-primary/10 text-primary" : ""}`
|
|
24
|
-
: "border-primary/40 from-white via-blue-100 to-purple-100 dark:from-transparent dark:via-blue-500/10 dark:to-purple-500/15 flex w-full cursor-pointer place-items-center gap-4 rounded-xl border bg-gradient-to-r px-4 py-2.5 text-start",
|
|
25
|
-
);
|
|
26
|
-
</script>
|
|
27
|
-
|
|
28
|
-
{#snippet body()}
|
|
29
|
-
<Icon
|
|
30
|
-
icon={configured ? mdiCloudUploadOutline : mdiCloudUpload}
|
|
31
|
-
size="1.375em"
|
|
32
|
-
class="{configured ? '' : 'text-primary'} shrink-0"
|
|
33
|
-
/>
|
|
34
|
-
|
|
35
|
-
<Text
|
|
36
|
-
size="small"
|
|
37
|
-
fontWeight="medium"
|
|
38
|
-
color={configured && !active ? undefined : "primary"}
|
|
39
|
-
class="flex-1 truncate"
|
|
40
|
-
>
|
|
41
|
-
{configured ? "Backups" : "Set up Backups"}
|
|
42
|
-
</Text>
|
|
43
|
-
{/snippet}
|
|
44
|
-
|
|
45
|
-
<div class="{configured ? '' : 'mx-4 mb-2'} {className ?? ''}">
|
|
46
|
-
{#if href}
|
|
47
|
-
<a {href} {onclick} class={shell}>
|
|
48
|
-
{@render body()}
|
|
49
|
-
</a>
|
|
50
|
-
{:else}
|
|
51
|
-
<button type="button" {onclick} class={shell}>
|
|
52
|
-
{@render body()}
|
|
53
|
-
</button>
|
|
54
|
-
{/if}
|
|
55
|
-
</div>
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
type Props = {
|
|
2
|
-
configured: boolean;
|
|
3
|
-
active?: boolean;
|
|
4
|
-
href?: string;
|
|
5
|
-
onclick?: () => void;
|
|
6
|
-
class?: string;
|
|
7
|
-
};
|
|
8
|
-
declare const ImmichBackupsNavButton: import("svelte").Component<Props, {}, "">;
|
|
9
|
-
type ImmichBackupsNavButton = ReturnType<typeof ImmichBackupsNavButton>;
|
|
10
|
-
export default ImmichBackupsNavButton;
|