@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.
- package/LICENSE +41 -0
- package/dist/components/backends/BackendItem.svelte +64 -0
- package/dist/components/backends/BackendItem.svelte.d.ts +10 -0
- package/dist/components/backends/BackendsList.svelte +73 -0
- package/dist/components/backends/BackendsList.svelte.d.ts +7 -0
- package/dist/components/backends/CreateLocalBackend.svelte +46 -0
- package/dist/components/backends/CreateLocalBackend.svelte.d.ts +7 -0
- package/dist/components/backends/OAuthDeviceFlow.svelte +78 -0
- package/dist/components/backends/OAuthDeviceFlow.svelte.d.ts +9 -0
- package/dist/components/backups/BackupItem.svelte +59 -0
- package/dist/components/backups/BackupItem.svelte.d.ts +7 -0
- package/dist/components/backups/BackupsList.svelte +82 -0
- package/dist/components/backups/BackupsList.svelte.d.ts +8 -0
- package/dist/components/backups/dialogs/ConfigureRepositoryModal.svelte +102 -0
- package/dist/components/backups/dialogs/ConfigureRepositoryModal.svelte.d.ts +8 -0
- package/dist/components/backups/dialogs/CreateRepositoryModal.svelte +59 -0
- package/dist/components/backups/dialogs/CreateRepositoryModal.svelte.d.ts +6 -0
- package/dist/components/backups/dialogs/ImportRepositoryModal.svelte +62 -0
- package/dist/components/backups/dialogs/ImportRepositoryModal.svelte.d.ts +8 -0
- package/dist/components/backups/dialogs/RestoreSnapshotModal.svelte +109 -0
- package/dist/components/backups/dialogs/RestoreSnapshotModal.svelte.d.ts +8 -0
- package/dist/components/backups/dialogs/ViewLogModal.svelte +208 -0
- package/dist/components/backups/dialogs/ViewLogModal.svelte.d.ts +7 -0
- package/dist/components/backups/metrics-history/MetricsHistoryModal.svelte +166 -0
- package/dist/components/backups/metrics-history/MetricsHistoryModal.svelte.d.ts +8 -0
- package/dist/components/backups/run-history/RepositoryRunHistory.svelte +39 -0
- package/dist/components/backups/run-history/RepositoryRunHistory.svelte.d.ts +7 -0
- package/dist/components/backups/run-history/RepositoryRunHistoryItem.svelte +45 -0
- package/dist/components/backups/run-history/RepositoryRunHistoryItem.svelte.d.ts +7 -0
- package/dist/components/backups/run-history/RunHistoryModal.svelte +18 -0
- package/dist/components/backups/run-history/RunHistoryModal.svelte.d.ts +8 -0
- package/dist/components/backups/snapshots-list/RepositorySnapshotsList.svelte +53 -0
- package/dist/components/backups/snapshots-list/RepositorySnapshotsList.svelte.d.ts +7 -0
- package/dist/components/backups/snapshots-list/RepositorySnapshotsListItem.svelte +41 -0
- package/dist/components/backups/snapshots-list/RepositorySnapshotsListItem.svelte.d.ts +8 -0
- package/dist/components/backups/snapshots-list/SnapshotsListModal.svelte +18 -0
- package/dist/components/backups/snapshots-list/SnapshotsListModal.svelte.d.ts +8 -0
- package/dist/components/dashboard/Dashboard.svelte +52 -0
- package/dist/components/dashboard/Dashboard.svelte.d.ts +9 -0
- package/dist/components/dashboard/DashboardAvgBackupTime.svelte +34 -0
- package/dist/components/dashboard/DashboardAvgBackupTime.svelte.d.ts +7 -0
- package/dist/components/dashboard/DashboardBackupHealth.svelte +91 -0
- package/dist/components/dashboard/DashboardBackupHealth.svelte.d.ts +9 -0
- package/dist/components/dashboard/DashboardCurrentUsage.svelte +10 -0
- package/dist/components/dashboard/DashboardCurrentUsage.svelte.d.ts +18 -0
- package/dist/components/dashboard/DashboardDailyBackupTime.svelte +31 -0
- package/dist/components/dashboard/DashboardDailyBackupTime.svelte.d.ts +7 -0
- package/dist/components/dashboard/DashboardInstall.svelte +15 -0
- package/dist/components/dashboard/DashboardInstall.svelte.d.ts +18 -0
- package/dist/components/dashboard/DashboardRecentBackups.svelte +104 -0
- package/dist/components/dashboard/DashboardRecentBackups.svelte.d.ts +8 -0
- package/dist/components/dashboard/DashboardTotalStored.svelte +27 -0
- package/dist/components/dashboard/DashboardTotalStored.svelte.d.ts +7 -0
- package/dist/components/integrations/immich/ImmichBackupsPage.svelte +14 -0
- package/dist/components/integrations/immich/ImmichBackupsPage.svelte.d.ts +6 -0
- package/dist/components/integrations/immich/ImmichConfigureBackup.svelte +402 -0
- package/dist/components/integrations/immich/ImmichConfigureBackup.svelte.d.ts +9 -0
- package/dist/components/integrations/immich/ImmichConfirmDefaultBackup.svelte +80 -0
- package/dist/components/integrations/immich/ImmichConfirmDefaultBackup.svelte.d.ts +8 -0
- package/dist/components/integrations/immich/ImmichManageBackup.svelte +77 -0
- package/dist/components/integrations/immich/ImmichManageBackup.svelte.d.ts +3 -0
- package/dist/components/integrations/immich/ImmichManageBackupOverview.svelte +100 -0
- package/dist/components/integrations/immich/ImmichManageBackupOverview.svelte.d.ts +8 -0
- package/dist/components/integrations/immich/ImmichOnboardingRestoreFlow.svelte +75 -0
- package/dist/components/integrations/immich/ImmichOnboardingRestoreFlow.svelte.d.ts +7 -0
- package/dist/components/integrations/immich/ImmichOnboardingSetupFlow.svelte +113 -0
- package/dist/components/integrations/immich/ImmichOnboardingSetupFlow.svelte.d.ts +8 -0
- package/dist/components/onboarding/OnboardingGate.svelte +48 -0
- package/dist/components/onboarding/OnboardingGate.svelte.d.ts +9 -0
- package/dist/components/onboarding/RecoveryKeyDisplay.svelte +103 -0
- package/dist/components/onboarding/RecoveryKeyDisplay.svelte.d.ts +6 -0
- package/dist/components/onboarding/SampleOnboarding.svelte +98 -0
- package/dist/components/onboarding/SampleOnboarding.svelte.d.ts +9 -0
- package/dist/components/onboarding/dialogs/BackupsRecoveryKeyModal.svelte +43 -0
- package/dist/components/onboarding/dialogs/BackupsRecoveryKeyModal.svelte.d.ts +6 -0
- package/dist/components/onboarding/restore-point-flow/RestorePointFlow.svelte +96 -0
- package/dist/components/onboarding/restore-point-flow/RestorePointFlow.svelte.d.ts +8 -0
- package/dist/components/onboarding/restore-point-flow/RestorePointFlow2SelectSnapshot.svelte +83 -0
- package/dist/components/onboarding/restore-point-flow/RestorePointFlow2SelectSnapshot.svelte.d.ts +9 -0
- package/dist/components/onboarding/restore-point-flow/RestorePointFlow3ConfirmRestore.svelte +118 -0
- package/dist/components/onboarding/restore-point-flow/RestorePointFlow3ConfirmRestore.svelte.d.ts +10 -0
- package/dist/components/onboarding/restore-point-flow/RestorePointFlow4Restore.svelte +59 -0
- package/dist/components/onboarding/restore-point-flow/RestorePointFlow4Restore.svelte.d.ts +8 -0
- package/dist/components/onboarding/stages/OnboardingStageBackupServices.svelte +82 -0
- package/dist/components/onboarding/stages/OnboardingStageBackupServices.svelte.d.ts +8 -0
- package/dist/components/onboarding/stages/OnboardingStageKeyConfirm.svelte +56 -0
- package/dist/components/onboarding/stages/OnboardingStageKeyConfirm.svelte.d.ts +9 -0
- package/dist/components/onboarding/stages/OnboardingStageKeyImport.svelte +57 -0
- package/dist/components/onboarding/stages/OnboardingStageKeyImport.svelte.d.ts +8 -0
- package/dist/components/onboarding/stages/OnboardingStageKeyIntro.svelte +50 -0
- package/dist/components/onboarding/stages/OnboardingStageKeyIntro.svelte.d.ts +7 -0
- package/dist/components/onboarding/stages/OnboardingStageKeySave.svelte +44 -0
- package/dist/components/onboarding/stages/OnboardingStageKeySave.svelte.d.ts +8 -0
- package/dist/components/onboarding/stages/OnboardingStageWelcome.svelte +56 -0
- package/dist/components/onboarding/stages/OnboardingStageWelcome.svelte.d.ts +9 -0
- package/dist/components/onboarding/stages/SampleCreateFirstBackup.svelte +43 -0
- package/dist/components/onboarding/stages/SampleCreateFirstBackup.svelte.d.ts +7 -0
- package/dist/components/onboarding/stages/SampleCreateFirstSchedule.svelte +49 -0
- package/dist/components/onboarding/stages/SampleCreateFirstSchedule.svelte.d.ts +7 -0
- package/dist/components/schedules/RepositoryPicker.svelte +105 -0
- package/dist/components/schedules/RepositoryPicker.svelte.d.ts +6 -0
- package/dist/components/schedules/ScheduleItem.svelte +47 -0
- package/dist/components/schedules/ScheduleItem.svelte.d.ts +8 -0
- package/dist/components/schedules/ScheduleList.svelte +51 -0
- package/dist/components/schedules/ScheduleList.svelte.d.ts +3 -0
- package/dist/components/schedules/dialogs/ConfigureScheduleModal.svelte +48 -0
- package/dist/components/schedules/dialogs/ConfigureScheduleModal.svelte.d.ts +8 -0
- package/dist/components/schedules/dialogs/CreateScheduleModal.svelte +43 -0
- package/dist/components/schedules/dialogs/CreateScheduleModal.svelte.d.ts +6 -0
- package/dist/components/test/ImmichTestUi.svelte +183 -0
- package/dist/components/test/ImmichTestUi.svelte.d.ts +6 -0
- package/dist/components/test/TestUi.svelte +134 -0
- package/dist/components/test/TestUi.svelte.d.ts +6 -0
- package/dist/components/test/dashboard/ActiveJobs.svelte +380 -0
- package/dist/components/test/dashboard/ActiveJobs.svelte.d.ts +3 -0
- package/dist/components/test/dashboard/BackupHealth.svelte +95 -0
- package/dist/components/test/dashboard/BackupHealth.svelte.d.ts +7 -0
- package/dist/components/test/dashboard/BackupStats.svelte +117 -0
- package/dist/components/test/dashboard/BackupStats.svelte.d.ts +8 -0
- package/dist/components/test/dashboard/Dashboard.svelte +76 -0
- package/dist/components/test/dashboard/Dashboard.svelte.d.ts +6 -0
- package/dist/components/test/dashboard/RecentBackups.svelte +96 -0
- package/dist/components/test/dashboard/RecentBackups.svelte.d.ts +8 -0
- package/dist/components/ui/PageLayout.svelte +67 -0
- package/dist/components/ui/PageLayout.svelte.d.ts +10 -0
- package/dist/components/ui/PathListField.svelte +83 -0
- package/dist/components/ui/PathListField.svelte.d.ts +17 -0
- package/dist/components/ui/PathPickerField.svelte +74 -0
- package/dist/components/ui/PathPickerField.svelte.d.ts +15 -0
- package/dist/components/ui/PathPickerModal.svelte +219 -0
- package/dist/components/ui/PathPickerModal.svelte.d.ts +14 -0
- package/dist/components/ui/StackList.svelte +30 -0
- package/dist/components/ui/StackList.svelte.d.ts +30 -0
- package/dist/components/ui/StackListItem.svelte +64 -0
- package/dist/components/ui/StackListItem.svelte.d.ts +13 -0
- package/dist/components/ui/VisualisationGauge.svelte +25 -0
- package/dist/components/ui/VisualisationGauge.svelte.d.ts +10 -0
- package/dist/components/ui/VisualisationSegmentedBar.svelte +48 -0
- package/dist/components/ui/VisualisationSegmentedBar.svelte.d.ts +14 -0
- package/dist/components/util/OnEvents.svelte +31 -0
- package/dist/components/util/OnEvents.svelte.d.ts +7 -0
- package/dist/components/util/RelativeTime.svelte +21 -0
- package/dist/components/util/RelativeTime.svelte.d.ts +6 -0
- package/dist/components/util/Suspense.svelte +21 -0
- package/dist/components/util/Suspense.svelte.d.ts +29 -0
- package/dist/components/util/TimedButton.svelte +37 -0
- package/dist/components/util/TimedButton.svelte.d.ts +7 -0
- package/dist/components/util/YuccaContext.svelte +26 -0
- package/dist/components/util/YuccaContext.svelte.d.ts +8 -0
- package/dist/events.d.ts +6 -0
- package/dist/events.js +47 -0
- package/dist/fetch-client.d.ts +289 -0
- package/dist/fetch-client.js +233 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +28 -0
- package/dist/options.d.ts +5 -0
- package/dist/options.js +6 -0
- package/dist/providers.d.ts +11 -0
- package/dist/providers.js +35 -0
- package/dist/query-client.d.ts +2 -0
- package/dist/query-client.js +2 -0
- package/dist/services/backend.service.d.ts +18 -0
- package/dist/services/backend.service.js +61 -0
- package/dist/services/filesystem.service.d.ts +2 -0
- package/dist/services/filesystem.service.js +11 -0
- package/dist/services/immich.integration.service.d.ts +6 -0
- package/dist/services/immich.integration.service.js +24 -0
- package/dist/services/integrations.service.d.ts +13 -0
- package/dist/services/integrations.service.js +42 -0
- package/dist/services/log.service.svelte.d.ts +53 -0
- package/dist/services/log.service.svelte.js +93 -0
- package/dist/services/metricsHistory.service.d.ts +4 -0
- package/dist/services/metricsHistory.service.js +12 -0
- package/dist/services/onboarding.service.d.ts +11 -0
- package/dist/services/onboarding.service.js +56 -0
- package/dist/services/repository.service.d.ts +45 -0
- package/dist/services/repository.service.js +157 -0
- package/dist/services/runHistory.service.d.ts +26 -0
- package/dist/services/runHistory.service.js +54 -0
- package/dist/services/schedule.service.d.ts +35 -0
- package/dist/services/schedule.service.js +126 -0
- package/dist/services/snapshot.service.d.ts +29 -0
- package/dist/services/snapshot.service.js +108 -0
- package/dist/services/task.service.d.ts +3 -0
- package/dist/services/task.service.js +20 -0
- package/dist/utils/actions.d.ts +2 -0
- package/dist/utils/actions.js +3 -0
- package/dist/utils/format.d.ts +2 -0
- package/dist/utils/format.js +24 -0
- package/dist/utils/handle-error.d.ts +9 -0
- package/dist/utils/handle-error.js +42 -0
- 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,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;
|