refactor: system config endpoints (#32457)

* refactor(web): tanstack query

* refactor: use createQuery

* fix: pnpm lockfile
This commit is contained in:
Jason Rasmussen
2026-10-16 20:51:28 +03:00
committed by Codex
parent 92ab6e9c41
commit 6adebeda0d
34 changed files with 335 additions and 204 deletions
+1
View File
@@ -36,6 +36,7 @@
"@photo-sphere-viewer/resolution-plugin": "^5.14.0",
"@photo-sphere-viewer/settings-plugin": "^5.14.0",
"@photo-sphere-viewer/video-plugin": "^5.14.0",
"@tanstack/svelte-query": "^6.3.2",
"@types/geojson": "^7946.0.16",
"@zoom-image/core": "^0.42.0",
"@zoom-image/svelte": "^0.3.0",
@@ -6,11 +6,12 @@
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 { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { createDefinedQuery, 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';
import { handleSystemConfigSave } from '$lib/services/system-config.service';
import { getStorageTemplateOptions, type SystemConfigTemplateStorageOptionDto } from '@immich/sdk';
import { cloneDeep } from 'lodash-es';
import { Code, Heading, IconButton, Link, modalManager, Text } from '@immich/ui';
import { mdiInformationOutline } from '@mdi/js';
import handlebar from 'handlebars';
@@ -39,8 +40,9 @@
type StorageTemplateOption = (typeof StorageTemplateOption)[keyof typeof StorageTemplateOption];
const disabled = $derived(featureFlagsManager.value.configFile);
const config = $derived(systemConfigManager.value);
let configToEdit = $state(systemConfigManager.cloneValue());
const query = createDefinedQuery(getConfigQuery());
const config = $derived(query.data);
let configToEdit = $state(cloneDeep(config));
// TODO remove once we support partial overwriting with database values
if (preselectDate && !featureFlagsManager.value.configFile && !configToEdit.storageTemplate.enabled) {
@@ -50,7 +52,7 @@
let templateOptions: SystemConfigTemplateStorageOptionDto | undefined = $state();
let selectedPreset = $state('');
let customTemplate = $state(systemConfigManager.value.storageTemplate.template);
let customTemplate = $state(config.storageTemplate.template);
const getTemplateOptions = async () => {
templateOptions = await getStorageTemplateOptions();
@@ -151,9 +153,9 @@
}
});
onDestroy(async () => {
onDestroy(() => {
if (saveOnClose) {
await handleSystemConfigSave({ storageTemplate: configToEdit.storageTemplate });
void executeMutation(updateConfigMutation(), { storageTemplate: configToEdit.storageTemplate });
}
});
</script>
@@ -1,9 +1,10 @@
<script lang="ts">
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { handleSystemConfigSave } from '$lib/services/system-config.service';
import { createDefinedQuery } from '$lib/queries/client';
import { getConfigDefaultsQuery, getConfigQuery, updateConfigMutation } from '$lib/queries/system-config.query';
import type { AdminConfigDto } from '@immich/sdk';
import { Button, toastManager } from '@immich/ui';
import { isEqual, pick } from 'lodash-es';
import { createMutation } from '@tanstack/svelte-query';
import { cloneDeep, isEqual, pick } from 'lodash-es';
import { t } from 'svelte-i18n';
type Props = {
@@ -15,20 +16,19 @@
let { disabled, keys, configToEdit = $bindable(), onBeforeSave }: Props = $props();
const showResetToDefault = $derived(
!isEqual(pick(systemConfigManager.value, keys), pick(systemConfigManager.defaultValue, keys)),
);
const config = createDefinedQuery(getConfigQuery());
const defaults = createDefinedQuery(getConfigDefaultsQuery());
const save = createMutation(updateConfigMutation);
const showResetToDefault = $derived(!isEqual(pick(config.data, keys), pick(defaults.data, keys)));
const handleReset = () => {
configToEdit = systemConfigManager.cloneValue();
configToEdit = cloneDeep(config.data);
toastManager.info($t('admin.reset_settings_to_recent_saved'));
};
const handleResetToDefault = () => {
const defaultConfig = systemConfigManager.cloneDefaultValue();
configToEdit = { ...configToEdit, ...pick(defaultConfig, keys) };
configToEdit = { ...configToEdit, ...cloneDeep(pick(defaults.data, keys)) };
toastManager.info($t('admin.reset_settings_to_default'));
};
@@ -36,7 +36,7 @@
const shouldSave = await onBeforeSave?.();
if (shouldSave ?? true) {
await handleSystemConfigSave(pick(configToEdit, keys));
save.mutate(pick(configToEdit, keys));
}
};
</script>
@@ -52,6 +52,8 @@
<div class="flex gap-1">
<Button shape="round" {disabled} size="small" color="secondary" onclick={handleReset}>{$t('reset')}</Button>
<Button shape="round" type="submit" {disabled} size="small" onclick={handleSave}>{$t('save')}</Button>
<Button shape="round" type="submit" disabled={disabled || save.isPending} size="small" onclick={handleSave}>
{$t('save')}
</Button>
</div>
</div>
@@ -1,57 +0,0 @@
import { getConfig, getConfigDefaults, type AdminConfigDto } from '@immich/sdk';
import { cloneDeep } from 'lodash-es';
import { eventManager } from '$lib/managers/event-manager.svelte';
class SystemConfigManager {
#value?: AdminConfigDto = $state();
#defaultValue?: AdminConfigDto = $state();
constructor() {
eventManager.on({
SystemConfigUpdate: (config) => (this.#value = config),
});
}
async init() {
await this.#loadConfig();
await this.#loadDefault();
}
get value() {
if (!this.#value) {
throw new Error('Server config manager must be initialized first');
}
return this.#value;
}
set value(config: AdminConfigDto) {
this.#value = config;
}
get defaultValue() {
if (!this.#defaultValue) {
throw new Error('Server config manager must be initialized first');
}
return this.#defaultValue;
}
cloneValue() {
return cloneDeep(this.value);
}
cloneDefaultValue() {
return cloneDeep(this.defaultValue);
}
async #loadConfig() {
this.#value = await getConfig();
}
async #loadDefault() {
this.#defaultValue = await getConfigDefaults();
}
}
export const systemConfigManager = new SystemConfigManager();
+49
View File
@@ -0,0 +1,49 @@
import {
createQuery,
QueryClient,
type CreateMutationOptions,
type DataTag,
type DefaultError,
type DefinedCreateQueryResult,
type NonUndefinedGuard,
type QueryKey,
type UndefinedInitialDataOptions,
} from '@tanstack/svelte-query';
import { noop } from 'lodash-es';
import { eventManager } from '$lib/managers/event-manager.svelte';
const createQueryClient = () => {
const client = new QueryClient({
defaultOptions: {
queries: {
retry: false,
},
},
});
eventManager.on({ AuthLogout: () => client.clear() });
return client;
};
export const queryClient = createQueryClient();
export const createDefinedQuery = <TQueryFnData, TError = DefaultError, TQueryKey extends QueryKey = QueryKey>(
options: UndefinedInitialDataOptions<TQueryFnData, TError, TQueryFnData, TQueryKey> & {
queryKey: DataTag<TQueryKey, TQueryFnData, TError>;
},
): DefinedCreateQueryResult<TQueryFnData, TError> => {
const initialData = queryClient.getQueryData<TQueryFnData, TQueryKey, NonUndefinedGuard<TQueryFnData>>(
options.queryKey,
);
if (initialData === undefined) {
throw new Error(`${options.queryKey.join('/')} must be loaded first`);
}
return createQuery(() => ({ ...options, initialData }));
};
export const executeMutation = <TData, TError, TVariables, TContext>(
options: CreateMutationOptions<TData, TError, TVariables, TContext>,
variables: TVariables,
) => queryClient.getMutationCache().build(queryClient, options).execute(variables).catch(noop);
@@ -0,0 +1,51 @@
import { getConfig, getConfigDefaults, updateConfig, type AdminConfigDto } from '@immich/sdk';
import { toastManager } from '@immich/ui';
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 = () =>
mutationOptions({
mutationFn: async (update: Partial<AdminConfigDto>) => {
const config = await getConfig();
const adminConfigDto = { ...config, ...update };
if (isEqual(config, adminConfigDto)) {
return;
}
return updateConfig({ adminConfigDto });
},
onSuccess: async (config) => {
if (!config) {
return;
}
queryClient.setQueryData(getConfigQuery().queryKey, config);
eventManager.emit('SystemConfigUpdate', config);
const $t = await getFormatter();
toastManager.primary($t('settings_saved'));
},
onError: async (error) => {
const $t = await getFormatter();
handleError(error, $t('errors.unable_to_save_settings'));
},
});
+6 -28
View File
@@ -1,12 +1,10 @@
import { getConfig, updateConfig, type ServerFeaturesDto, type AdminConfigDto } from '@immich/sdk';
import { toastManager, type ActionItem } from '@immich/ui';
import type { AdminConfigDto, ServerFeaturesDto } from '@immich/sdk';
import type { ActionItem } from '@immich/ui';
import { mdiContentCopy, mdiDownload, mdiUpload } from '@mdi/js';
import { isEqual } from 'lodash-es';
import type { MessageFormatter } from 'svelte-i18n';
import { eventManager } from '$lib/managers/event-manager.svelte';
import { executeMutation } from '$lib/queries/client';
import { updateConfigMutation } from '$lib/queries/system-config.query';
import { copyToClipboard, downloadJson } from '$lib/utils';
import { handleError } from '$lib/utils/handle-error';
import { getFormatter } from '$lib/utils/i18n';
export const getSystemConfigActions = (
$t: MessageFormatter,
@@ -44,26 +42,7 @@ export const getSystemConfigActions = (
return { CopyToClipboard, Download, Upload };
};
export const handleSystemConfigSave = async (update: Partial<AdminConfigDto>) => {
const $t = await getFormatter();
const config = await getConfig();
const adminConfigDto = { ...config, ...update };
if (isEqual(config, adminConfigDto)) {
return;
}
try {
const newConfig = await updateConfig({ adminConfigDto });
eventManager.emit('SystemConfigUpdate', newConfig);
toastManager.primary($t('settings_saved'));
} catch (error) {
handleError(error, $t('errors.unable_to_save_settings'));
}
};
export const handleUploadConfig = () => {
const handleUploadConfig = () => {
const input = document.createElement('input');
input.setAttribute('type', 'file');
input.setAttribute('accept', '.json');
@@ -76,8 +55,7 @@ export const handleUploadConfig = () => {
}
const reader = async () => {
const text = await file.text();
const newConfig = JSON.parse(text);
await handleSystemConfigSave(newConfig);
await executeMutation(updateConfigMutation(), JSON.parse(text));
};
reader()
.catch((error) => console.error('Error handling JSON config upload', error))
+6
View File
@@ -13,6 +13,8 @@ import { get, writable } from 'svelte/store';
import { page } from '$app/state';
import { authManager } from '$lib/managers/auth-manager.svelte';
import { eventManager } from '$lib/managers/event-manager.svelte';
import { queryClient } from '$lib/queries/client';
import { getConfigQuery } from '$lib/queries/system-config.query';
import { Route } from '$lib/route';
import { maintenanceStore } from '$lib/stores/maintenance.store';
import { notificationManager } from '$lib/stores/notification-manager.svelte';
@@ -68,6 +70,10 @@ websocket
})
.on('disconnect', () => websocketStore.connected.set(false))
.on('on_server_version', (serverVersion) => websocketStore.serverVersion.set(serverVersion))
.on(
'on_config_update',
() => void queryClient.invalidateQueries({ queryKey: getConfigQuery().queryKey, exact: true }),
)
.on('AppRestartV1', (mode) => websocketStore.serverRestarting.set(mode))
.on('MaintenanceStatusV1', (status) => {
maintenanceStore.status.set(status);
+28 -24
View File
@@ -12,6 +12,7 @@
import { eventManager } from '$lib/managers/event-manager.svelte';
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
import ServerRestartingModal from '$lib/modals/ServerRestartingModal.svelte';
import { queryClient } from '$lib/queries/client';
import { Route } from '$lib/route';
import { lang, locale } from '$lib/stores/preferences.store';
import { sidebarStore } from '$lib/stores/sidebar.svelte';
@@ -37,6 +38,7 @@
} from '@immich/ui';
import { En } from 'media-chrome/lang/en';
import { addTranslation } from 'media-chrome/utils/i18n';
import { QueryClientProvider } from '@tanstack/svelte-query';
import { onMount, type Snippet } from 'svelte';
import { t } from 'svelte-i18n';
import { get } from 'svelte/store';
@@ -256,30 +258,32 @@
{/if}
</svelte:head>
<TooltipProvider>
{#if page.data.error}
<ErrorLayout error={page.data.error}></ErrorLayout>
{:else}
{@render children?.()}
{/if}
<QueryClientProvider client={queryClient}>
<TooltipProvider>
{#if page.data.error}
<ErrorLayout error={page.data.error}></ErrorLayout>
{:else}
{@render children?.()}
{/if}
{#if showNavigationLoadingBar}
<NavigationLoadingBar />
{/if}
{#if showNavigationLoadingBar}
<NavigationLoadingBar />
{/if}
<DownloadPanel />
<UploadPanel />
<ScreencastOverlay />
<DownloadPanel />
<UploadPanel />
<ScreencastOverlay />
<CommandPaletteProvider
providers={[
getPagesProvider($t),
getSettingsProvider($t),
defaultProvider({ name: $t('documentation'), types: ['doc', 'documentation'], actions: CORE_PAGE_COMMANDS }),
defaultProvider({ name: $t('support'), actions: PROJECT_SUPPORT_COMMANDS }),
defaultProvider({ name: 'Socials', types: ['social', 'socials'], actions: SOCIAL_COMMANDS }),
defaultProvider({ name: $t('mobile_app'), actions: MOBILE_APP_COMMANDS }),
defaultProvider({ name: 'Sites', types: ['site', 'sites'], actions: OTHER_SITE_COMMANDS }),
]}
/>
</TooltipProvider>
<CommandPaletteProvider
providers={[
getPagesProvider($t),
getSettingsProvider($t),
defaultProvider({ name: $t('documentation'), types: ['doc', 'documentation'], actions: CORE_PAGE_COMMANDS }),
defaultProvider({ name: $t('support'), actions: PROJECT_SUPPORT_COMMANDS }),
defaultProvider({ name: 'Socials', types: ['social', 'socials'], actions: SOCIAL_COMMANDS }),
defaultProvider({ name: $t('mobile_app'), actions: MOBILE_APP_COMMANDS }),
defaultProvider({ name: 'Sites', types: ['site', 'sites'], actions: OTHER_SITE_COMMANDS }),
]}
/>
</TooltipProvider>
</QueryClientProvider>
-2
View File
@@ -1,8 +1,6 @@
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { authenticate } from '$lib/utils/auth';
import type { LayoutLoad } from './$types';
export const load = (async ({ url }) => {
await authenticate(url, { admin: true });
await systemConfigManager.init();
}) satisfies LayoutLoad;
@@ -21,7 +21,8 @@
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 { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { createDefinedQuery } 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';
import {
@@ -56,6 +57,8 @@
const { data }: Props = $props();
const config = createDefinedQuery(getConfigQuery());
const settings: Array<{
component: Component;
title: string;
@@ -208,7 +211,7 @@
);
const { CopyToClipboard, Upload, Download } = $derived(
getSystemConfigActions($t, featureFlagsManager.value, systemConfigManager.value),
getSystemConfigActions($t, featureFlagsManager.value, config.data),
);
</script>
@@ -1,17 +1,15 @@
import { getConfig, getConfigDefaults } from '@immich/sdk';
import { queryClient } from '$lib/queries/client';
import { getConfigDefaultsQuery, getConfigQuery } from '$lib/queries/system-config.query';
import { authenticate } from '$lib/utils/auth';
import { getFormatter } from '$lib/utils/i18n';
import type { PageLoad } from './$types';
export const load = (async ({ url }) => {
await authenticate(url, { admin: true });
const config = await getConfig();
const defaultConfig = await getConfigDefaults();
await Promise.all([queryClient.query(getConfigQuery()), queryClient.query(getConfigDefaultsQuery())]);
const $t = await getFormatter();
return {
config,
defaultConfig,
meta: {
title: $t('admin.system_settings'),
},
@@ -6,19 +6,23 @@
import { SettingInputFieldType } from '$lib/constants';
import FormatMessage from '$lib/elements/FormatMessage.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import AuthDisableLoginConfirmModal from '$lib/modals/AuthDisableLoginConfirmModal.svelte';
import { createDefinedQuery } 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';
import { Button, Link, modalManager, Text, toastManager } from '@immich/ui';
import { mdiRestart } from '@mdi/js';
import { cloneDeep } from 'lodash-es';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
import SettingSelect from './SettingSelect.svelte';
const query = createDefinedQuery(getConfigQuery());
const config = $derived(query.data);
const disabled = $derived(featureFlagsManager.value.configFile);
const config = $derived(systemConfigManager.value);
let configToEdit = $state(systemConfigManager.cloneValue());
let configToEdit = $state(cloneDeep(config));
const handleToggleOverride = () => {
// click runs before bind
@@ -1,4 +1,7 @@
<script lang="ts">
import { createDefinedQuery } from '$lib/queries/client';
import { getConfigQuery } from '$lib/queries/system-config.query';
import { cloneDeep } from 'lodash-es';
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
import SettingInputField from '$lib/components/shared-components/settings/SettingInputField.svelte';
import SettingSelect from './SettingSelect.svelte';
@@ -6,14 +9,15 @@
import { SettingInputFieldType } from '$lib/constants';
import FormatMessage from '$lib/elements/FormatMessage.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { Link } from '@immich/ui';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
const query = createDefinedQuery(getConfigQuery());
const config = $derived(query.data);
const disabled = $derived(featureFlagsManager.value.configFile);
const config = $derived(systemConfigManager.value);
let configToEdit = $state(systemConfigManager.cloneValue());
let configToEdit = $state(cloneDeep(config));
let cronExpressionOptions = $derived([
{ text: $t('interval.night_at_midnight'), value: '0 0 * * *' },
@@ -1,4 +1,6 @@
<script lang="ts">
import { createDefinedQuery } from '$lib/queries/client';
import { getConfigQuery } from '$lib/queries/system-config.query';
import SettingAccordion from '$lib/components/shared-components/settings/SettingAccordion.svelte';
import SettingCheckboxes from './SettingCheckboxes.svelte';
import SettingInputField from '$lib/components/shared-components/settings/SettingInputField.svelte';
@@ -8,7 +10,6 @@
import { SettingInputFieldType } from '$lib/constants';
import FormatMessage from '$lib/elements/FormatMessage.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import {
AudioCodec,
CQMode,
@@ -21,13 +22,15 @@
} from '@immich/sdk';
import { Icon, Link } from '@immich/ui';
import { mdiHelpCircleOutline } from '@mdi/js';
import { isEqual, sortBy } from 'lodash-es';
import { isEqual, sortBy, cloneDeep } from 'lodash-es';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
const query = createDefinedQuery(getConfigQuery());
const config = $derived(query.data);
const disabled = $derived(featureFlagsManager.value.configFile);
const config = $derived(systemConfigManager.value);
let configToEdit = $state(systemConfigManager.cloneValue());
let configToEdit = $state(cloneDeep(config));
</script>
<div>
@@ -1,4 +1,7 @@
<script lang="ts">
import { createDefinedQuery } from '$lib/queries/client';
import { getConfigQuery } from '$lib/queries/system-config.query';
import { cloneDeep } from 'lodash-es';
import SettingSelect from './SettingSelect.svelte';
import { Colorspace, ImageFormat } from '@immich/sdk';
import { fade } from 'svelte/transition';
@@ -9,12 +12,13 @@
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
import { SettingInputFieldType } from '$lib/constants';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { t } from 'svelte-i18n';
const query = createDefinedQuery(getConfigQuery());
const config = $derived(query.data);
const disabled = $derived(featureFlagsManager.value.configFile);
const config = $derived(systemConfigManager.value);
let configToEdit = $state(systemConfigManager.cloneValue());
let configToEdit = $state(cloneDeep(config));
</script>
<div>
@@ -1,4 +1,7 @@
<script lang="ts">
import { createDefinedQuery } from '$lib/queries/client';
import { getConfigQuery } from '$lib/queries/system-config.query';
import { cloneDeep } from 'lodash-es';
import SettingAccordion from '$lib/components/shared-components/settings/SettingAccordion.svelte';
import SettingInputField from '$lib/components/shared-components/settings/SettingInputField.svelte';
import SettingSwitch from '$lib/components/shared-components/settings/SettingSwitch.svelte';
@@ -6,14 +9,15 @@
import { SettingInputFieldType } from '$lib/constants';
import FormatMessage from '$lib/elements/FormatMessage.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { Link } from '@immich/ui';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
const query = createDefinedQuery(getConfigQuery());
const config = $derived(query.data);
const disabled = $derived(featureFlagsManager.value.configFile);
const config = $derived(systemConfigManager.value);
let configToEdit = $state(systemConfigManager.cloneValue());
let configToEdit = $state(cloneDeep(config));
</script>
<div>
@@ -1,16 +1,20 @@
<script lang="ts">
import { createDefinedQuery } from '$lib/queries/client';
import { getConfigQuery } from '$lib/queries/system-config.query';
import { cloneDeep } from 'lodash-es';
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
import SettingInputField from '$lib/components/shared-components/settings/SettingInputField.svelte';
import { SettingInputFieldType } from '$lib/constants';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { QueueName, type AdminConfigJobDto } from '@immich/sdk';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
const query = createDefinedQuery(getConfigQuery());
const config = $derived(query.data);
const disabled = $derived(featureFlagsManager.value.configFile);
const config = $derived(systemConfigManager.value);
let configToEdit = $state(systemConfigManager.cloneValue());
let configToEdit = $state(cloneDeep(config));
const queueNames = [
QueueName.ThumbnailGeneration,
@@ -1,4 +1,7 @@
<script lang="ts">
import { createDefinedQuery } from '$lib/queries/client';
import { getConfigQuery } from '$lib/queries/system-config.query';
import { cloneDeep } from 'lodash-es';
import SettingAccordion from '$lib/components/shared-components/settings/SettingAccordion.svelte';
import SettingInputField from '$lib/components/shared-components/settings/SettingInputField.svelte';
import SettingSelect from './SettingSelect.svelte';
@@ -7,14 +10,15 @@
import { SettingInputFieldType } from '$lib/constants';
import FormatMessage from '$lib/elements/FormatMessage.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { Link } from '@immich/ui';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
const query = createDefinedQuery(getConfigQuery());
const config = $derived(query.data);
const disabled = $derived(featureFlagsManager.value.configFile);
const config = $derived(systemConfigManager.value);
let configToEdit = $state(systemConfigManager.cloneValue());
let configToEdit = $state(cloneDeep(config));
let cronExpressionOptions = $derived([
{ text: $t('interval.night_at_midnight'), value: '0 0 * * *' },
@@ -1,16 +1,20 @@
<script lang="ts">
import { createDefinedQuery } from '$lib/queries/client';
import { getConfigQuery } from '$lib/queries/system-config.query';
import { cloneDeep } from 'lodash-es';
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
import SettingSelect from './SettingSelect.svelte';
import SettingSwitch from '$lib/components/shared-components/settings/SettingSwitch.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { LogLevel } from '@immich/sdk';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
const query = createDefinedQuery(getConfigQuery());
const config = $derived(query.data);
const disabled = $derived(featureFlagsManager.value.configFile);
const config = $derived(systemConfigManager.value);
let configToEdit = $state(systemConfigManager.cloneValue());
let configToEdit = $state(cloneDeep(config));
</script>
<div>
@@ -1,4 +1,6 @@
<script lang="ts">
import { createDefinedQuery } from '$lib/queries/client';
import { getConfigQuery } from '$lib/queries/system-config.query';
import SettingAccordion from '$lib/components/shared-components/settings/SettingAccordion.svelte';
import SettingInputField from '$lib/components/shared-components/settings/SettingInputField.svelte';
import SettingSelect from './SettingSelect.svelte';
@@ -7,16 +9,17 @@
import { SettingInputFieldType } from '$lib/constants';
import FormatMessage from '$lib/elements/FormatMessage.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { Button, IconButton } from '@immich/ui';
import { mdiPlus, mdiTrashCanOutline } from '@mdi/js';
import { isEqual } from 'lodash-es';
import { isEqual, cloneDeep } from 'lodash-es';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
const query = createDefinedQuery(getConfigQuery());
const config = $derived(query.data);
const disabled = $derived(featureFlagsManager.value.configFile);
const config = $derived(systemConfigManager.value);
let configToEdit = $state(systemConfigManager.cloneValue());
let configToEdit = $state(cloneDeep(config));
</script>
<div class="mt-2">
@@ -1,4 +1,7 @@
<script lang="ts">
import { createDefinedQuery } from '$lib/queries/client';
import { getConfigQuery } from '$lib/queries/system-config.query';
import { cloneDeep } from 'lodash-es';
import SettingAccordion from '$lib/components/shared-components/settings/SettingAccordion.svelte';
import SettingInputField from '$lib/components/shared-components/settings/SettingInputField.svelte';
import SettingSwitch from '$lib/components/shared-components/settings/SettingSwitch.svelte';
@@ -6,14 +9,15 @@
import { SettingInputFieldType } from '$lib/constants';
import FormatMessage from '$lib/elements/FormatMessage.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { Link } from '@immich/ui';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
const query = createDefinedQuery(getConfigQuery());
const config = $derived(query.data);
const disabled = $derived(featureFlagsManager.value.configFile);
const config = $derived(systemConfigManager.value);
let configToEdit = $state(systemConfigManager.cloneValue());
let configToEdit = $state(cloneDeep(config));
</script>
<div class="mt-2">
@@ -1,13 +1,17 @@
<script lang="ts">
import { createDefinedQuery } from '$lib/queries/client';
import { getConfigQuery } from '$lib/queries/system-config.query';
import { cloneDeep } from 'lodash-es';
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
import SettingSwitch from '$lib/components/shared-components/settings/SettingSwitch.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
const query = createDefinedQuery(getConfigQuery());
const disabled = $derived(featureFlagsManager.value.configFile);
let configToEdit = $state(systemConfigManager.cloneValue());
let configToEdit = $state(cloneDeep(query.data));
</script>
<div class="mt-2">
@@ -1,16 +1,20 @@
<script lang="ts">
import { createDefinedQuery } from '$lib/queries/client';
import { getConfigQuery } from '$lib/queries/system-config.query';
import { cloneDeep } from 'lodash-es';
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
import SettingSwitch from '$lib/components/shared-components/settings/SettingSwitch.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
import SettingSelect from './SettingSelect.svelte';
import { ReleaseChannel } from '@immich/sdk';
const query = createDefinedQuery(getConfigQuery());
const config = $derived(query.data);
const disabled = $derived(featureFlagsManager.value.configFile);
const config = $derived(systemConfigManager.value);
let configToEdit = $state(systemConfigManager.cloneValue());
let configToEdit = $state(cloneDeep(config));
</script>
<div>
@@ -1,16 +1,20 @@
<script lang="ts">
import { createDefinedQuery } from '$lib/queries/client';
import { getConfigQuery } from '$lib/queries/system-config.query';
import { cloneDeep } from 'lodash-es';
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
import SettingInputField from '$lib/components/shared-components/settings/SettingInputField.svelte';
import SettingSwitch from '$lib/components/shared-components/settings/SettingSwitch.svelte';
import { SettingInputFieldType } from '$lib/constants';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
const query = createDefinedQuery(getConfigQuery());
const config = $derived(query.data);
const disabled = $derived(featureFlagsManager.value.configFile);
const config = $derived(systemConfigManager.value);
let configToEdit = $state(systemConfigManager.cloneValue());
let configToEdit = $state(cloneDeep(config));
</script>
<div class="mt-2">
@@ -1,4 +1,5 @@
<script lang="ts">
import { cloneDeep } from 'lodash-es';
import TemplateSettings from './TemplateSettings.svelte';
import SettingAccordion from '$lib/components/shared-components/settings/SettingAccordion.svelte';
import SettingInputField from '$lib/components/shared-components/settings/SettingInputField.svelte';
@@ -7,17 +8,19 @@
import { SettingInputFieldType } from '$lib/constants';
import { authManager } from '$lib/managers/auth-manager.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { handleSystemConfigSave } from '$lib/services/system-config.service';
import { createDefinedQuery, executeMutation } from '$lib/queries/client';
import { getConfigQuery, updateConfigMutation } from '$lib/queries/system-config.query';
import { handleError } from '$lib/utils/handle-error';
import { sendTestEmailAdmin } from '@immich/sdk';
import { Button, toastManager } from '@immich/ui';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
const query = createDefinedQuery(getConfigQuery());
const config = $derived(query.data);
const disabled = $derived(featureFlagsManager.value.configFile);
const config = $derived(systemConfigManager.value);
let configToEdit = $state(systemConfigManager.cloneValue());
let configToEdit = $state(cloneDeep(config));
let isSending = $state(false);
@@ -50,7 +53,7 @@
);
if (!disabled) {
await handleSystemConfigSave({ notifications: configToEdit.notifications });
await executeMutation(updateConfigMutation(), { notifications: configToEdit.notifications });
}
} catch (error) {
handleError(error, $t('admin.notification_email_test_email_failed'));
@@ -1,16 +1,20 @@
<script lang="ts">
import { createDefinedQuery } from '$lib/queries/client';
import { getConfigQuery } from '$lib/queries/system-config.query';
import { cloneDeep } from 'lodash-es';
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
import SettingInputField from '$lib/components/shared-components/settings/SettingInputField.svelte';
import SettingSwitch from '$lib/components/shared-components/settings/SettingSwitch.svelte';
import { SettingInputFieldType } from '$lib/constants';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
const query = createDefinedQuery(getConfigQuery());
const config = $derived(query.data);
const disabled = $derived(featureFlagsManager.value.configFile);
const config = $derived(systemConfigManager.value);
let configToEdit = $state(systemConfigManager.cloneValue());
let configToEdit = $state(cloneDeep(config));
</script>
<div>
@@ -2,7 +2,8 @@
import SettingAccordion from '$lib/components/shared-components/settings/SettingAccordion.svelte';
import SettingTextarea from './SettingTextarea.svelte';
import FormatMessage from '$lib/elements/FormatMessage.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { createDefinedQuery } from '$lib/queries/client';
import { getConfigQuery } from '$lib/queries/system-config.query';
import EmailTemplatePreviewModal from '$lib/modals/EmailTemplatePreviewModal.svelte';
import { handleError } from '$lib/utils/handle-error';
import { type AdminConfigDto, type AdminConfigTemplateEmailsDto, getNotificationTemplateAdmin } from '@immich/sdk';
@@ -17,6 +18,8 @@
let { config = $bindable() }: Props = $props();
const saved = createDefinedQuery(getConfigQuery());
let loadingPreview = $state(false);
const getTemplate = async (name: string, template: string) => {
@@ -53,7 +56,7 @@
];
const isEdited = (templateKey: keyof AdminConfigTemplateEmailsDto) =>
config.templates.email[templateKey] !== systemConfigManager.value.templates.email[templateKey];
config.templates.email[templateKey] !== saved.data.templates.email[templateKey];
const onsubmit = (event: Event) => {
event.preventDefault();
@@ -1,14 +1,18 @@
<script lang="ts">
import { createDefinedQuery } from '$lib/queries/client';
import { getConfigQuery } from '$lib/queries/system-config.query';
import { cloneDeep } from 'lodash-es';
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
import SettingTextarea from './SettingTextarea.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
const query = createDefinedQuery(getConfigQuery());
const config = $derived(query.data);
const disabled = $derived(featureFlagsManager.value.configFile);
const config = $derived(systemConfigManager.value);
let configToEdit = $state(systemConfigManager.cloneValue());
let configToEdit = $state(cloneDeep(config));
</script>
<div>
@@ -1,16 +1,20 @@
<script lang="ts">
import { createDefinedQuery } from '$lib/queries/client';
import { getConfigQuery } from '$lib/queries/system-config.query';
import { cloneDeep } from 'lodash-es';
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
import SettingInputField from '$lib/components/shared-components/settings/SettingInputField.svelte';
import SettingSwitch from '$lib/components/shared-components/settings/SettingSwitch.svelte';
import { SettingInputFieldType } from '$lib/constants';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
const query = createDefinedQuery(getConfigQuery());
const config = $derived(query.data);
const disabled = $derived(featureFlagsManager.value.configFile);
const config = $derived(systemConfigManager.value);
let configToEdit = $state(systemConfigManager.cloneValue());
let configToEdit = $state(cloneDeep(config));
</script>
<div>
@@ -1,16 +1,20 @@
<script lang="ts">
import { createDefinedQuery } from '$lib/queries/client';
import { getConfigQuery } from '$lib/queries/system-config.query';
import { cloneDeep } from 'lodash-es';
import { fade } from 'svelte/transition';
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
import SettingInputField from '$lib/components/shared-components/settings/SettingInputField.svelte';
import { SettingInputFieldType } from '$lib/constants';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { t } from 'svelte-i18n';
const query = createDefinedQuery(getConfigQuery());
const config = $derived(query.data);
const disabled = $derived(featureFlagsManager.value.configFile);
const config = $derived(systemConfigManager.value);
let configToEdit = $state(systemConfigManager.cloneValue());
let configToEdit = $state(cloneDeep(config));
</script>
<div>
+3 -2
View File
@@ -12,7 +12,8 @@
import OnboardingUserPrivacy from './OnboardingUserPrivacy.svelte';
import { authManager } from '$lib/managers/auth-manager.svelte';
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { queryClient } from '$lib/queries/client';
import { getConfigQuery } from '$lib/queries/system-config.query';
import { Route } from '$lib/route';
import { OnboardingRole } from '$lib/types';
import { setUserOnboarding, updateAdminOnboarding } from '@immich/sdk';
@@ -155,7 +156,7 @@
onMount(async () => {
if (userRole === OnboardingRole.SERVER) {
await systemConfigManager.init();
await queryClient.query(getConfigQuery());
}
});
</script>
@@ -1,14 +1,19 @@
<script lang="ts">
import SettingSwitch from '$lib/components/shared-components/settings/SettingSwitch.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { handleSystemConfigSave } from '$lib/services/system-config.service';
import { createDefinedQuery, executeMutation } from '$lib/queries/client';
import { getConfigQuery, updateConfigMutation } from '$lib/queries/system-config.query';
import { cloneDeep } from 'lodash-es';
import { onDestroy } from 'svelte';
import { t } from 'svelte-i18n';
const configToEdit = $state(systemConfigManager.cloneValue());
const query = createDefinedQuery(getConfigQuery());
const configToEdit = $state(cloneDeep(query.data));
onDestroy(async () => {
await handleSystemConfigSave({ map: configToEdit.map, newVersionCheck: configToEdit.newVersionCheck });
onDestroy(() => {
void executeMutation(updateConfigMutation(), {
map: configToEdit.map,
newVersionCheck: configToEdit.newVersionCheck,
});
});
</script>