From ef7a37a26a0871e6916dce7d286edfd7beac5f11 Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Wed, 17 Dec 2025 13:43:07 -0500 Subject: [PATCH] WIP --- i18n/en.json | 2 +- pnpm-lock.yaml | 10 +- web/package.json | 2 +- web/src/lib/modals/SystemSettingsModal.svelte | 15 +- web/src/lib/services/system-config.service.ts | 425 +++++++++++----- .../admin/system-settings/+layout.svelte | 84 ++- .../authentication/+page.svelte | 207 +++----- .../admin/system-settings/backup/+page.svelte | 63 +-- .../admin/system-settings/job/+page.svelte | 42 +- .../system-settings/library/+page.svelte | 15 +- .../system-settings/logging/+page.svelte | 11 +- .../machine-learning/+page.svelte | 481 +++++++++--------- .../nightly-tasks/+page.svelte | 12 +- .../admin/system-settings/oauth/+page.svelte | 173 +++++++ .../system-settings/password/+page.svelte | 14 + .../admin/system-settings/server/+page.svelte | 10 +- 16 files changed, 942 insertions(+), 624 deletions(-) create mode 100644 web/src/routes/admin/system-settings/oauth/+page.svelte create mode 100644 web/src/routes/admin/system-settings/password/+page.svelte diff --git a/i18n/en.json b/i18n/en.json index 7eb9ffbef6..323ce2bc2f 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -272,7 +272,7 @@ "oauth_timeout_description": "Timeout for requests in milliseconds", "ocr_job_description": "Use machine learning to recognize text in images", "password_enable_description": "Login with email and password", - "password_settings": "Password Login", + "password_settings": "Password login", "password_settings_description": "Manage password login settings", "paths_validated_successfully": "All paths validated successfully", "person_cleanup_job": "Person cleanup", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 33afa6bc4a..ed15376304 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -717,8 +717,8 @@ importers: specifier: file:../open-api/typescript-sdk version: link:../open-api/typescript-sdk '@immich/ui': - specifier: ^0.50.0 - version: 0.50.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2) + specifier: ^0.50.1 + version: 0.50.1(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2) '@mapbox/mapbox-gl-rtl-text': specifier: 0.2.3 version: 0.2.3(mapbox-gl@1.13.3) @@ -2989,8 +2989,8 @@ packages: peerDependencies: svelte: ^5.0.0 - '@immich/ui@0.50.0': - resolution: {integrity: sha512-7AW9SRZTAgal8xlkUAxm7o4+pSG7HcKb+Bh9JpWLaDRRdGyPCZMmsNa9CjZglOQ7wkAD07tQ9u4+zezBLe0dlQ==} + '@immich/ui@0.50.1': + resolution: {integrity: sha512-fNlQGh75ZFa/UZAgJaYk9/ItHOXHNNzN4CunjCmE7WocVVkUZbUxopN9Ku3F5GULSqD/zJ5gNO6PQAZ1ZoSaaQ==} peerDependencies: svelte: ^5.0.0 @@ -14700,7 +14700,7 @@ snapshots: dependencies: svelte: 5.45.2 - '@immich/ui@0.50.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)': + '@immich/ui@0.50.1(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)': dependencies: '@immich/svelte-markdown-preprocess': 0.1.0(svelte@5.45.2) '@internationalized/date': 3.10.0 diff --git a/web/package.json b/web/package.json index cfa0f5cc30..1f475a4458 100644 --- a/web/package.json +++ b/web/package.json @@ -28,7 +28,7 @@ "@formatjs/icu-messageformat-parser": "^2.9.8", "@immich/justified-layout-wasm": "^0.4.3", "@immich/sdk": "file:../open-api/typescript-sdk", - "@immich/ui": "^0.50.0", + "@immich/ui": "^0.50.1", "@mapbox/mapbox-gl-rtl-text": "0.2.3", "@mdi/js": "^7.4.47", "@photo-sphere-viewer/core": "^5.14.0", diff --git a/web/src/lib/modals/SystemSettingsModal.svelte b/web/src/lib/modals/SystemSettingsModal.svelte index 6020ce6db0..4fb36c3e5e 100644 --- a/web/src/lib/modals/SystemSettingsModal.svelte +++ b/web/src/lib/modals/SystemSettingsModal.svelte @@ -4,7 +4,7 @@ import { AppRoute } from '$lib/constants'; import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte'; import { systemConfigManager } from '$lib/managers/system-config-manager.svelte'; - import { getSystemConfigActions, handleSystemConfigSave } from '$lib/services/system-config.service'; + import { getSystemConfigActions, handleSystemConfigSave, resolveSetting } from '$lib/services/system-config.service'; import type { SystemConfigContext } from '$lib/types'; import type { SystemConfigDto } from '@immich/sdk'; import { Button, FormModal, type ModalSize } from '@immich/ui'; @@ -19,13 +19,13 @@ child: Snippet<[SystemConfigContext]>; }; - let { keys, size = 'medium', onBeforeSave, child }: Props = $props(); + let { keys, size = 'large', onBeforeSave, child }: Props = $props(); const disabled = $derived(featureFlagsManager.value.configFile); const config = $derived(systemConfigManager.value); let configToEdit = $state(systemConfigManager.cloneValue()); const { settings } = $derived(getSystemConfigActions($t, featureFlagsManager.value, systemConfigManager.value)); - const setting = $derived(settings.find((setting) => setting.href === page.url.pathname)); + const setting = $derived(resolveSetting(settings, page.url.pathname)); const showResetToDefault = $derived(!isEqual(pick(configToEdit, keys), pick(systemConfigManager.defaultValue, keys))); const handleResetToDefault = () => { @@ -47,14 +47,7 @@ {#if setting} - +
{@render child({ disabled, config, configToEdit })} {#if showResetToDefault} diff --git a/web/src/lib/services/system-config.service.ts b/web/src/lib/services/system-config.service.ts index 3a394fc19f..891831742f 100644 --- a/web/src/lib/services/system-config.service.ts +++ b/web/src/lib/services/system-config.service.ts @@ -1,12 +1,14 @@ import { AppRoute } from '$lib/constants'; import { downloadManager } from '$lib/managers/download-manager.svelte'; import { eventManager } from '$lib/managers/event-manager.svelte'; +import AuthDisableLoginConfirmModal from '$lib/modals/AuthDisableLoginConfirmModal.svelte'; +import type { SystemConfigContext } from '$lib/types'; import { copyToClipboard } from '$lib/utils'; import { downloadBlob } from '$lib/utils/asset-utils'; import { handleError } from '$lib/utils/handle-error'; import { getFormatter } from '$lib/utils/i18n'; -import { getConfig, updateConfig, type ServerFeaturesDto, type SystemConfigDto } from '@immich/sdk'; -import { toastManager, type ActionItem } from '@immich/ui'; +import { getConfig, unlinkAllOAuthAccountsAdmin, updateConfig, type ServerFeaturesDto, type SystemConfigDto } from '@immich/sdk'; +import { modalManager, toastManager, type ActionItem } from '@immich/ui'; import { mdiAccountOutline, mdiBackupRestore, @@ -18,141 +20,293 @@ import { mdiDownload, mdiFileDocumentOutline, mdiFolderOutline, - mdiImageOutline, mdiLockOutline, mdiMapMarkerOutline, mdiPaletteOutline, - mdiRestore, + mdiRestart, mdiRobotOutline, mdiServerOutline, mdiSync, mdiTrashCanOutline, mdiUpdate, mdiUpload, - mdiVideoOutline, + mdiVideoOutline } from '@mdi/js'; import { isEqual } from 'lodash-es'; import type { MessageFormatter } from 'svelte-i18n'; +type SettingsGroup = { + title: string, + subtitle?: string; + items: SettingItem[]; +} + +type SettingItem = { + title: string; subtitle: string; href: string; icon: string; +}; + +export const resolveSetting = (groups: SettingsGroup[], pathname: string) => { + for (const group of groups) { + for (const item of group.items) { + if (item.href === pathname) { + return item; + } + } + } +} + export const getSystemConfigActions = ( $t: MessageFormatter, featureFlags: ServerFeaturesDto, config: SystemConfigDto, ) => { - const settings: Array<{ title: string; subtitle: string; href: string; icon: string }> = [ + const settings: SettingsGroup[] = [ { - title: $t('admin.authentication_settings'), - subtitle: $t('admin.authentication_settings_description'), - href: `${AppRoute.ADMIN_SETTINGS}/authentication`, - icon: mdiLockOutline, + + title: $t('admin.authentication_settings'), + subtitle: $t('admin.authentication_settings_description'), + + items: [ + { +title:$t('admin.password_settings'), subtitle: $t('admin.password_settings_description'), + href: `${AppRoute.ADMIN_SETTINGS}/password`, + icon: mdiLockOutline, + }, + { + + title:$t('admin.oauth_settings'), subtitle:$t('admin.oauth_settings_description'), + href: `${AppRoute.ADMIN_SETTINGS}/oauth`, + icon: mdiFileDocumentOutline, + }, + ] }, { - title: $t('admin.backup_settings'), - subtitle: $t('admin.backup_settings_description'), - href: `${AppRoute.ADMIN_SETTINGS}/backup`, - icon: mdiBackupRestore, + title: 'General', items: [ + // { + // title: $t('admin.image_settings'), + // subtitle: $t('admin.image_settings_description'), + // href: `${AppRoute.ADMIN_SETTINGS}/image`, + // icon: mdiImageOutline, + // }, + { + title: $t('admin.library_settings'), + subtitle: $t('admin.library_settings_description'), + href: `${AppRoute.ADMIN_SETTINGS}/library`, + icon: mdiBookshelf, + }, + // { + // title: $t('admin.maintenance_settings'), + // subtitle: $t('admin.maintenance_settings_description'), + // href: `${AppRoute.ADMIN_SETTINGS}/maintenance`, + // icon: mdiRestore, + // }, + { + title: $t('admin.map_gps_settings'), + subtitle: $t('admin.map_gps_settings_description'), + href: `${AppRoute.ADMIN_SETTINGS}/location`, + icon: mdiMapMarkerOutline, + }, + { + title: $t('admin.metadata_settings'), + subtitle: $t('admin.metadata_settings_description'), + href: `${AppRoute.ADMIN_SETTINGS}/metadata`, + icon: mdiDatabaseOutline, + }, + { + title: $t('admin.notification_settings'), + subtitle: $t('admin.notification_settings_description'), + href: `${AppRoute.ADMIN_SETTINGS}/notifications`, + icon: mdiBellOutline, + }, + { + title: $t('admin.storage_template_settings'), + subtitle: $t('admin.storage_template_settings_description'), + href: `${AppRoute.ADMIN_SETTINGS}/storage-template`, + icon: mdiFolderOutline, + }, + { + title: $t('admin.theme_settings'), + subtitle: $t('admin.theme_settings_description'), + href: `${AppRoute.ADMIN_SETTINGS}/theme`, + icon: mdiPaletteOutline, + }, + { + title: $t('admin.trash_settings'), + subtitle: $t('admin.trash_settings_description'), + href: `${AppRoute.ADMIN_SETTINGS}/trash`, + icon: mdiTrashCanOutline, + }, + { + title: $t('admin.user_settings'), + subtitle: $t('admin.user_settings_description'), + href: `${AppRoute.ADMIN_SETTINGS}/user`, + icon: mdiAccountOutline, + }, + ] + }, + + + { + title: 'Image', items: [ + { + title: 'General settings', + subtitle: $t('admin.transcoding_settings_description'), + href: `${AppRoute.ADMIN_SETTINGS}/video-transcoding`, + icon: mdiVideoOutline, + }, + { + title: 'Thumbnail settings', + subtitle: $t('admin.transcoding_settings_description'), + href: `${AppRoute.ADMIN_SETTINGS}/video-transcoding`, + icon: mdiVideoOutline, + }, + { + title: 'Preview settings', + subtitle: $t('admin.transcoding_settings_description'), + href: `${AppRoute.ADMIN_SETTINGS}/video-transcoding`, + icon: mdiVideoOutline, + }, + { + title: 'Full-size settings', + subtitle: $t('admin.transcoding_settings_description'), + href: `${AppRoute.ADMIN_SETTINGS}/video-transcoding`, + icon: mdiVideoOutline, + }, + ] }, { - title: $t('admin.image_settings'), - subtitle: $t('admin.image_settings_description'), - href: `${AppRoute.ADMIN_SETTINGS}/image`, - icon: mdiImageOutline, + title: 'Video', items: [ + { + title: 'General settings', + subtitle: $t('admin.transcoding_settings_description'), + href: `${AppRoute.ADMIN_SETTINGS}/video-transcoding`, + icon: mdiVideoOutline, + }, + { + title: 'Transcoding Policies', + subtitle: $t('admin.transcoding_settings_description'), + href: `${AppRoute.ADMIN_SETTINGS}/video-transcoding`, + icon: mdiVideoOutline, + }, + { + title: 'Hardware Acceleration', + subtitle: $t('admin.transcoding_settings_description'), + href: `${AppRoute.ADMIN_SETTINGS}/video-transcoding`, + icon: mdiVideoOutline, + }, + { + title: 'Encoding Options', + subtitle: $t('admin.transcoding_settings_description'), + href: `${AppRoute.ADMIN_SETTINGS}/video-transcoding`, + icon: mdiVideoOutline, + }, + { + title: $t('admin.transcoding_settings'), + subtitle: $t('admin.transcoding_settings_description'), + href: `${AppRoute.ADMIN_SETTINGS}/video-transcoding`, + icon: mdiVideoOutline, + }, + { + title: 'Advanced Settings', + subtitle: $t('admin.transcoding_settings_description'), + href: `${AppRoute.ADMIN_SETTINGS}/video-transcoding`, + icon: mdiVideoOutline, + }, + ] + }, + + { + title: 'Machine learning', items: [ + { + title: 'Connection settings', + subtitle: $t('admin.machine_learning_settings_description'), + href: `${AppRoute.ADMIN_SETTINGS}/machine-learning`, + icon: mdiRobotOutline, + }, + // { + // title: $t('admin.machine_learning_settings'), + // subtitle: $t('admin.machine_learning_settings_description'), + // href: `${AppRoute.ADMIN_SETTINGS}/machine-learning`, + // icon: mdiRobotOutline, + // }, + { + title: 'Search', + subtitle: $t('admin.machine_learning_settings_description'), + href: `${AppRoute.ADMIN_SETTINGS}/machine-learning`, + icon: mdiRobotOutline, + }, + { + title: 'Duplicate Detection', + subtitle: $t('admin.machine_learning_settings_description'), + href: `${AppRoute.ADMIN_SETTINGS}/machine-learning`, + icon: mdiRobotOutline, + }, + { + title: 'Facial Recognition', + subtitle: $t('admin.machine_learning_settings_description'), + href: `${AppRoute.ADMIN_SETTINGS}/machine-learning`, + icon: mdiRobotOutline, + }, + { + title: 'OCR', + subtitle: $t('admin.machine_learning_settings_description'), + href: `${AppRoute.ADMIN_SETTINGS}/machine-learning`, + icon: mdiRobotOutline, + }, + ] + }, + + { + title: 'Job settings', items: [ + { + title: 'General settings', + subtitle: $t('admin.server_settings_description'), + href: `${AppRoute.ADMIN_SETTINGS}/server`, + icon: mdiServerOutline, + }, + { + title: $t('admin.nightly_tasks_settings'), + subtitle: $t('admin.nightly_tasks_settings_description'), + href: `${AppRoute.ADMIN_SETTINGS}/nightly-tasks`, + icon: mdiClockOutline, + }, + + ] }, { - title: $t('admin.job_settings'), - subtitle: $t('admin.job_settings_description'), - href: `${AppRoute.ADMIN_SETTINGS}/job`, - icon: mdiSync, - }, - { - title: $t('admin.library_settings'), - subtitle: $t('admin.library_settings_description'), - href: `${AppRoute.ADMIN_SETTINGS}/library`, - icon: mdiBookshelf, - }, - { - title: $t('admin.logging_settings'), - subtitle: $t('admin.manage_log_settings'), - href: `${AppRoute.ADMIN_SETTINGS}/logging`, - icon: mdiFileDocumentOutline, - }, - { - title: $t('admin.machine_learning_settings'), - subtitle: $t('admin.machine_learning_settings_description'), - href: `${AppRoute.ADMIN_SETTINGS}/machine-learning`, - icon: mdiRobotOutline, - }, - { - title: $t('admin.maintenance_settings'), - subtitle: $t('admin.maintenance_settings_description'), - href: `${AppRoute.ADMIN_SETTINGS}/maintenance`, - icon: mdiRestore, - }, - { - title: $t('admin.map_gps_settings'), - subtitle: $t('admin.map_gps_settings_description'), - href: `${AppRoute.ADMIN_SETTINGS}/location`, - icon: mdiMapMarkerOutline, - }, - { - title: $t('admin.metadata_settings'), - subtitle: $t('admin.metadata_settings_description'), - href: `${AppRoute.ADMIN_SETTINGS}/metadata`, - icon: mdiDatabaseOutline, - }, - { - title: $t('admin.nightly_tasks_settings'), - subtitle: $t('admin.nightly_tasks_settings_description'), - href: `${AppRoute.ADMIN_SETTINGS}/nightly-tasks`, - icon: mdiClockOutline, - }, - { - title: $t('admin.notification_settings'), - subtitle: $t('admin.notification_settings_description'), - href: `${AppRoute.ADMIN_SETTINGS}/notifications`, - icon: mdiBellOutline, - }, - { - title: $t('admin.server_settings'), - subtitle: $t('admin.server_settings_description'), - href: `${AppRoute.ADMIN_SETTINGS}/server`, - icon: mdiServerOutline, - }, - { - title: $t('admin.storage_template_settings'), - subtitle: $t('admin.storage_template_settings_description'), - href: `${AppRoute.ADMIN_SETTINGS}/storage-template`, - icon: mdiFolderOutline, - }, - { - title: $t('admin.theme_settings'), - subtitle: $t('admin.theme_settings_description'), - href: `${AppRoute.ADMIN_SETTINGS}/theme`, - icon: mdiPaletteOutline, - }, - { - title: $t('admin.trash_settings'), - subtitle: $t('admin.trash_settings_description'), - href: `${AppRoute.ADMIN_SETTINGS}/trash`, - icon: mdiTrashCanOutline, - }, - { - title: $t('admin.user_settings'), - subtitle: $t('admin.user_settings_description'), - href: `${AppRoute.ADMIN_SETTINGS}/user`, - icon: mdiAccountOutline, - }, - { - title: $t('admin.version_check_settings'), - subtitle: $t('admin.version_check_settings_description'), - href: `${AppRoute.ADMIN_SETTINGS}/version-check`, - icon: mdiUpdate, - }, - { - title: $t('admin.transcoding_settings'), - subtitle: $t('admin.transcoding_settings_description'), - href: `${AppRoute.ADMIN_SETTINGS}/video-transcoding`, - icon: mdiVideoOutline, + title: 'Server settings', items: [ + { + title: 'General settings', + subtitle: $t('admin.server_settings_description'), + href: `${AppRoute.ADMIN_SETTINGS}/server`, + icon: mdiServerOutline, + }, + { + title: $t('admin.authentication_settings'), + subtitle: $t('admin.authentication_settings_description'), + href: `${AppRoute.ADMIN_SETTINGS}/authentication`, + icon: mdiLockOutline, + }, + + { + title: $t('admin.job_settings'), + subtitle: $t('admin.job_settings_description'), + href: `${AppRoute.ADMIN_SETTINGS}/job`, + icon: mdiSync, + }, + { + title: $t('admin.backup_settings'), + subtitle: $t('admin.backup_settings_description'), + href: `${AppRoute.ADMIN_SETTINGS}/backup`, + icon: mdiBackupRestore, + }, + { + title: $t('admin.version_check_settings'), + subtitle: $t('admin.version_check_settings_description'), + href: `${AppRoute.ADMIN_SETTINGS}/version-check`, + icon: mdiUpdate, + }, + ] }, ]; @@ -213,12 +367,12 @@ export const handleSystemConfigSave = async (update: Partial) = const jsonReplacer = (_key: string, value: unknown) => value instanceof Object && !Array.isArray(value) ? Object.keys(value) - .sort() - // eslint-disable-next-line unicorn/no-array-reduce - .reduce((sorted: { [key: string]: unknown }, key) => { - sorted[key] = (value as { [key: string]: unknown })[key]; - return sorted; - }, {}) + .sort() + // eslint-disable-next-line unicorn/no-array-reduce + .reduce((sorted: { [key: string]: unknown }, key) => { + sorted[key] = (value as { [key: string]: unknown })[key]; + return sorted; + }, {}) : value; export const handleCopyToClipboard = async (config: SystemConfigDto) => { @@ -255,3 +409,38 @@ export const handleUploadConfig = () => { }); input.remove(); }; + + +export const handleUnlinkAllOAuthAccounts = async () => { + const $t = await getFormatter(); + const confirmed = await modalManager.showDialog({ + icon: mdiRestart, + title: $t('admin.unlink_all_oauth_accounts'), + prompt: $t('admin.unlink_all_oauth_accounts_prompt'), + confirmColor: 'danger', + }); + + if (!confirmed) { + return; + } + + try { + await unlinkAllOAuthAccountsAdmin(); + toastManager.success(); + } catch (error) { + handleError(error, $t('errors.something_went_wrong')); + } + }; + + +export const onBeforeSave = async ({ configToEdit }: SystemConfigContext) => { + const allMethodsDisabled = !configToEdit.oauth.enabled && !configToEdit.passwordLogin.enabled; + if (allMethodsDisabled) { + const isConfirmed = await modalManager.show(AuthDisableLoginConfirmModal, {}); + if (!isConfirmed) { + return false; + } + } + + return true; + }; diff --git a/web/src/routes/admin/system-settings/+layout.svelte b/web/src/routes/admin/system-settings/+layout.svelte index 4924e527be..7d6b780c94 100644 --- a/web/src/routes/admin/system-settings/+layout.svelte +++ b/web/src/routes/admin/system-settings/+layout.svelte @@ -4,7 +4,7 @@ import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte'; import { systemConfigManager } from '$lib/managers/system-config-manager.svelte'; import { getSystemConfigActions } from '$lib/services/system-config.service'; - import { Alert, Button, CommandPaletteContext, Icon, Text } from '@immich/ui'; + import { Alert, Button, Card, CardHeader, CardTitle, CommandPaletteContext, Container, Icon, Text } from '@immich/ui'; import { mdiPencilOutline } from '@mdi/js'; import type { Snippet } from 'svelte'; import { t } from 'svelte-i18n'; @@ -22,39 +22,69 @@ ); let searchQuery = $state(''); - let filteredSettings = $derived( - settings.filter(({ title, subtitle }) => { - const query = searchQuery.toLowerCase(); - return title.toLowerCase().includes(query) || subtitle.toLowerCase().includes(query); - }), + const filteredGroups = $derived( + settings + .map(({ title, items }) => { + const query = searchQuery.toLowerCase(); + return { + title, + items: items.filter( + ({ title, subtitle }) => title.toLowerCase().includes(query) || subtitle.toLowerCase().includes(query), + ), + }; + }) + .filter(({ items }) => items.length > 0), ); -
-
- {#if featureFlagsManager.value.configFile} - - {/if} -
- -
-
- {#each filteredSettings as { title, subtitle, href, icon } (href)} - + {/each} +
+
- - - {/each} - + {/each} + + diff --git a/web/src/routes/admin/system-settings/authentication/+page.svelte b/web/src/routes/admin/system-settings/authentication/+page.svelte index e7122f50cc..1c7e8cf4d2 100644 --- a/web/src/routes/admin/system-settings/authentication/+page.svelte +++ b/web/src/routes/admin/system-settings/authentication/+page.svelte @@ -1,16 +1,13 @@ - {#snippet child({ disabled, config, configToEdit })} - + {#snippet child({ disabled, configToEdit })} + + + - - {#snippet descriptionSnippet()} -

- - {#snippet children({ message })} - - {message} -
-
- {/snippet} -
-

- {/snippet} -
+ + + + + {#snippet children({ message })} + + {message} + + {/snippet} + + + - + > + + {/snippet}
diff --git a/web/src/routes/admin/system-settings/job/+page.svelte b/web/src/routes/admin/system-settings/job/+page.svelte index cb7c6a09ff..63ee1e99f6 100644 --- a/web/src/routes/admin/system-settings/job/+page.svelte +++ b/web/src/routes/admin/system-settings/job/+page.svelte @@ -1,9 +1,8 @@ - {#snippet child({ disabled, config, configToEdit })} + {#snippet child({ disabled, configToEdit })} {#each queueNames as queueName (queueName)} -
- {#if isSystemConfigJobDto(configToEdit, queueName)} - - {:else} - - {/if} -
+ {#if isSystemConfigJobDto(configToEdit, queueName)} + + + + {:else} + + + {$t('admin.job_not_concurrency_safe')} + + {/if} {/each} {/snippet}
diff --git a/web/src/routes/admin/system-settings/library/+page.svelte b/web/src/routes/admin/system-settings/library/+page.svelte index 53b86baffc..9e090557e8 100644 --- a/web/src/routes/admin/system-settings/library/+page.svelte +++ b/web/src/routes/admin/system-settings/library/+page.svelte @@ -1,20 +1,17 @@ - {#snippet child({ disabled, config, configToEdit })} - + > + + {/snippet} diff --git a/web/src/routes/admin/system-settings/logging/+page.svelte b/web/src/routes/admin/system-settings/logging/+page.svelte index 5f28064aa9..d47b4c5d79 100644 --- a/web/src/routes/admin/system-settings/logging/+page.svelte +++ b/web/src/routes/admin/system-settings/logging/+page.svelte @@ -1,18 +1,17 @@ {#snippet child({ disabled, config, configToEdit })} - + + + + import SystemSettingsCard from '$lib/components/SystemSettingsCard.svelte'; - import SettingInputField from '$lib/components/shared-components/settings/setting-input-field.svelte'; import SettingSelect from '$lib/components/shared-components/settings/setting-select.svelte'; - import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte'; - import { SettingInputFieldType } from '$lib/constants'; import FormatMessage from '$lib/elements/FormatMessage.svelte'; import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte'; import SystemSettingsModal from '$lib/modals/SystemSettingsModal.svelte'; - import { Button, IconButton } from '@immich/ui'; + import { + Button, + Field, + HelperText, + IconButton, + Input, + Label, + Link, + NumberInput, + Stack, + Switch, + Text, + } from '@immich/ui'; import { mdiPlus, mdiTrashCanOutline } from '@mdi/js'; - import { isEqual } from 'lodash-es'; import { t } from 'svelte-i18n'; {#snippet child({ disabled, config, configToEdit })} -
- + + + -
- {#each configToEdit.machineLearning.urls as _, i (i)} - - {#snippet trailingSnippet()} - {#if configToEdit.machineLearning.urls.length > 1} - configToEdit.machineLearning.urls.splice(i, 1)} - icon={mdiTrashCanOutline} - color="danger" - /> - {/if} - {/snippet} - - {/each} -
+
- + + + + + + + + + + + + + + + + + + + + + + + {#snippet children({ message })} + {message} + {/snippet} + + + + + + + + + + + + - + + - + - + + - + - + + - + - - - - - + - + + + - + + + + - + - + + + - - - + + + + + + + + {/snippet}
diff --git a/web/src/routes/admin/system-settings/nightly-tasks/+page.svelte b/web/src/routes/admin/system-settings/nightly-tasks/+page.svelte index d271948998..3faf5b38c7 100644 --- a/web/src/routes/admin/system-settings/nightly-tasks/+page.svelte +++ b/web/src/routes/admin/system-settings/nightly-tasks/+page.svelte @@ -3,6 +3,7 @@ import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte'; import { SettingInputFieldType } from '$lib/constants'; import SystemSettingsModal from '$lib/modals/SystemSettingsModal.svelte'; + import { Field, Switch } from '@immich/ui'; import { t } from 'svelte-i18n'; @@ -18,12 +19,13 @@ {disabled} isEdited={!(configToEdit.nightlyTasks.startTime === config.nightlyTasks.startTime)} /> - + > + + + import SettingSelect from '$lib/components/shared-components/settings/setting-select.svelte'; + import FormatMessage from '$lib/elements/FormatMessage.svelte'; + import SystemSettingsModal from '$lib/modals/SystemSettingsModal.svelte'; + import { handleUnlinkAllOAuthAccounts, onBeforeSave } from '$lib/services/system-config.service'; + import { OAuthTokenEndpointAuthMethod, type SystemConfigDto } from '@immich/sdk'; + import { Alert, Button, Field, Input, NumberInput, Switch, Text } from '@immich/ui'; + import { t } from 'svelte-i18n'; + + const handleToggleOverride = (configToEdit: SystemConfigDto) => { + // click runs before bind + const previouslyEnabled = configToEdit.oauth.mobileOverrideEnabled; + if (!previouslyEnabled && !configToEdit.oauth.mobileRedirectUri) { + configToEdit.oauth.mobileRedirectUri = globalThis.location.origin + '/api/oauth/mobile-redirect'; + } + }; + + + + {#snippet child({ disabled, config, configToEdit })} + +
test
+
+ {$t('admin.unlink_all_oauth_accounts_description')} +
+ +
+
+
+ + + + {#snippet children({ message })} + + {message} + + {/snippet} + + + + + + + + + + + + + + + + + + + + {#if configToEdit.oauth.clientSecret} + + {/if} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + configToEdit.oauth.defaultStorageQuota ?? undefined, + (value) => (configToEdit.oauth.defaultStorageQuota = value ?? null) + } + /> + + + + + + + + + + + + + + + + handleToggleOverride(configToEdit)} + /> + + + {#if configToEdit.oauth.mobileOverrideEnabled} + + + + {/if} + {/snippet} +
diff --git a/web/src/routes/admin/system-settings/password/+page.svelte b/web/src/routes/admin/system-settings/password/+page.svelte new file mode 100644 index 0000000000..e22acfeaf2 --- /dev/null +++ b/web/src/routes/admin/system-settings/password/+page.svelte @@ -0,0 +1,14 @@ + + + + {#snippet child({ disabled, configToEdit })} + + + + {/snippet} + diff --git a/web/src/routes/admin/system-settings/server/+page.svelte b/web/src/routes/admin/system-settings/server/+page.svelte index 6c69447718..8bbc33ed03 100644 --- a/web/src/routes/admin/system-settings/server/+page.svelte +++ b/web/src/routes/admin/system-settings/server/+page.svelte @@ -3,18 +3,18 @@ import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte'; import { SettingInputFieldType } from '$lib/constants'; import SystemSettingsModal from '$lib/modals/SystemSettingsModal.svelte'; + import { Field, Input } from '@immich/ui'; import { t } from 'svelte-i18n'; {#snippet child({ disabled, config, configToEdit })} - + > + +