From f1f203719d57663563f90effd000b62681f1c8ed Mon Sep 17 00:00:00 2001 From: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com> Date: Thu, 13 Nov 2025 19:17:44 +0100 Subject: [PATCH] refactor: admin settings (#23843) --- .../admin-settings/AdminSettings.svelte | 2 +- .../admin-settings/AuthSettings.svelte | 155 ++++++++-------- .../admin-settings/BackupSettings.svelte | 52 ++---- .../admin-settings/FFmpegSettings.svelte | 121 ++++++------- .../admin-settings/ImageSettings.svelte | 99 ++++------ .../admin-settings/JobSettings.svelte | 42 ++--- .../admin-settings/LibrarySettings.svelte | 59 ++---- .../admin-settings/LoggingSettings.svelte | 41 ++--- .../MachineLearningSettings.svelte | 170 +++++++++--------- .../admin-settings/MapSettings.svelte | 51 ++---- .../admin-settings/MetadataSettings.svelte | 33 +--- .../NewVersionCheckSettings.svelte | 33 +--- .../NightlyTasksSettings.svelte | 46 ++--- .../NotificationSettings.svelte | 102 +++++------ .../admin-settings/ServerSettings.svelte | 42 ++--- .../StorageTemplateSettings.svelte | 83 ++++----- .../admin-settings/TemplateSettings.svelte | 6 +- .../admin-settings/ThemeSettings.svelte | 36 +--- .../admin-settings/TrashSettings.svelte | 44 ++--- .../admin-settings/UserSettings.svelte | 30 +--- .../admin-settings/admin-settings.ts | 11 -- .../components/album-page/album-viewer.svelte | 2 +- .../asset-viewer/actions/delete-action.svelte | 2 +- .../asset-viewer/asset-viewer-nav-bar.svelte | 2 +- .../asset-viewer/detail-panel.svelte | 2 +- web/src/lib/components/jobs/JobsPanel.svelte | 4 +- .../onboarding-page/onboarding-hello.svelte | 2 +- .../onboarding-server-privacy.svelte | 35 ++-- .../onboarding-storage-template.svelte | 32 +--- .../gallery-viewer/gallery-viewer.svelte | 2 +- .../shared-components/map/map.svelte | 2 +- .../navigation-bar/navigation-bar.svelte | 2 +- .../search-bar/search-text-section.svelte | 4 +- .../settings/SystemConfigButtonRow.svelte | 57 ++++++ .../settings/setting-buttons-row.svelte | 31 ---- .../side-bar/user-sidebar.svelte | 2 +- .../actions/DeleteAssetsAction.svelte | 2 +- .../actions/TimelineKeyboardActions.svelte | 2 +- .../user-settings-page/oauth-settings.svelte | 2 +- .../user-settings-list.svelte | 4 +- web/src/lib/managers/event-manager.svelte.ts | 10 +- web/src/lib/modals/PinCodeResetModal.svelte | 2 +- web/src/lib/modals/UserCreateModal.svelte | 2 +- .../lib/modals/UserDeleteConfirmModal.svelte | 2 +- web/src/lib/services/shared-link.service.ts | 3 +- web/src/lib/services/system-config.service.ts | 102 +++++++++++ web/src/lib/services/user-admin.service.ts | 2 +- ...ore.ts => system-config-manager.svelte.ts} | 59 +++++- web/src/lib/utils/license-utils.ts | 2 +- web/src/lib/utils/server.ts | 2 +- .../[[assetId=id]]/+page.svelte | 2 +- .../[[assetId=id]]/+page.svelte | 2 +- .../[[assetId=id]]/+page.svelte | 2 +- .../[[assetId=id]]/+page.svelte | 2 +- .../[[assetId=id]]/+page.svelte | 4 +- web/src/routes/+layout.svelte | 2 +- web/src/routes/+page.ts | 2 +- web/src/routes/admin/+layout.ts | 6 + .../routes/admin/system-settings/+page.svelte | 138 ++++---------- web/src/routes/admin/system-settings/+page.ts | 8 +- web/src/routes/auth/login/+page.svelte | 2 +- web/src/routes/auth/login/+page.ts | 2 +- web/src/routes/auth/onboarding/+page.svelte | 12 +- web/src/routes/auth/register/+page.svelte | 2 +- web/src/routes/auth/register/+page.ts | 2 +- 65 files changed, 787 insertions(+), 1036 deletions(-) create mode 100644 web/src/lib/components/shared-components/settings/SystemConfigButtonRow.svelte delete mode 100644 web/src/lib/components/shared-components/settings/setting-buttons-row.svelte create mode 100644 web/src/lib/services/system-config.service.ts rename web/src/lib/stores/{server-config.store.ts => system-config-manager.svelte.ts} (52%) create mode 100644 web/src/routes/admin/+layout.ts diff --git a/web/src/lib/components/admin-settings/AdminSettings.svelte b/web/src/lib/components/admin-settings/AdminSettings.svelte index 54be8bea96..d8fde9e29b 100644 --- a/web/src/lib/components/admin-settings/AdminSettings.svelte +++ b/web/src/lib/components/admin-settings/AdminSettings.svelte @@ -1,5 +1,5 @@
-
+ event.preventDefault()}>
diff --git a/web/src/lib/components/admin-settings/FFmpegSettings.svelte b/web/src/lib/components/admin-settings/FFmpegSettings.svelte index d176839543..ff22960b49 100644 --- a/web/src/lib/components/admin-settings/FFmpegSettings.svelte +++ b/web/src/lib/components/admin-settings/FFmpegSettings.svelte @@ -1,12 +1,13 @@
-
+ event.preventDefault()}>

