diff --git a/web/src/lib/components/admin-settings/StorageTemplateSettings.svelte b/web/src/lib/components/admin-settings/StorageTemplateSettings.svelte
index eed418c39..05657654c 100644
--- a/web/src/lib/components/admin-settings/StorageTemplateSettings.svelte
+++ b/web/src/lib/components/admin-settings/StorageTemplateSettings.svelte
@@ -6,7 +6,7 @@
import FormatMessage from '$lib/elements/FormatMessage.svelte';
import { authManager } from '$lib/managers/auth-manager.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
- import { createDefinedQuery, executeMutation } from '$lib/queries/client';
+ import { createPreloadedQuery, executeMutation } from '$lib/queries/client';
import { getConfigQuery, updateConfigMutation } from '$lib/queries/system-config.query';
import StorageTemplateVariablesModal from '$lib/modals/StorageTemplateVariablesModal.svelte';
import { Route } from '$lib/route';
@@ -40,7 +40,7 @@
type StorageTemplateOption = (typeof StorageTemplateOption)[keyof typeof StorageTemplateOption];
const disabled = $derived(featureFlagsManager.value.configFile);
- const query = createDefinedQuery(getConfigQuery());
+ const query = createPreloadedQuery(getConfigQuery);
const config = $derived(query.data);
let configToEdit = $state(cloneDeep(config));
@@ -155,7 +155,7 @@
onDestroy(() => {
if (saveOnClose) {
- void executeMutation(updateConfigMutation(), { storageTemplate: configToEdit.storageTemplate });
+ void executeMutation(updateConfigMutation, { storageTemplate: configToEdit.storageTemplate });
}
});
diff --git a/web/src/lib/components/shared-components/navigation-bar/AccountInfoPanel.svelte b/web/src/lib/components/shared-components/navigation-bar/AccountInfoPanel.svelte
index d79fb0118..3fa820e53 100644
--- a/web/src/lib/components/shared-components/navigation-bar/AccountInfoPanel.svelte
+++ b/web/src/lib/components/shared-components/navigation-bar/AccountInfoPanel.svelte
@@ -19,7 +19,7 @@
let { onClose }: Props = $props();
- const query = createQuery(() => getAboutInfoQuery());
+ const query = createQuery(getAboutInfoQuery);
const info = $derived(query.data);
diff --git a/web/src/lib/components/shared-components/settings/SystemConfigButtonRow.svelte b/web/src/lib/components/shared-components/settings/SystemConfigButtonRow.svelte
index fb40e600c..f8226a923 100644
--- a/web/src/lib/components/shared-components/settings/SystemConfigButtonRow.svelte
+++ b/web/src/lib/components/shared-components/settings/SystemConfigButtonRow.svelte
@@ -1,5 +1,5 @@
@@ -28,9 +30,13 @@
{onSubmit}
submitText={$t('add')}
size="small"
+ disabled={!value || exists}
>
{$t('admin.exclusion_pattern_description')}
+ {#if exists}
+ {$t('errors.exclusion_pattern_already_exists')}
+ {/if}
diff --git a/web/src/lib/modals/LibraryExclusionPatternEditModal.svelte b/web/src/lib/modals/LibraryExclusionPatternEditModal.svelte
index d2fc52a80..0fb230476 100644
--- a/web/src/lib/modals/LibraryExclusionPatternEditModal.svelte
+++ b/web/src/lib/modals/LibraryExclusionPatternEditModal.svelte
@@ -1,5 +1,6 @@
-
+
{$t('admin.exclusion_pattern_description')}
diff --git a/web/src/lib/modals/LibraryFolderAddModal.svelte b/web/src/lib/modals/LibraryFolderAddModal.svelte
index 6d261c4af..780205e43 100644
--- a/web/src/lib/modals/LibraryFolderAddModal.svelte
+++ b/web/src/lib/modals/LibraryFolderAddModal.svelte
@@ -1,7 +1,8 @@
@@ -28,9 +30,13 @@
{onSubmit}
size="small"
submitText={$t('add')}
+ disabled={!value || exists}
>
{$t('admin.library_folder_description')}
+ {#if exists}
+ {$t('errors.library_folder_already_exists')}
+ {/if}
diff --git a/web/src/lib/modals/LibraryFolderEditModal.svelte b/web/src/lib/modals/LibraryFolderEditModal.svelte
index 53294b29a..a84587e1f 100644
--- a/web/src/lib/modals/LibraryFolderEditModal.svelte
+++ b/web/src/lib/modals/LibraryFolderEditModal.svelte
@@ -1,5 +1,6 @@
-
+
{$t('admin.library_folder_description')}
diff --git a/web/src/lib/queries/client.ts b/web/src/lib/queries/client.ts
index 513f05476..9ce0267ec 100644
--- a/web/src/lib/queries/client.ts
+++ b/web/src/lib/queries/client.ts
@@ -1,24 +1,70 @@
+import { toastManager } from '@immich/ui';
import {
createQuery,
+ MutationCache,
+ mutationOptions,
+ MutationObserver,
QueryClient,
type CreateMutationOptions,
type DataTag,
type DefaultError,
type DefinedCreateQueryResult,
+ type MutateOptions,
type NonUndefinedGuard,
type QueryKey,
type UndefinedInitialDataOptions,
} from '@tanstack/svelte-query';
import { noop } from 'lodash-es';
+import type { Translations } from 'svelte-i18n';
import { eventManager } from '$lib/managers/event-manager.svelte';
+import { handleError } from '$lib/utils/handle-error';
+import { getFormatter } from '$lib/utils/i18n';
+
+type ToastMeta =
+ true | Translations | { color: 'primary' | 'success' | 'info' | 'warning' | 'danger'; description: Translations };
+
+declare module '@tanstack/svelte-query' {
+ interface Register {
+ mutationMeta: {
+ success?: { toast?: ToastMeta };
+ error?: { toast?: Translations };
+ };
+ }
+}
const createQueryClient = () => {
const client = new QueryClient({
defaultOptions: {
queries: {
retry: false,
+ staleTime: Infinity,
},
},
+ mutationCache: new MutationCache({
+ onSuccess: async (_data, _variables, _onMutateResult, { meta }) => {
+ const toast = meta?.success?.toast;
+ if (!toast) {
+ return;
+ }
+
+ if (toast === true) {
+ toastManager.primary();
+ return;
+ }
+
+ const $t = await getFormatter();
+ if (typeof toast === 'string') {
+ toastManager.primary($t(toast));
+ return;
+ }
+
+ toastManager[toast.color]($t(toast.description));
+ },
+ onError: async (error, _variables, _onMutateResult, { meta }) => {
+ const $t = await getFormatter();
+ handleError(error, $t(meta?.error?.toast ?? 'errors.something_went_wrong'));
+ },
+ }),
});
eventManager.on({ AuthLogout: () => client.clear() });
@@ -28,22 +74,45 @@ const createQueryClient = () => {
export const queryClient = createQueryClient();
-export const createDefinedQuery = (
- options: UndefinedInitialDataOptions & {
+export const asMutation = (
+ fn: (...args: TArgs) => Promise,
+ options?: Omit, 'mutationFn' | 'mutationKey'>,
+) => mutationOptions({ ...options, mutationFn: (args: TArgs) => fn(...args) });
+
+export const createPreloadedQuery = <
+ TQueryFnData,
+ TError = DefaultError,
+ TData = TQueryFnData,
+ TQueryKey extends QueryKey = QueryKey,
+>(
+ optionsFn: () => UndefinedInitialDataOptions & {
queryKey: DataTag;
},
-): DefinedCreateQueryResult => {
- const initialData = queryClient.getQueryData>(
- options.queryKey,
- );
- if (initialData === undefined) {
- throw new Error(`${options.queryKey.join('/')} must be loaded first`);
- }
+): DefinedCreateQueryResult =>
+ createQuery(() => {
+ const options = optionsFn();
+ const initialData = queryClient.getQueryData>(
+ options.queryKey,
+ );
+ if (initialData === undefined) {
+ throw new Error(`${options.queryKey.join('/')} must be loaded first`);
+ }
- return createQuery(() => ({ ...options, initialData }));
-};
+ return { ...options, initialData };
+ });
+
+type MutationArgs = TVariables extends void
+ ? [variables?: TVariables, mutateOptions?: MutateOptions]
+ : [variables: TVariables, mutateOptions?: MutateOptions];
export const executeMutation = (
- options: CreateMutationOptions,
- variables: TVariables,
-) => queryClient.getMutationCache().build(queryClient, options).execute(variables).catch(noop);
+ options: () => CreateMutationOptions,
+ ...[variables, mutateOptions]: MutationArgs
+) => {
+ const observer = new MutationObserver(queryClient, options());
+ const unsubscribe = observer.subscribe(noop);
+ return observer
+ .mutate(variables as TVariables, mutateOptions)
+ .catch(noop)
+ .finally(unsubscribe);
+};
diff --git a/web/src/lib/queries/library.query.ts b/web/src/lib/queries/library.query.ts
index 920de891e..4949ce557 100644
--- a/web/src/lib/queries/library.query.ts
+++ b/web/src/lib/queries/library.query.ts
@@ -1,8 +1,74 @@
-import { getLibraryStatistics } from '@immich/sdk';
+import {
+ createLibrary,
+ deleteLibrary,
+ getAllLibraries,
+ getLibraryStatistics,
+ scanLibrary,
+ updateLibrary,
+ type LibraryResponseDto,
+} from '@immich/sdk';
import { queryOptions } from '@tanstack/svelte-query';
+import { asMutation, queryClient } from '$lib/queries/client';
+
+const findLibrary = (libraries: LibraryResponseDto[], id: string) => {
+ const library = libraries.find((library) => library.id === id);
+ if (!library) {
+ throw new Error(`Library ${id} not found`);
+ }
+ return library;
+};
+
+export const getAllLibrariesQuery = () =>
+ queryOptions({
+ queryKey: ['libraries'],
+ queryFn: ({ signal }) => getAllLibraries({ signal }),
+ });
+
+export const getLibraryQuery = (id: string) =>
+ queryOptions({
+ ...getAllLibrariesQuery(),
+ select: (libraries) => findLibrary(libraries, id),
+ });
export const getLibraryStatisticsQuery = (id: string) =>
queryOptions({
queryKey: ['libraries', id, 'statistics'],
queryFn: ({ signal }) => getLibraryStatistics({ id }, { signal }),
+ refetchInterval: 30_000,
+ });
+
+export const createLibraryMutation = () =>
+ asMutation(createLibrary, {
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: getAllLibrariesQuery().queryKey, exact: true }),
+ meta: { error: { toast: 'errors.unable_to_create_library' } },
+ });
+
+export const updateLibraryMutation = () =>
+ asMutation(updateLibrary, {
+ onSuccess: async (library) => {
+ queryClient.setQueryData(getAllLibrariesQuery().queryKey, (libraries) =>
+ libraries?.map((item) => (item.id === library.id ? library : item)),
+ );
+ await queryClient.invalidateQueries({ queryKey: getLibraryStatisticsQuery(library.id).queryKey });
+ },
+ meta: { success: { toast: 'admin.library_updated' }, error: { toast: 'errors.unable_to_update_library' } },
+ });
+
+export const deleteLibraryMutation = () =>
+ asMutation(deleteLibrary, {
+ onSuccess: (_, [{ id }]) => {
+ queryClient.setQueryData(getAllLibrariesQuery().queryKey, (libraries) =>
+ libraries?.filter((library) => library.id !== id),
+ );
+ queryClient.removeQueries({ queryKey: getLibraryStatisticsQuery(id).queryKey });
+ },
+ meta: { success: { toast: 'admin.library_deleted' }, error: { toast: 'errors.unable_to_remove_library' } },
+ });
+
+export const scanLibraryMutation = () =>
+ asMutation(scanLibrary, {
+ meta: {
+ success: { toast: { color: 'info', description: 'admin.scanning_library' } },
+ error: { toast: 'errors.unable_to_scan_library' },
+ },
});
diff --git a/web/src/lib/queries/queue.query.ts b/web/src/lib/queries/queue.query.ts
new file mode 100644
index 000000000..3640bf236
--- /dev/null
+++ b/web/src/lib/queries/queue.query.ts
@@ -0,0 +1,12 @@
+import { QueueCommand, QueueName, runQueueCommandLegacy } from '@immich/sdk';
+import { mutationOptions } from '@tanstack/svelte-query';
+
+export const scanAllLibrariesMutation = () =>
+ mutationOptions({
+ mutationFn: () =>
+ runQueueCommandLegacy({ name: QueueName.Library, queueCommandDto: { command: QueueCommand.Start } }),
+ meta: {
+ success: { toast: { color: 'info', description: 'admin.refreshing_all_libraries' } },
+ error: { toast: 'errors.unable_to_scan_libraries' },
+ },
+ });
diff --git a/web/src/lib/queries/server.query.ts b/web/src/lib/queries/server.query.ts
index 675658305..1390de3f3 100644
--- a/web/src/lib/queries/server.query.ts
+++ b/web/src/lib/queries/server.query.ts
@@ -5,21 +5,18 @@ export const getAboutInfoQuery = () =>
queryOptions({
queryKey: ['server', 'about'],
queryFn: ({ signal }) => getAboutInfo({ signal }),
- staleTime: Infinity,
});
export const getVersionHistoryQuery = () =>
queryOptions({
queryKey: ['server', 'version-history'],
queryFn: ({ signal }) => getVersionHistory({ signal }),
- staleTime: Infinity,
});
export const getStorageQuery = () =>
queryOptions({
queryKey: ['server', 'storage'],
queryFn: ({ signal }) => getStorage({ signal }),
- staleTime: Infinity,
});
export const getServerStatisticsQuery = () =>
diff --git a/web/src/lib/queries/system-config.query.ts b/web/src/lib/queries/system-config.query.ts
index fb7646570..2126d8a11 100644
--- a/web/src/lib/queries/system-config.query.ts
+++ b/web/src/lib/queries/system-config.query.ts
@@ -4,21 +4,18 @@ import { mutationOptions, queryOptions } from '@tanstack/svelte-query';
import { isEqual } from 'lodash-es';
import { eventManager } from '$lib/managers/event-manager.svelte';
import { queryClient } from '$lib/queries/client';
-import { handleError } from '$lib/utils/handle-error';
import { getFormatter } from '$lib/utils/i18n';
export const getConfigQuery = () =>
queryOptions({
queryKey: ['system-config'],
queryFn: ({ signal }) => getConfig({ signal }),
- staleTime: Infinity,
});
export const getConfigDefaultsQuery = () =>
queryOptions({
queryKey: ['system-config', 'defaults'],
queryFn: ({ signal }) => getConfigDefaults({ signal }),
- staleTime: Infinity,
});
export const updateConfigMutation = () =>
@@ -44,8 +41,5 @@ export const updateConfigMutation = () =>
const $t = await getFormatter();
toastManager.primary($t('settings_saved'));
},
- onError: async (error) => {
- const $t = await getFormatter();
- handleError(error, $t('errors.unable_to_save_settings'));
- },
+ meta: { error: { toast: 'errors.unable_to_save_settings' } },
});
diff --git a/web/src/lib/queries/user-admin.query.ts b/web/src/lib/queries/user-admin.query.ts
new file mode 100644
index 000000000..bcf010e57
--- /dev/null
+++ b/web/src/lib/queries/user-admin.query.ts
@@ -0,0 +1,20 @@
+import { searchUsersAdmin } from '@immich/sdk';
+import { queryOptions } from '@tanstack/svelte-query';
+
+export const searchUsersAdminQuery = () =>
+ queryOptions({
+ queryKey: ['user-admin'],
+ queryFn: ({ signal }) => searchUsersAdmin({ withDeleted: true }, { signal }),
+ });
+
+export const getUserAdminQuery = (id: string) =>
+ queryOptions({
+ ...searchUsersAdminQuery(),
+ select: (users) => {
+ const user = users.find((user) => user.id === id);
+ if (!user) {
+ throw new Error(`User ${id} not found`);
+ }
+ return user;
+ },
+ });
diff --git a/web/src/lib/services/library.service.ts b/web/src/lib/services/library.service.ts
index 95a49147d..d2f95a561 100644
--- a/web/src/lib/services/library.service.ts
+++ b/web/src/lib/services/library.service.ts
@@ -1,33 +1,23 @@
-import {
- createLibrary,
- deleteLibrary,
- QueueCommand,
- QueueName,
- runQueueCommandLegacy,
- scanLibrary,
- updateLibrary,
- type CreateLibraryDto,
- type LibraryResponseDto,
- type UpdateLibraryDto,
-} from '@immich/sdk';
-import { modalManager, toastManager, type ActionItem } from '@immich/ui';
+import type { LibraryResponseDto } from '@immich/sdk';
+import { modalManager, type ActionItem } from '@immich/ui';
import { mdiInformationOutline, mdiPencilOutline, mdiPlusBoxOutline, mdiSync, mdiTrashCanOutline } from '@mdi/js';
import type { MessageFormatter } from 'svelte-i18n';
import { goto } from '$app/navigation';
-import { eventManager } from '$lib/managers/event-manager.svelte';
import LibraryExclusionPatternAddModal from '$lib/modals/LibraryExclusionPatternAddModal.svelte';
import LibraryExclusionPatternEditModal from '$lib/modals/LibraryExclusionPatternEditModal.svelte';
import LibraryFolderAddModal from '$lib/modals/LibraryFolderAddModal.svelte';
import LibraryFolderEditModal from '$lib/modals/LibraryFolderEditModal.svelte';
+import { executeMutation } from '$lib/queries/client';
+import { deleteLibraryMutation, scanLibraryMutation, updateLibraryMutation } from '$lib/queries/library.query';
+import { scanAllLibrariesMutation } from '$lib/queries/queue.query';
import { Route } from '$lib/route';
-import { handleError } from '$lib/utils/handle-error';
import { getFormatter } from '$lib/utils/i18n';
export const getLibrariesActions = ($t: MessageFormatter) => {
const ScanAll: ActionItem = {
title: $t('scan_all_libraries'),
icon: mdiSync,
- onAction: () => handleScanAllLibraries(),
+ onAction: () => executeMutation(scanAllLibrariesMutation),
shortcuts: { shift: true, key: 'r' },
};
@@ -79,7 +69,7 @@ export const getLibraryActions = ($t: MessageFormatter, library: LibraryResponse
const Scan: ActionItem = {
icon: mdiSync,
title: $t('scan_library'),
- onAction: () => handleScanLibrary(library),
+ onAction: () => executeMutation(scanLibraryMutation, [{ id: library.id }]),
shortcuts: { shift: true, key: 'r' },
};
@@ -122,54 +112,6 @@ export const getLibraryExclusionPatternActions = (
return { Edit, Delete };
};
-const handleScanAllLibraries = async () => {
- const $t = await getFormatter();
-
- try {
- await runQueueCommandLegacy({ name: QueueName.Library, queueCommandDto: { command: QueueCommand.Start } });
- toastManager.info($t('admin.refreshing_all_libraries'));
- } catch (error) {
- handleError(error, $t('errors.unable_to_scan_libraries'));
- }
-};
-
-const handleScanLibrary = async (library: LibraryResponseDto) => {
- const $t = await getFormatter();
- try {
- await scanLibrary({ id: library.id });
- toastManager.info($t('admin.scanning_library'));
- } catch (error) {
- handleError(error, $t('errors.unable_to_scan_library'));
- }
-};
-
-export const handleCreateLibrary = async (dto: CreateLibraryDto) => {
- const $t = await getFormatter();
-
- try {
- const library = await createLibrary({ createLibraryDto: dto });
- eventManager.emit('LibraryCreate', library);
- toastManager.primary($t('admin.library_created', { values: { library: library.name } }));
- return library;
- } catch (error) {
- handleError(error, $t('errors.unable_to_create_library'));
- }
-};
-
-export const handleUpdateLibrary = async (library: LibraryResponseDto, dto: UpdateLibraryDto) => {
- const $t = await getFormatter();
-
- try {
- const updatedLibrary = await updateLibrary({ id: library.id, updateLibraryDto: dto });
- eventManager.emit('LibraryUpdate', updatedLibrary);
- toastManager.primary($t('admin.library_updated'));
- return true;
- } catch (error) {
- handleError(error, $t('errors.unable_to_update_library'));
- return false;
- }
-};
-
const handleDeleteLibrary = async (library: LibraryResponseDto) => {
const $t = await getFormatter();
@@ -190,57 +132,9 @@ const handleDeleteLibrary = async (library: LibraryResponseDto) => {
}
}
- try {
- await deleteLibrary({ id: library.id });
- eventManager.emit('LibraryDelete', { id: library.id });
- toastManager.primary($t('admin.library_deleted'));
- } catch (error) {
- handleError(error, $t('errors.unable_to_remove_library'));
- }
-};
-
-export const handleAddLibraryFolder = async (library: LibraryResponseDto, folder: string) => {
- const $t = await getFormatter();
-
- if (library.importPaths.includes(folder)) {
- toastManager.danger($t('errors.library_folder_already_exists'));
- return false;
- }
-
- try {
- const updatedLibrary = await updateLibrary({
- id: library.id,
- updateLibraryDto: { importPaths: [...library.importPaths, folder] },
- });
- eventManager.emit('LibraryUpdate', updatedLibrary);
- toastManager.primary($t('admin.library_updated'));
- } catch (error) {
- handleError(error, $t('errors.unable_to_update_library'));
- return false;
- }
-
- return true;
-};
-
-export const handleEditLibraryFolder = async (library: LibraryResponseDto, oldValue: string, newValue: string) => {
- const $t = await getFormatter();
-
- if (oldValue === newValue) {
- return true;
- }
-
- const importPaths = library.importPaths.map((path) => (path === oldValue ? newValue : path));
-
- try {
- const updatedLibrary = await updateLibrary({ id: library.id, updateLibraryDto: { importPaths } });
- eventManager.emit('LibraryUpdate', updatedLibrary);
- toastManager.primary($t('admin.library_updated'));
- } catch (error) {
- handleError(error, $t('errors.unable_to_update_library'));
- return false;
- }
-
- return true;
+ await executeMutation(deleteLibraryMutation, [{ id: library.id }], {
+ onSuccess: () => void goto(Route.libraries()),
+ });
};
const handleDeleteLibraryFolder = async (library: LibraryResponseDto, folder: string) => {
@@ -255,60 +149,8 @@ const handleDeleteLibraryFolder = async (library: LibraryResponseDto, folder: st
return;
}
- try {
- const updatedLibrary = await updateLibrary({
- id: library.id,
- updateLibraryDto: { importPaths: library.importPaths.filter((path) => path !== folder) },
- });
- eventManager.emit('LibraryUpdate', updatedLibrary);
- toastManager.primary($t('admin.library_updated'));
- } catch (error) {
- handleError(error, $t('errors.unable_to_update_library'));
- }
-};
-
-export const handleAddLibraryExclusionPattern = async (library: LibraryResponseDto, exclusionPattern: string) => {
- const $t = await getFormatter();
-
- if (library.exclusionPatterns.includes(exclusionPattern)) {
- toastManager.danger($t('errors.exclusion_pattern_already_exists'));
- return false;
- }
-
- try {
- const updatedLibrary = await updateLibrary({
- id: library.id,
- updateLibraryDto: { exclusionPatterns: [...library.exclusionPatterns, exclusionPattern] },
- });
- eventManager.emit('LibraryUpdate', updatedLibrary);
- toastManager.primary($t('admin.library_updated'));
- } catch (error) {
- handleError(error, $t('errors.unable_to_update_library'));
- return false;
- }
-
- return true;
-};
-
-export const handleEditExclusionPattern = async (library: LibraryResponseDto, oldValue: string, newValue: string) => {
- const $t = await getFormatter();
-
- if (oldValue === newValue) {
- return true;
- }
-
- const exclusionPatterns = library.exclusionPatterns.map((pattern) => (pattern === oldValue ? newValue : pattern));
-
- try {
- const updatedLibrary = await updateLibrary({ id: library.id, updateLibraryDto: { exclusionPatterns } });
- eventManager.emit('LibraryUpdate', updatedLibrary);
- toastManager.primary($t('admin.library_updated'));
- } catch (error) {
- handleError(error, $t('errors.unable_to_update_library'));
- return false;
- }
-
- return true;
+ const importPaths = library.importPaths.filter((path) => path !== folder);
+ await executeMutation(updateLibraryMutation, [{ id: library.id, updateLibraryDto: { importPaths } }]);
};
const handleDeleteExclusionPattern = async (library: LibraryResponseDto, exclusionPattern: string) => {
@@ -319,16 +161,6 @@ const handleDeleteExclusionPattern = async (library: LibraryResponseDto, exclusi
return;
}
- try {
- const updatedLibrary = await updateLibrary({
- id: library.id,
- updateLibraryDto: {
- exclusionPatterns: library.exclusionPatterns.filter((pattern) => pattern !== exclusionPattern),
- },
- });
- eventManager.emit('LibraryUpdate', updatedLibrary);
- toastManager.primary($t('admin.library_updated'));
- } catch (error) {
- handleError(error, $t('errors.unable_to_update_library'));
- }
+ const exclusionPatterns = library.exclusionPatterns.filter((pattern) => pattern !== exclusionPattern);
+ await executeMutation(updateLibraryMutation, [{ id: library.id, updateLibraryDto: { exclusionPatterns } }]);
};
diff --git a/web/src/lib/services/system-config.service.ts b/web/src/lib/services/system-config.service.ts
index 568d59575..cfa616a49 100644
--- a/web/src/lib/services/system-config.service.ts
+++ b/web/src/lib/services/system-config.service.ts
@@ -55,7 +55,7 @@ const handleUploadConfig = () => {
}
const reader = async () => {
const text = await file.text();
- await executeMutation(updateConfigMutation(), JSON.parse(text));
+ await executeMutation(updateConfigMutation, JSON.parse(text));
};
reader()
.catch((error) => console.error('Error handling JSON config upload', error))
diff --git a/web/src/routes/admin/library-management/(list)/+layout.svelte b/web/src/routes/admin/library-management/(list)/+layout.svelte
index d41a1c99b..8a361b1fb 100644
--- a/web/src/routes/admin/library-management/(list)/+layout.svelte
+++ b/web/src/routes/admin/library-management/(list)/+layout.svelte
@@ -1,30 +1,18 @@
-
-
@@ -70,61 +35,16 @@
{#if libraries.length > 0}
- {$t('name')}
- {$t('owner')}
- {$t('photos')}
- {$t('videos')}
- {$t('size')}
-
+ {$t('name')}
+ {$t('owner')}
+ {$t('photos')}
+ {$t('videos')}
+ {$t('size')}
+
- {#each libraries as library (library.id + library.name)}
- {@const owner = owners[library.id]}
-
-
- {library.name}
-
-
- {owner.name}
-
- {#await data.statisticsPromise}
-
-
-
-
-
-
-
-
-
- {:then loadedStats}
- {@const stats = loadedStats[library.id]}
-
- {stats.photos.toLocaleString($locale)}
-
-
- {stats.videos.toLocaleString($locale)}
-
-
- {@const [diskUsage, diskUsageUnit] = getBytesWithUnit(stats.usage, 0)}
- {diskUsage}
- {diskUsageUnit}
-
- {:catch}
-
-
-
-
-
-
-
-
-
- {/await}
-
-
-
-
+ {#each libraries as library (library.id)}
+
{/each}
@@ -141,37 +61,3 @@
-
-
diff --git a/web/src/routes/admin/library-management/(list)/+layout.ts b/web/src/routes/admin/library-management/(list)/+layout.ts
index 4721cf550..f0e4cdec4 100644
--- a/web/src/routes/admin/library-management/(list)/+layout.ts
+++ b/web/src/routes/admin/library-management/(list)/+layout.ts
@@ -1,27 +1,16 @@
-import { getAllLibraries, getLibraryStatistics, getUserAdmin, searchUsersAdmin } from '@immich/sdk';
+import { queryClient } from '$lib/queries/client';
+import { getAllLibrariesQuery } from '$lib/queries/library.query';
+import { searchUsersAdminQuery } from '$lib/queries/user-admin.query';
import { authenticate } from '$lib/utils/auth';
import { getFormatter } from '$lib/utils/i18n';
import type { LayoutLoad } from './$types';
-export const load = (async ({ url, depends }) => {
- depends('app:libraries');
+export const load = (async ({ url }) => {
await authenticate(url, { admin: true });
- const allUsers = await searchUsersAdmin({ withDeleted: false });
+ await Promise.all([queryClient.query(getAllLibrariesQuery()), queryClient.query(searchUsersAdminQuery())]);
const $t = await getFormatter();
- const libraries = await getAllLibraries();
- const statisticsPromise = Promise.all(
- libraries.map(async ({ id }) => [id, await getLibraryStatistics({ id })] as const),
- );
- const owners = await Promise.all(
- libraries.map(async ({ id, ownerId }) => [id, await getUserAdmin({ id: ownerId })] as const),
- );
-
return {
- allUsers,
- libraries,
- statisticsPromise: statisticsPromise.then((stats) => Object.fromEntries(stats)),
- owners: Object.fromEntries(owners),
meta: {
title: $t('external_libraries'),
},
diff --git a/web/src/routes/admin/library-management/(list)/LibraryTableRow.svelte b/web/src/routes/admin/library-management/(list)/LibraryTableRow.svelte
new file mode 100644
index 000000000..faf25e32d
--- /dev/null
+++ b/web/src/routes/admin/library-management/(list)/LibraryTableRow.svelte
@@ -0,0 +1,100 @@
+
+
+
+
+ {library.name}
+
+
+ {owner.name}
+
+ {#if stats}
+ {@const [diskUsage, diskUsageUnit] = getBytesWithUnit(stats.usage, 0)}
+
+ {stats.photos.toLocaleString($locale)}
+
+
+ {stats.videos.toLocaleString($locale)}
+
+
+ {diskUsage}
+ {diskUsageUnit}
+
+ {:else}
+
+
+
+
+
+
+
+
+
+ {/if}
+
+
+
+
+
+
diff --git a/web/src/routes/admin/library-management/(list)/new/+page.svelte b/web/src/routes/admin/library-management/(list)/new/+page.svelte
index d5ace4a90..b8db1a86f 100644
--- a/web/src/routes/admin/library-management/(list)/new/+page.svelte
+++ b/web/src/routes/admin/library-management/(list)/new/+page.svelte
@@ -1,31 +1,26 @@
diff --git a/web/src/routes/admin/library-management/[id]/+layout.svelte b/web/src/routes/admin/library-management/[id]/+layout.svelte
index 32b412275..690a01ea1 100644
--- a/web/src/routes/admin/library-management/[id]/+layout.svelte
+++ b/web/src/routes/admin/library-management/[id]/+layout.svelte
@@ -1,15 +1,13 @@
-
-
{
- depends('app:library');
+export const load = (async ({ params: { id }, url }) => {
await authenticate(url, { admin: true });
- let library: LibraryResponseDto;
-
- try {
- library = await getLibrary({ id });
- } catch {
+ const libraries = await queryClient.query(getAllLibrariesQuery());
+ if (libraries.every((library) => library.id !== id)) {
redirect(307, Route.libraries());
}
const $t = await getFormatter();
return {
- library,
meta: {
title: $t('admin.library_details'),
},
diff --git a/web/src/routes/admin/library-management/[id]/edit/+page.svelte b/web/src/routes/admin/library-management/[id]/edit/+page.svelte
index 02c89e521..b668cac5b 100644
--- a/web/src/routes/admin/library-management/[id]/edit/+page.svelte
+++ b/web/src/routes/admin/library-management/[id]/edit/+page.svelte
@@ -1,30 +1,25 @@
diff --git a/web/src/routes/admin/system-settings/+page.svelte b/web/src/routes/admin/system-settings/+page.svelte
index ab3b43ac4..51ffa1ea2 100644
--- a/web/src/routes/admin/system-settings/+page.svelte
+++ b/web/src/routes/admin/system-settings/+page.svelte
@@ -21,7 +21,7 @@
import SettingAccordion from '$lib/components/shared-components/settings/SettingAccordion.svelte';
import SearchBar from '$lib/elements/SearchBar.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
- import { createDefinedQuery } from '$lib/queries/client';
+ import { createPreloadedQuery } from '$lib/queries/client';
import { getConfigQuery } from '$lib/queries/system-config.query';
import { getSystemConfigActions } from '$lib/services/system-config.service';
import { Alert, CommandPaletteDefaultProvider, Container } from '@immich/ui';
@@ -57,7 +57,7 @@
const { data }: Props = $props();
- const config = createDefinedQuery(getConfigQuery());
+ const config = createPreloadedQuery(getConfigQuery);
const settings: Array<{
component: Component;
diff --git a/web/src/routes/admin/system-settings/AuthSettings.svelte b/web/src/routes/admin/system-settings/AuthSettings.svelte
index 7fd23dc79..0e6f080c1 100644
--- a/web/src/routes/admin/system-settings/AuthSettings.svelte
+++ b/web/src/routes/admin/system-settings/AuthSettings.svelte
@@ -7,7 +7,7 @@
import FormatMessage from '$lib/elements/FormatMessage.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import AuthDisableLoginConfirmModal from '$lib/modals/AuthDisableLoginConfirmModal.svelte';
- import { createDefinedQuery } from '$lib/queries/client';
+ import { createPreloadedQuery } from '$lib/queries/client';
import { getConfigQuery } from '$lib/queries/system-config.query';
import { handleError } from '$lib/utils/handle-error';
import { OAuthTokenEndpointAuthMethod, unlinkAllOAuthAccountsAdmin } from '@immich/sdk';
@@ -18,7 +18,7 @@
import { fade } from 'svelte/transition';
import SettingSelect from './SettingSelect.svelte';
- const query = createDefinedQuery(getConfigQuery());
+ const query = createPreloadedQuery(getConfigQuery);
const config = $derived(query.data);
const disabled = $derived(featureFlagsManager.value.configFile);
diff --git a/web/src/routes/admin/system-settings/BackupSettings.svelte b/web/src/routes/admin/system-settings/BackupSettings.svelte
index 4e147794a..b9da87d6d 100644
--- a/web/src/routes/admin/system-settings/BackupSettings.svelte
+++ b/web/src/routes/admin/system-settings/BackupSettings.svelte
@@ -1,5 +1,5 @@