mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
feat: yucca integration
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
<script lang="ts">
|
||||
import RestoreFlowDetectInstall from '$lib/components/maintenance/restore-flow/RestoreFlowDetectInstall.svelte';
|
||||
import RestoreFlowIntro from '$lib/components/maintenance/restore-flow/RestoreFlowIntro.svelte';
|
||||
import RestoreFlowSelectBackup from '$lib/components/maintenance/restore-flow/RestoreFlowSelectBackup.svelte';
|
||||
import { OnboardingGate } from 'orchestration-ui';
|
||||
|
||||
type Props = {
|
||||
end: () => void;
|
||||
@@ -9,11 +11,26 @@
|
||||
|
||||
const { end, expectedVersion }: Props = $props();
|
||||
|
||||
let stage = $state(0);
|
||||
let stage = $state(localStorage.getItem('restoring-yucca') ? 1 : 0);
|
||||
|
||||
$effect(() => {
|
||||
if (stage === 1) {
|
||||
localStorage.setItem('restoring-yucca', '1');
|
||||
} else {
|
||||
localStorage.removeItem('restoring-yucca');
|
||||
}
|
||||
});
|
||||
|
||||
const next = () => stage++;
|
||||
const previous = () => stage--;
|
||||
</script>
|
||||
|
||||
{#if stage === 0}
|
||||
<RestoreFlowDetectInstall next={() => stage++} {end} />
|
||||
<RestoreFlowIntro flowToYucca={() => (stage = 1)} flowToDatabase={() => (stage = 2)} {end} />
|
||||
{:else if stage === 1}
|
||||
<OnboardingGate flow="immich-restore" onExit={previous} onFinish={() => stage++} />
|
||||
{:else if stage === 2}
|
||||
<RestoreFlowDetectInstall {next} previous={() => (stage = 0)} />
|
||||
{:else}
|
||||
<RestoreFlowSelectBackup previous={() => stage--} {end} {expectedVersion} />
|
||||
<RestoreFlowSelectBackup {previous} {end} {expectedVersion} />
|
||||
{/if}
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
|
||||
type Props = {
|
||||
next: () => void;
|
||||
end: () => void;
|
||||
previous: () => void;
|
||||
};
|
||||
|
||||
const { next, end }: Props = $props();
|
||||
const { next, previous }: Props = $props();
|
||||
|
||||
let detectedInstall: MaintenanceDetectInstallResponseDto | undefined = $state();
|
||||
|
||||
@@ -93,6 +93,6 @@
|
||||
</div>
|
||||
<Text>{$t('maintenance_restore_library_confirm')}</Text>
|
||||
<HStack>
|
||||
<Button onclick={end} variant="ghost">{$t('cancel')}</Button>
|
||||
<Button onclick={previous} variant="ghost">{$t('back')}</Button>
|
||||
<Button onclick={next} trailingIcon={mdiArrowRight}>{$t('next')}</Button>
|
||||
</HStack>
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { Button, Heading, HStack } from '@immich/ui';
|
||||
import { mdiArrowRight } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
type Props = {
|
||||
flowToYucca: () => void;
|
||||
flowToDatabase: () => void;
|
||||
end: () => void;
|
||||
};
|
||||
|
||||
const { flowToYucca, flowToDatabase, end }: Props = $props();
|
||||
</script>
|
||||
|
||||
<Heading size="large" color="primary" tag="h1">Where would you like to restore from?</Heading>
|
||||
<HStack>
|
||||
<Button onclick={end} variant="ghost">{$t('cancel')}</Button>
|
||||
<Button onclick={flowToYucca} trailingIcon={mdiArrowRight}>FUTO Backups</Button>
|
||||
<Button onclick={flowToDatabase} trailingIcon={mdiArrowRight}>Database Backup</Button>
|
||||
</HStack>
|
||||
@@ -14,6 +14,7 @@
|
||||
mdiAccountOutline,
|
||||
mdiArchiveArrowDown,
|
||||
mdiArchiveArrowDownOutline,
|
||||
mdiBackupRestore,
|
||||
mdiFolderOutline,
|
||||
mdiHeart,
|
||||
mdiHeartOutline,
|
||||
@@ -87,6 +88,8 @@
|
||||
<NavbarItem title={$t('folders')} href={Route.folders()} icon={{ icon: mdiFolderOutline, flipped: true }} />
|
||||
{/if}
|
||||
|
||||
<NavbarItem title="Backups" href={Route.backups()} icon={mdiBackupRestore} />
|
||||
|
||||
<NavbarItem title={$t('utilities')} href={Route.utilities()} icon={mdiToolboxOutline} activeIcon={mdiToolbox} />
|
||||
|
||||
<NavbarItem
|
||||
|
||||
@@ -147,6 +147,12 @@ export const Route = {
|
||||
workflows: () => '/utilities/workflows',
|
||||
viewWorkflow: ({ id }: { id: string }) => `/utilities/workflows/${id}`,
|
||||
|
||||
// backups
|
||||
backups: () => '/backups',
|
||||
backupsRepositories: () => '/backups/repositories',
|
||||
backupsSchedules: () => '/backups/schedules',
|
||||
backupsConfig: () => '/backups/config',
|
||||
|
||||
// queues
|
||||
queues: () => '/admin/queues',
|
||||
viewQueue: ({ name }: { name: QueueName }) => `/admin/queues/${asQueueSlug(name)}`,
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/state';
|
||||
import NavigationBar from '$lib/components/shared-components/navigation-bar/navigation-bar.svelte';
|
||||
import { Route } from '$lib/route';
|
||||
import { sidebarStore } from '$lib/stores/sidebar.svelte';
|
||||
import { AppShell, AppShellHeader, AppShellSidebar, NavbarItem } from '@immich/ui';
|
||||
import { mdiBackupRestore, mdiClock, mdiCog, mdiViewDashboard } from '@mdi/js';
|
||||
import { OnboardingGate, orchestrationApiProvider, sdk, setProvider, YuccaContext } from 'orchestration-ui';
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
let { children }: { children: Snippet } = $props();
|
||||
|
||||
sdk.defaults.baseUrl = window.location.origin;
|
||||
setProvider(orchestrationApiProvider);
|
||||
</script>
|
||||
|
||||
<AppShell>
|
||||
<AppShellHeader>
|
||||
<NavigationBar noBorder />
|
||||
</AppShellHeader>
|
||||
|
||||
<AppShellSidebar bind:open={sidebarStore.isOpen}>
|
||||
<div class="flex flex-col pt-8 pe-4 gap-1">
|
||||
<NavbarItem
|
||||
title="Dashboard"
|
||||
href={Route.backups()}
|
||||
icon={mdiViewDashboard}
|
||||
isActive={() => page.url.pathname === '/backups'}
|
||||
/>
|
||||
<NavbarItem title="Repositories" href={Route.backupsRepositories()} icon={mdiBackupRestore} />
|
||||
<NavbarItem title="Schedules" href={Route.backupsSchedules()} icon={mdiClock} />
|
||||
<NavbarItem title="Configure" href={Route.backupsConfig()} icon={mdiCog} />
|
||||
</div>
|
||||
</AppShellSidebar>
|
||||
|
||||
<YuccaContext baseUrl={window.location.origin}>
|
||||
<div class="p-4 flex flex-col gap-2 max-w-6xl m-auto">
|
||||
<OnboardingGate flow="immich-setup" onExit={() => goto('/')}>
|
||||
{@render children()}
|
||||
</OnboardingGate>
|
||||
</div>
|
||||
</YuccaContext>
|
||||
</AppShell>
|
||||
@@ -0,0 +1,6 @@
|
||||
import { authenticate } from '$lib/utils/auth';
|
||||
import type { LayoutLoad } from './$types';
|
||||
|
||||
export const load = (async ({ url }) => {
|
||||
await authenticate(url);
|
||||
}) satisfies LayoutLoad;
|
||||
@@ -0,0 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { Dashboard } from 'orchestration-ui';
|
||||
import { goto } from '$app/navigation';
|
||||
import { Route } from '$lib/route';
|
||||
</script>
|
||||
|
||||
<Dashboard
|
||||
onNavigate={(target) => {
|
||||
if (target === 'backups') goto(Route.backupsRepositories());
|
||||
else if (target === 'schedules') goto(Route.backupsSchedules());
|
||||
else if (target === 'config') goto(Route.backupsConfig());
|
||||
}}
|
||||
/>
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load = (() => {
|
||||
return {
|
||||
meta: {
|
||||
title: 'Backups',
|
||||
},
|
||||
};
|
||||
}) satisfies PageLoad;
|
||||
@@ -0,0 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { BackendsList } from 'orchestration-ui';
|
||||
</script>
|
||||
|
||||
<BackendsList />
|
||||
@@ -0,0 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { BackupsList } from 'orchestration-ui';
|
||||
</script>
|
||||
|
||||
<BackupsList local />
|
||||
@@ -0,0 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { ScheduleList } from 'orchestration-ui';
|
||||
</script>
|
||||
|
||||
<ScheduleList />
|
||||
@@ -6,6 +6,7 @@
|
||||
import { maintenanceStore } from '$lib/stores/maintenance.store';
|
||||
import { MaintenanceAction } from '@immich/sdk';
|
||||
import { Button, Heading, Link, ProgressBar, Scrollable, Text } from '@immich/ui';
|
||||
import { YuccaContext } from 'orchestration-ui';
|
||||
import { t } from 'svelte-i18n';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
@@ -37,58 +38,60 @@
|
||||
);
|
||||
</script>
|
||||
|
||||
<AuthPageLayout
|
||||
withHeader={$status?.action === MaintenanceAction.Start || $status?.action === MaintenanceAction.End}
|
||||
withBackdrop={$status?.action === MaintenanceAction.Start}
|
||||
>
|
||||
<div class="flex flex-col place-items-center text-center gap-8">
|
||||
{#if $status?.action === MaintenanceAction.RestoreDatabase}
|
||||
<Heading size="large" color="primary" tag="h1">{$t('maintenance_action_restore')}</Heading>
|
||||
{#if $status.error}
|
||||
<Scrollable class="max-h-80">
|
||||
<pre class="text-left text-sm"><code>{error}</code></pre>
|
||||
</Scrollable>
|
||||
<Button onclick={end}>{$t('maintenance_end')}</Button>
|
||||
<YuccaContext baseUrl={window.location.origin}>
|
||||
<AuthPageLayout
|
||||
withHeader={$status?.action === MaintenanceAction.Start || $status?.action === MaintenanceAction.End}
|
||||
withBackdrop={$status?.action === MaintenanceAction.Start}
|
||||
>
|
||||
<div class="flex flex-col place-items-center text-center gap-8">
|
||||
{#if $status?.action === MaintenanceAction.RestoreDatabase}
|
||||
<Heading size="large" color="primary" tag="h1">{$t('maintenance_action_restore')}</Heading>
|
||||
{#if $status.error}
|
||||
<Scrollable class="max-h-80">
|
||||
<pre class="text-left text-sm"><code>{error}</code></pre>
|
||||
</Scrollable>
|
||||
<Button onclick={end}>{$t('maintenance_end')}</Button>
|
||||
{:else}
|
||||
<ProgressBar progress={$status.progress || 0} />
|
||||
{#if $status.task === 'backup'}
|
||||
<Text>{$t('maintenance_task_backup')}</Text>
|
||||
{/if}
|
||||
{#if $status.task === 'restore'}
|
||||
<Text>{$t('maintenance_task_restore')}</Text>
|
||||
{/if}
|
||||
{#if $status.task === 'migrations'}
|
||||
<Text>{$t('maintenance_task_migrations')}</Text>
|
||||
{/if}
|
||||
{#if $status.task === 'rollback'}
|
||||
<Text>{$t('maintenance_task_rollback')}</Text>
|
||||
{/if}
|
||||
{/if}
|
||||
{:else if $status?.action === MaintenanceAction.SelectDatabaseRestore && $auth}
|
||||
<MaintenanceRestoreFlow {end} expectedVersion={data.expectedVersion} />
|
||||
{:else}
|
||||
<ProgressBar progress={$status.progress || 0} />
|
||||
{#if $status.task === 'backup'}
|
||||
<Text>{$t('maintenance_task_backup')}</Text>
|
||||
{/if}
|
||||
{#if $status.task === 'restore'}
|
||||
<Text>{$t('maintenance_task_restore')}</Text>
|
||||
{/if}
|
||||
{#if $status.task === 'migrations'}
|
||||
<Text>{$t('maintenance_task_migrations')}</Text>
|
||||
{/if}
|
||||
{#if $status.task === 'rollback'}
|
||||
<Text>{$t('maintenance_task_rollback')}</Text>
|
||||
{/if}
|
||||
{/if}
|
||||
{:else if $status?.action === MaintenanceAction.SelectDatabaseRestore && $auth}
|
||||
<MaintenanceRestoreFlow {end} expectedVersion={data.expectedVersion} />
|
||||
{:else}
|
||||
<Heading size="large" color="primary" tag="h1">{$t('maintenance_title')}</Heading>
|
||||
<p>
|
||||
<FormatMessage key="maintenance_description">
|
||||
{#snippet children({ tag, message })}
|
||||
{#if tag === 'link'}
|
||||
<Link href="https://docs.immich.app/administration/maintenance-mode">
|
||||
{message}
|
||||
</Link>
|
||||
{/if}
|
||||
{/snippet}
|
||||
</FormatMessage>
|
||||
</p>
|
||||
{#if $auth}
|
||||
<Heading size="large" color="primary" tag="h1">{$t('maintenance_title')}</Heading>
|
||||
<p>
|
||||
{$t('maintenance_logged_in_as', {
|
||||
values: {
|
||||
user: $auth.username,
|
||||
},
|
||||
})}
|
||||
<FormatMessage key="maintenance_description">
|
||||
{#snippet children({ tag, message })}
|
||||
{#if tag === 'link'}
|
||||
<Link href="https://docs.immich.app/administration/maintenance-mode">
|
||||
{message}
|
||||
</Link>
|
||||
{/if}
|
||||
{/snippet}
|
||||
</FormatMessage>
|
||||
</p>
|
||||
<Button onclick={end}>{$t('maintenance_end')}</Button>
|
||||
{#if $auth}
|
||||
<p>
|
||||
{$t('maintenance_logged_in_as', {
|
||||
values: {
|
||||
user: $auth.username,
|
||||
},
|
||||
})}
|
||||
</p>
|
||||
<Button onclick={end}>{$t('maintenance_end')}</Button>
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</AuthPageLayout>
|
||||
</div>
|
||||
</AuthPageLayout>
|
||||
</YuccaContext>
|
||||
|
||||
Reference in New Issue
Block a user