@@ -75,7 +63,7 @@ label={$t('admin.transcoding_transcode_policy')} {disabled} desc={$t('admin.transcoding_transcode_policy_description')} - bind:value={config.ffmpeg.transcode} + bind:value={configToEdit.ffmpeg.transcode} name="transcode" options={[ { value: TranscodePolicy.All, text: $t('all_videos') }, @@ -96,14 +84,14 @@ text: $t('admin.transcoding_disabled_description'), }, ]} - isEdited={config.ffmpeg.transcode !== savedConfig.ffmpeg.transcode} + isEdited={configToEdit.ffmpeg.transcode !== config.ffmpeg.transcode} /> @@ -121,7 +109,7 @@ label={$t('admin.transcoding_accepted_audio_codecs')} {disabled} desc={$t('admin.transcoding_accepted_audio_codecs_description')} - bind:value={config.ffmpeg.acceptedAudioCodecs} + bind:value={configToEdit.ffmpeg.acceptedAudioCodecs} name="audioCodecs" options={[ { value: AudioCodec.Aac, text: 'AAC' }, @@ -130,8 +118,8 @@ { value: AudioCodec.PcmS16Le, text: 'PCM (16 bit)' }, ]} isEdited={!isEqual( + sortBy(configToEdit.ffmpeg.acceptedAudioCodecs), sortBy(config.ffmpeg.acceptedAudioCodecs), - sortBy(savedConfig.ffmpeg.acceptedAudioCodecs), )} /> @@ -139,7 +127,7 @@ label={$t('admin.transcoding_accepted_containers')} {disabled} desc={$t('admin.transcoding_accepted_containers_description')} - bind:value={config.ffmpeg.acceptedContainers} + bind:value={configToEdit.ffmpeg.acceptedContainers} name="videoContainers" options={[ { value: VideoContainer.Mov, text: 'MOV' }, @@ -147,8 +135,8 @@ { value: VideoContainer.Webm, text: 'WebM' }, ]} isEdited={!isEqual( + sortBy(configToEdit.ffmpeg.acceptedContainers), sortBy(config.ffmpeg.acceptedContainers), - sortBy(savedConfig.ffmpeg.acceptedContainers), )} />

@@ -164,7 +152,7 @@ label={$t('admin.transcoding_video_codec')} {disabled} desc={$t('admin.transcoding_video_codec_description')} - bind:value={config.ffmpeg.targetVideoCodec} + bind:value={configToEdit.ffmpeg.targetVideoCodec} options={[ { value: VideoCodec.H264, text: 'h264' }, { value: VideoCodec.Hevc, text: 'hevc' }, @@ -172,8 +160,8 @@ { value: VideoCodec.Av1, text: 'av1' }, ]} name="vcodec" - isEdited={config.ffmpeg.targetVideoCodec !== savedConfig.ffmpeg.targetVideoCodec} - onSelect={() => (config.ffmpeg.acceptedVideoCodecs = [config.ffmpeg.targetVideoCodec])} + isEdited={configToEdit.ffmpeg.targetVideoCodec !== config.ffmpeg.targetVideoCodec} + onSelect={() => (configToEdit.ffmpeg.acceptedVideoCodecs = [configToEdit.ffmpeg.targetVideoCodec])} /> @@ -181,25 +169,25 @@ label={$t('admin.transcoding_audio_codec')} {disabled} desc={$t('admin.transcoding_audio_codec_description')} - bind:value={config.ffmpeg.targetAudioCodec} + bind:value={configToEdit.ffmpeg.targetAudioCodec} options={[ { value: AudioCodec.Aac, text: 'aac' }, { value: AudioCodec.Mp3, text: 'mp3' }, { value: AudioCodec.Libopus, text: 'opus' }, ]} name="acodec" - isEdited={config.ffmpeg.targetAudioCodec !== savedConfig.ffmpeg.targetAudioCodec} + isEdited={configToEdit.ffmpeg.targetAudioCodec !== config.ffmpeg.targetAudioCodec} onSelect={() => - config.ffmpeg.acceptedAudioCodecs.includes(config.ffmpeg.targetAudioCodec) + configToEdit.ffmpeg.acceptedAudioCodecs.includes(configToEdit.ffmpeg.targetAudioCodec) ? null - : config.ffmpeg.acceptedAudioCodecs.push(config.ffmpeg.targetAudioCodec)} + : configToEdit.ffmpeg.acceptedAudioCodecs.push(configToEdit.ffmpeg.targetAudioCodec)} />
@@ -307,7 +295,7 @@ label={$t('admin.transcoding_acceleration_api')} {disabled} desc={$t('admin.transcoding_acceleration_api_description')} - bind:value={config.ffmpeg.accel} + bind:value={configToEdit.ffmpeg.accel} name="accel" options={[ { value: TranscodeHWAccel.Nvenc, text: $t('admin.transcoding_acceleration_nvenc') }, @@ -328,27 +316,27 @@ text: $t('disabled'), }, ]} - isEdited={config.ffmpeg.accel !== savedConfig.ffmpeg.accel} + isEdited={configToEdit.ffmpeg.accel !== config.ffmpeg.accel} /> @@ -356,16 +344,16 @@ title={$t('admin.transcoding_temporal_aq')} {disabled} subtitle={$t('admin.transcoding_temporal_aq_description')} - bind:checked={config.ffmpeg.temporalAQ} - isEdited={config.ffmpeg.temporalAQ !== savedConfig.ffmpeg.temporalAQ} + bind:checked={configToEdit.ffmpeg.temporalAQ} + isEdited={configToEdit.ffmpeg.temporalAQ !== config.ffmpeg.temporalAQ} />
@@ -381,8 +369,8 @@ inputType={SettingInputFieldType.NUMBER} label={$t('admin.transcoding_max_b_frames')} description={$t('admin.transcoding_max_b_frames_description')} - bind:value={config.ffmpeg.bframes} - isEdited={config.ffmpeg.bframes !== savedConfig.ffmpeg.bframes} + bind:value={configToEdit.ffmpeg.bframes} + isEdited={configToEdit.ffmpeg.bframes !== config.ffmpeg.bframes} {disabled} /> @@ -390,8 +378,8 @@ inputType={SettingInputFieldType.NUMBER} label={$t('admin.transcoding_reference_frames')} description={$t('admin.transcoding_reference_frames_description')} - bind:value={config.ffmpeg.refs} - isEdited={config.ffmpeg.refs !== savedConfig.ffmpeg.refs} + bind:value={configToEdit.ffmpeg.refs} + isEdited={configToEdit.ffmpeg.refs !== config.ffmpeg.refs} {disabled} /> @@ -399,8 +387,8 @@ inputType={SettingInputFieldType.NUMBER} label={$t('admin.transcoding_max_keyframe_interval')} description={$t('admin.transcoding_max_keyframe_interval_description')} - bind:value={config.ffmpeg.gopSize} - isEdited={config.ffmpeg.gopSize !== savedConfig.ffmpeg.gopSize} + bind:value={configToEdit.ffmpeg.gopSize} + isEdited={configToEdit.ffmpeg.gopSize !== config.ffmpeg.gopSize} {disabled} />
@@ -408,12 +396,7 @@
- onReset({ ...options, configKeys: ['ffmpeg'] })} - onSave={() => onSave({ ffmpeg: config.ffmpeg })} - showResetToDefault={!isEqual(savedConfig.ffmpeg, defaultConfig.ffmpeg)} - {disabled} - /> +
diff --git a/web/src/lib/components/admin-settings/ImageSettings.svelte b/web/src/lib/components/admin-settings/ImageSettings.svelte index fd2ac29c6b..d63e48d372 100644 --- a/web/src/lib/components/admin-settings/ImageSettings.svelte +++ b/web/src/lib/components/admin-settings/ImageSettings.svelte @@ -1,62 +1,40 @@
-
+ event.preventDefault()}>
@@ -64,7 +42,7 @@ label={$t('admin.image_resolution')} desc={$t('admin.image_resolution_description')} number - bind:value={config.image.thumbnail.size} + bind:value={configToEdit.image.thumbnail.size} options={[ { value: 1080, text: '1080p' }, { value: 720, text: '720p' }, @@ -73,7 +51,7 @@ { value: 200, text: '200p' }, ]} name="resolution" - isEdited={config.image.thumbnail.size !== savedConfig.image.thumbnail.size} + isEdited={configToEdit.image.thumbnail.size !== config.image.thumbnail.size} {disabled} /> @@ -81,8 +59,8 @@ inputType={SettingInputFieldType.NUMBER} label={$t('admin.image_quality')} description={$t('admin.image_thumbnail_quality_description')} - bind:value={config.image.thumbnail.quality} - isEdited={config.image.thumbnail.quality !== savedConfig.image.thumbnail.quality} + bind:value={configToEdit.image.thumbnail.quality} + isEdited={configToEdit.image.thumbnail.quality !== config.image.thumbnail.quality} {disabled} /> @@ -91,18 +69,17 @@ key="preview-settings" title={$t('admin.image_preview_title')} subtitle={$t('admin.image_preview_description')} - isOpen={openByDefault} > @@ -110,7 +87,7 @@ label={$t('admin.image_resolution')} desc={$t('admin.image_resolution_description')} number - bind:value={config.image.preview.size} + bind:value={configToEdit.image.preview.size} options={[ { value: 2160, text: '4K' }, { value: 1440, text: '1440p' }, @@ -118,7 +95,7 @@ { value: 720, text: '720p' }, ]} name="resolution" - isEdited={config.image.preview.size !== savedConfig.image.preview.size} + isEdited={configToEdit.image.preview.size !== config.image.preview.size} {disabled} /> @@ -126,8 +103,8 @@ inputType={SettingInputFieldType.NUMBER} label={$t('admin.image_quality')} description={$t('admin.image_preview_quality_description')} - bind:value={config.image.preview.quality} - isEdited={config.image.preview.quality !== savedConfig.image.preview.quality} + bind:value={configToEdit.image.preview.quality} + isEdited={configToEdit.image.preview.quality !== config.image.preview.quality} {disabled} /> @@ -136,14 +113,13 @@ key="fullsize-settings" title={$t('admin.image_fullsize_title')} subtitle={$t('admin.image_fullsize_description')} - isOpen={openByDefault} > (config.image.fullsize.enabled = isChecked)} - isEdited={config.image.fullsize.enabled !== savedConfig.image.fullsize.enabled} + checked={configToEdit.image.fullsize.enabled} + onToggle={(isChecked) => (configToEdit.image.fullsize.enabled = isChecked)} + isEdited={configToEdit.image.fullsize.enabled !== config.image.fullsize.enabled} {disabled} /> @@ -152,23 +128,23 @@ @@ -176,9 +152,9 @@ (config.image.colorspace = isChecked ? Colorspace.P3 : Colorspace.Srgb)} - isEdited={config.image.colorspace !== savedConfig.image.colorspace} + checked={configToEdit.image.colorspace === Colorspace.P3} + onToggle={(isChecked) => (configToEdit.image.colorspace = isChecked ? Colorspace.P3 : Colorspace.Srgb)} + isEdited={configToEdit.image.colorspace !== config.image.colorspace} {disabled} />
@@ -187,21 +163,16 @@ (config.image.extractEmbedded = !config.image.extractEmbedded)} - isEdited={config.image.extractEmbedded !== savedConfig.image.extractEmbedded} + checked={configToEdit.image.extractEmbedded} + onToggle={() => (configToEdit.image.extractEmbedded = !configToEdit.image.extractEmbedded)} + isEdited={configToEdit.image.extractEmbedded !== config.image.extractEmbedded} {disabled} />
- onReset({ ...options, configKeys: ['image'] })} - onSave={() => onSave({ image: config.image })} - showResetToDefault={!isEqual(savedConfig.image, defaultConfig.image)} - {disabled} - /> +
diff --git a/web/src/lib/components/admin-settings/JobSettings.svelte b/web/src/lib/components/admin-settings/JobSettings.svelte index 70de73f81b..5fd7b4117f 100644 --- a/web/src/lib/components/admin-settings/JobSettings.svelte +++ b/web/src/lib/components/admin-settings/JobSettings.svelte @@ -1,24 +1,16 @@
-
+ event.preventDefault()}> {#each jobNames as jobName (jobName)}
{#if isSystemConfigJobDto(jobName)} @@ -55,9 +42,9 @@ {disabled} label={$t('admin.job_concurrency', { values: { job: $getJobName(jobName) } })} description="" - bind:value={config.job[jobName].concurrency} + bind:value={configToEdit.job[jobName].concurrency} required={true} - isEdited={!(config.job[jobName].concurrency == savedConfig.job[jobName].concurrency)} + isEdited={!(configToEdit.job[jobName].concurrency == config.job[jobName].concurrency)} /> {:else} - onReset({ ...options, configKeys: ['job'] })} - onSave={() => onSave({ job: config.job })} - showResetToDefault={!isEqual(savedConfig.job, defaultConfig.job)} - {disabled} - /> +
diff --git a/web/src/lib/components/admin-settings/LibrarySettings.svelte b/web/src/lib/components/admin-settings/LibrarySettings.svelte index 82ce13ae2c..83fc3fdbed 100644 --- a/web/src/lib/components/admin-settings/LibrarySettings.svelte +++ b/web/src/lib/components/admin-settings/LibrarySettings.svelte @@ -1,36 +1,18 @@
-
+ event.preventDefault()}>
diff --git a/web/src/lib/components/admin-settings/LoggingSettings.svelte b/web/src/lib/components/admin-settings/LoggingSettings.svelte index 90bd04d9a6..707689d502 100644 --- a/web/src/lib/components/admin-settings/LoggingSettings.svelte +++ b/web/src/lib/components/admin-settings/LoggingSettings.svelte @@ -1,42 +1,30 @@
-
+ event.preventDefault()}>
- onReset({ ...options, configKeys: ['logging'] })} - onSave={() => onSave({ logging: config.logging })} - showResetToDefault={!isEqual(savedConfig.logging, defaultConfig.logging)} - {disabled} - /> +
diff --git a/web/src/lib/components/admin-settings/MachineLearningSettings.svelte b/web/src/lib/components/admin-settings/MachineLearningSettings.svelte index e05b5088a4..773d30f05e 100644 --- a/web/src/lib/components/admin-settings/MachineLearningSettings.svelte +++ b/web/src/lib/components/admin-settings/MachineLearningSettings.svelte @@ -1,65 +1,52 @@
-
+ event.preventDefault()}>

- {#each config.machineLearning.urls as _, i (i)} + {#each configToEdit.machineLearning.urls as _, i (i)} {#snippet trailingSnippet()} - {#if config.machineLearning.urls.length > 1} + {#if configToEdit.machineLearning.urls.length > 1} config.machineLearning.urls.splice(i, 1)} + onclick={() => configToEdit.machineLearning.urls.splice(i, 1)} icon={mdiTrashCanOutline} color="danger" /> @@ -75,8 +62,8 @@ size="small" shape="round" leadingIcon={mdiPlus} - onclick={() => config.machineLearning.urls.push('')} - disabled={disabled || !config.machineLearning.enabled}>{$t('add_url')} configToEdit.machineLearning.urls.push('')} + disabled={disabled || !configToEdit.machineLearning.enabled}>{$t('add_url')}
@@ -89,8 +76,8 @@

@@ -98,21 +85,25 @@
@@ -126,8 +117,8 @@
@@ -135,10 +126,10 @@ {#snippet descriptionSnippet()}

@@ -162,8 +153,8 @@


@@ -171,14 +162,14 @@
@@ -192,8 +183,8 @@
@@ -202,54 +193,62 @@ label={$t('admin.machine_learning_facial_recognition_model')} desc={$t('admin.machine_learning_facial_recognition_model_description')} name="facial-recognition-model" - bind:value={config.machineLearning.facialRecognition.modelName} + bind:value={configToEdit.machineLearning.facialRecognition.modelName} options={[ { value: 'antelopev2', text: 'antelopev2' }, { value: 'buffalo_l', text: 'buffalo_l' }, { value: 'buffalo_m', text: 'buffalo_m' }, { value: 'buffalo_s', text: 'buffalo_s' }, ]} - disabled={disabled || !config.machineLearning.enabled || !config.machineLearning.facialRecognition.enabled} - isEdited={config.machineLearning.facialRecognition.modelName !== - savedConfig.machineLearning.facialRecognition.modelName} + disabled={disabled || + !configToEdit.machineLearning.enabled || + !configToEdit.machineLearning.facialRecognition.enabled} + isEdited={configToEdit.machineLearning.facialRecognition.modelName !== + config.machineLearning.facialRecognition.modelName} />
@@ -263,8 +262,8 @@
@@ -273,7 +272,7 @@ label={$t('admin.machine_learning_ocr_model')} desc={$t('admin.machine_learning_ocr_model_description')} name="ocr-model" - bind:value={config.machineLearning.ocr.modelName} + bind:value={configToEdit.machineLearning.ocr.modelName} options={[ { text: 'PP-OCRv5_server (Chinese, Japanese and English)', value: 'PP-OCRv5_server' }, { text: 'PP-OCRv5_mobile (Chinese, Japanese and English)', value: 'PP-OCRv5_mobile' }, @@ -284,53 +283,48 @@ { text: 'PP-OCRv5_mobile (Russian, Belarusian, Ukrainian and English)', value: 'ESLAV__PP-OCRv5_mobile' }, { text: 'PP-OCRv5_mobile (Thai and English)', value: 'TH__PP-OCRv5_mobile' }, ]} - disabled={disabled || !config.machineLearning.enabled || !config.machineLearning.ocr.enabled} - isEdited={config.machineLearning.ocr.modelName !== savedConfig.machineLearning.ocr.modelName} + disabled={disabled || !configToEdit.machineLearning.enabled || !configToEdit.machineLearning.ocr.enabled} + isEdited={configToEdit.machineLearning.ocr.modelName !== config.machineLearning.ocr.modelName} />
- onReset({ ...options, configKeys: ['machineLearning'] })} - onSave={() => onSave({ machineLearning: config.machineLearning })} - showResetToDefault={!isEqual(savedConfig.machineLearning, defaultConfig.machineLearning)} - {disabled} - /> +
diff --git a/web/src/lib/components/admin-settings/MapSettings.svelte b/web/src/lib/components/admin-settings/MapSettings.svelte index 4db210d8dc..5ecb9f5419 100644 --- a/web/src/lib/components/admin-settings/MapSettings.svelte +++ b/web/src/lib/components/admin-settings/MapSettings.svelte @@ -1,35 +1,22 @@
-
+ event.preventDefault()}>
@@ -37,7 +24,7 @@ title={$t('admin.map_enable_description')} subtitle={$t('admin.map_implications')} {disabled} - bind:checked={config.map.enabled} + bind:checked={configToEdit.map.enabled} />
@@ -46,17 +33,17 @@ inputType={SettingInputFieldType.TEXT} label={$t('admin.map_light_style')} description={$t('admin.map_style_description')} - bind:value={config.map.lightStyle} - disabled={disabled || !config.map.enabled} - isEdited={config.map.lightStyle !== savedConfig.map.lightStyle} + bind:value={configToEdit.map.lightStyle} + disabled={disabled || !configToEdit.map.enabled} + isEdited={configToEdit.map.lightStyle !== config.map.lightStyle} />
@@ -82,20 +69,12 @@
- onReset({ ...options, configKeys: ['map', 'reverseGeocoding'] })} - onSave={() => onSave({ map: config.map, reverseGeocoding: config.reverseGeocoding })} - showResetToDefault={!isEqual( - { map: savedConfig.map, reverseGeocoding: savedConfig.reverseGeocoding }, - { map: defaultConfig.map, reverseGeocoding: defaultConfig.reverseGeocoding }, - )} - {disabled} - /> +
diff --git a/web/src/lib/components/admin-settings/MetadataSettings.svelte b/web/src/lib/components/admin-settings/MetadataSettings.svelte index 04e2d010e1..607faef51c 100644 --- a/web/src/lib/components/admin-settings/MetadataSettings.svelte +++ b/web/src/lib/components/admin-settings/MetadataSettings.svelte @@ -1,46 +1,27 @@
-
+ event.preventDefault()}>
- onReset({ ...options, configKeys: ['metadata'] })} - onSave={() => onSave({ metadata: config.metadata })} - showResetToDefault={!isEqual(savedConfig.metadata.faces.import, defaultConfig.metadata.faces.import)} - {disabled} - /> +
diff --git a/web/src/lib/components/admin-settings/NewVersionCheckSettings.svelte b/web/src/lib/components/admin-settings/NewVersionCheckSettings.svelte index b713f906c0..327bf34717 100644 --- a/web/src/lib/components/admin-settings/NewVersionCheckSettings.svelte +++ b/web/src/lib/components/admin-settings/NewVersionCheckSettings.svelte @@ -1,44 +1,25 @@
-
+ event.preventDefault()}>
- onReset({ ...options, configKeys: ['newVersionCheck'] })} - onSave={() => onSave({ newVersionCheck: config.newVersionCheck })} - showResetToDefault={!isEqual(savedConfig.newVersionCheck, defaultConfig.newVersionCheck)} + bind:checked={configToEdit.newVersionCheck.enabled} {disabled} /> +
diff --git a/web/src/lib/components/admin-settings/NightlyTasksSettings.svelte b/web/src/lib/components/admin-settings/NightlyTasksSettings.svelte index 9ba4e4e3b8..688c7cb4f0 100644 --- a/web/src/lib/components/admin-settings/NightlyTasksSettings.svelte +++ b/web/src/lib/components/admin-settings/NightlyTasksSettings.svelte @@ -1,81 +1,63 @@
-
+ event.preventDefault()}>
- onReset({ ...options, configKeys: ['nightlyTasks'] })} - onSave={() => onSave({ nightlyTasks: config.nightlyTasks })} - showResetToDefault={!isEqual(savedConfig.nightlyTasks, defaultConfig.nightlyTasks)} - {disabled} - /> +
diff --git a/web/src/lib/components/admin-settings/NotificationSettings.svelte b/web/src/lib/components/admin-settings/NotificationSettings.svelte index 35f13da5a0..0e0af55c4f 100644 --- a/web/src/lib/components/admin-settings/NotificationSettings.svelte +++ b/web/src/lib/components/admin-settings/NotificationSettings.svelte @@ -1,29 +1,22 @@
-
+ event.preventDefault()}>

@@ -87,9 +76,9 @@ required label={$t('host')} description={$t('admin.notification_email_host_description')} - disabled={disabled || !config.notifications.smtp.enabled} - bind:value={config.notifications.smtp.transport.host} - isEdited={config.notifications.smtp.transport.host !== savedConfig.notifications.smtp.transport.host} + disabled={disabled || !configToEdit.notifications.smtp.enabled} + bind:value={configToEdit.notifications.smtp.transport.host} + isEdited={configToEdit.notifications.smtp.transport.host !== config.notifications.smtp.transport.host} />
@@ -143,16 +132,16 @@ required label={$t('admin.notification_email_from_address')} description={$t('admin.notification_email_from_address_description')} - disabled={disabled || !config.notifications.smtp.enabled} - bind:value={config.notifications.smtp.from} - isEdited={config.notifications.smtp.from !== savedConfig.notifications.smtp.from} + disabled={disabled || !configToEdit.notifications.smtp.enabled} + bind:value={configToEdit.notifications.smtp.from} + isEdited={configToEdit.notifications.smtp.from !== config.notifications.smtp.from} />
- + - onReset({ ...options, configKeys: ['notifications', 'templates'] })} - onSave={() => onSave({ notifications: config.notifications, templates: config.templates })} - showResetToDefault={!isEqual(savedConfig, defaultConfig)} - {disabled} - /> +
diff --git a/web/src/lib/components/admin-settings/ServerSettings.svelte b/web/src/lib/components/admin-settings/ServerSettings.svelte index d04b351eff..4a04010cf3 100644 --- a/web/src/lib/components/admin-settings/ServerSettings.svelte +++ b/web/src/lib/components/admin-settings/ServerSettings.svelte @@ -1,64 +1,46 @@
-
+ event.preventDefault()}>
- onReset({ ...options, configKeys: ['server'] })} - onSave={() => onSave({ server: config.server })} - showResetToDefault={!isEqual(savedConfig.server, defaultConfig.server)} - {disabled} - /> +
diff --git a/web/src/lib/components/admin-settings/StorageTemplateSettings.svelte b/web/src/lib/components/admin-settings/StorageTemplateSettings.svelte index a779b57f2d..c445769ae0 100644 --- a/web/src/lib/components/admin-settings/StorageTemplateSettings.svelte +++ b/web/src/lib/components/admin-settings/StorageTemplateSettings.svelte @@ -2,50 +2,34 @@ import { resolve } from '$app/paths'; import SupportedDatetimePanel from '$lib/components/admin-settings/SupportedDatetimePanel.svelte'; import SupportedVariablesPanel from '$lib/components/admin-settings/SupportedVariablesPanel.svelte'; - import SettingButtonsRow from '$lib/components/shared-components/settings/setting-buttons-row.svelte'; + import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte'; import SettingInputField from '$lib/components/shared-components/settings/setting-input-field.svelte'; import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte'; import { AppRoute, SettingInputFieldType } from '$lib/constants'; import FormatMessage from '$lib/elements/FormatMessage.svelte'; + import { handleSystemConfigSave } from '$lib/services/system-config.service'; + import { featureFlags, systemConfigManager } from '$lib/stores/system-config-manager.svelte'; import { user } from '$lib/stores/user.store'; - import { - getStorageTemplateOptions, - type SystemConfigDto, - type SystemConfigTemplateStorageOptionDto, - } from '@immich/sdk'; + import { getStorageTemplateOptions, type SystemConfigTemplateStorageOptionDto } from '@immich/sdk'; import { LoadingSpinner } from '@immich/ui'; import handlebar from 'handlebars'; - import { isEqual } from 'lodash-es'; import * as luxon from 'luxon'; - import type { Snippet } from 'svelte'; + import { onDestroy } from 'svelte'; import { t } from 'svelte-i18n'; import { createBubbler, preventDefault } from 'svelte/legacy'; import { fade } from 'svelte/transition'; - import type { SettingsResetEvent, SettingsSaveEvent } from './admin-settings'; - interface Props { - savedConfig: SystemConfigDto; - defaultConfig: SystemConfigDto; - config: SystemConfigDto; - disabled?: boolean; + type Props = { minified?: boolean; - onReset: SettingsResetEvent; - onSave: SettingsSaveEvent; duration?: number; - children?: Snippet; - } + saveOnClose?: boolean; + }; - let { - savedConfig, - defaultConfig, - config = $bindable(), - disabled = false, - minified = false, - onReset, - onSave, - duration = 500, - children, - }: Props = $props(); + const { minified = false, duration = 500, saveOnClose = false }: Props = $props(); + + const disabled = $featureFlags.configFile; + const config = $derived(systemConfigManager.value); + let configToEdit = $state(systemConfigManager.cloneValue()); const bubble = createBubbler(); let templateOptions: SystemConfigTemplateStorageOptionDto | undefined = $state(); @@ -53,7 +37,7 @@ const getTemplateOptions = async () => { templateOptions = await getStorageTemplateOptions(); - selectedPreset = savedConfig.storageTemplate.template; + selectedPreset = config.storageTemplate.template; }; const getSupportDateTimeFormat = () => getStorageTemplateOptions(); @@ -101,15 +85,21 @@ }; const handlePresetSelection = () => { - config.storageTemplate.template = selectedPreset; + configToEdit.storageTemplate.template = selectedPreset; }; let parsedTemplate = $derived(() => { try { - return renderTemplate(config.storageTemplate.template); + return renderTemplate(configToEdit.storageTemplate.template); } catch { return 'error'; } }); + + onDestroy(async () => { + if (saveOnClose) { + await handleSystemConfigSave({ storageTemplate: configToEdit.storageTemplate }); + } + });
@@ -145,8 +135,8 @@ {#if !minified} @@ -154,14 +144,14 @@ title={$t('admin.storage_template_hash_verification_enabled')} {disabled} subtitle={$t('admin.storage_template_hash_verification_enabled_description')} - bind:checked={config.storageTemplate.hashVerificationEnabled} + bind:checked={configToEdit.storageTemplate.hashVerificationEnabled} isEdited={!( - config.storageTemplate.hashVerificationEnabled === savedConfig.storageTemplate.hashVerificationEnabled + configToEdit.storageTemplate.hashVerificationEnabled === config.storageTemplate.hashVerificationEnabled )} /> {/if} - {#if config.storageTemplate.enabled} + {#if configToEdit.storageTemplate.enabled}

{$t('variables')}

@@ -220,7 +210,7 @@ + const { CopyToClipboard, Upload, Download } = $derived( + getSystemConfigActions($t, $featureFlags, systemConfigManager.value), + ); + {#snippet buttons()} @@ -256,58 +211,27 @@ - - - {#if !$featureFlags.configFile} - - {/if} + + + {/snippet} - - {#snippet children({ savedConfig, defaultConfig })} -
-
- {#if $featureFlags.configFile} - - {/if} -
- -
- - {#each filteredSettings as { component: Component, title, subtitle, key, icon } (key)} - - adminSettingElement?.handleSave(config)} - onReset={(options) => adminSettingElement?.handleReset(options)} - disabled={$featureFlags.configFile} - bind:config - {defaultConfig} - {savedConfig} - /> - - {/each} - -
-
- {/snippet} -
+
+
+ {#if $featureFlags.configFile} + + {/if} +
+ +
+ + {#each filteredSettings as { component: Component, title, subtitle, key, icon } (key)} + + + + {/each} + +
+
diff --git a/web/src/routes/admin/system-settings/+page.ts b/web/src/routes/admin/system-settings/+page.ts index 294096a4be..10dc0cf246 100644 --- a/web/src/routes/admin/system-settings/+page.ts +++ b/web/src/routes/admin/system-settings/+page.ts @@ -1,15 +1,17 @@ import { authenticate } from '$lib/utils/auth'; import { getFormatter } from '$lib/utils/i18n'; -import { getConfig } from '@immich/sdk'; +import { getConfig, getConfigDefaults } from '@immich/sdk'; import type { PageLoad } from './$types'; export const load = (async ({ url }) => { await authenticate(url, { admin: true }); - const configs = await getConfig(); + const config = await getConfig(); + const defaultConfig = await getConfigDefaults(); const $t = await getFormatter(); return { - configs, + config, + defaultConfig, meta: { title: $t('admin.system_settings'), }, diff --git a/web/src/routes/auth/login/+page.svelte b/web/src/routes/auth/login/+page.svelte index 352eaed408..2943dc1e07 100644 --- a/web/src/routes/auth/login/+page.svelte +++ b/web/src/routes/auth/login/+page.svelte @@ -3,7 +3,7 @@ import AuthPageLayout from '$lib/components/layouts/AuthPageLayout.svelte'; import { AppRoute } from '$lib/constants'; import { eventManager } from '$lib/managers/event-manager.svelte'; - import { featureFlags, serverConfig } from '$lib/stores/server-config.store'; + import { featureFlags, serverConfig } from '$lib/stores/system-config-manager.svelte'; import { oauth } from '$lib/utils'; import { getServerErrorMessage, handleError } from '$lib/utils/handle-error'; import { login, type LoginResponseDto } from '@immich/sdk'; diff --git a/web/src/routes/auth/login/+page.ts b/web/src/routes/auth/login/+page.ts index 54c5da716a..ddf5b43fb9 100644 --- a/web/src/routes/auth/login/+page.ts +++ b/web/src/routes/auth/login/+page.ts @@ -1,5 +1,5 @@ import { AppRoute } from '$lib/constants'; -import { serverConfig } from '$lib/stores/server-config.store'; +import { serverConfig } from '$lib/stores/system-config-manager.svelte'; import { getFormatter } from '$lib/utils/i18n'; import { redirect } from '@sveltejs/kit'; diff --git a/web/src/routes/auth/onboarding/+page.svelte b/web/src/routes/auth/onboarding/+page.svelte index d2e9a9f240..9275fb95c1 100644 --- a/web/src/routes/auth/onboarding/+page.svelte +++ b/web/src/routes/auth/onboarding/+page.svelte @@ -12,7 +12,7 @@ import OnboardingUserPrivacy from '$lib/components/onboarding-page/onboarding-user-privacy.svelte'; import { AppRoute, QueryParameter } from '$lib/constants'; import { OnboardingRole } from '$lib/models/onboarding-role'; - import { retrieveServerConfig, retrieveSystemConfig, serverConfig } from '$lib/stores/server-config.store'; + import { retrieveServerConfig, serverConfig, systemConfigManager } from '$lib/stores/system-config-manager.svelte'; import { user } from '$lib/stores/user.store'; import { setUserOnboarding, updateAdminOnboarding } from '@immich/sdk'; import { @@ -152,11 +152,13 @@ ); }; - onMount(async () => { - await retrieveSystemConfig(); - }); - const OnboardingStep = $derived(onboardingSteps[index].component); + + onMount(async () => { + if (userRole === OnboardingRole.SERVER) { + await systemConfigManager.init(); + } + });
diff --git a/web/src/routes/auth/register/+page.svelte b/web/src/routes/auth/register/+page.svelte index 3eb046e80f..affa5f816c 100644 --- a/web/src/routes/auth/register/+page.svelte +++ b/web/src/routes/auth/register/+page.svelte @@ -2,7 +2,7 @@ import { goto } from '$app/navigation'; import AuthPageLayout from '$lib/components/layouts/AuthPageLayout.svelte'; import { AppRoute } from '$lib/constants'; - import { retrieveServerConfig } from '$lib/stores/server-config.store'; + import { retrieveServerConfig } from '$lib/stores/system-config-manager.svelte'; import { handleError } from '$lib/utils/handle-error'; import { signUpAdmin } from '@immich/sdk'; import { Alert, Button, Field, Input, PasswordInput, Text } from '@immich/ui'; diff --git a/web/src/routes/auth/register/+page.ts b/web/src/routes/auth/register/+page.ts index 88b56caa47..30969c3167 100644 --- a/web/src/routes/auth/register/+page.ts +++ b/web/src/routes/auth/register/+page.ts @@ -1,5 +1,5 @@ import { AppRoute } from '$lib/constants'; -import { serverConfig } from '$lib/stores/server-config.store'; +import { serverConfig } from '$lib/stores/system-config-manager.svelte'; import { getFormatter } from '$lib/utils/i18n'; import { redirect } from '@sveltejs/kit'; import { get } from 'svelte/store';