Compare commits

...

11 commits

Author SHA1 Message Date
martabal
1cb58728b2
merge main 2023-08-29 17:21:32 +02:00
martabal
406754195b
update machine learning 2023-08-29 17:12:12 +02:00
martabal
1d544c8c4d
fix: merge 2023-08-28 15:19:00 +02:00
martabal
7226d12d5d
merge main 2023-08-28 14:33:39 +02:00
martabal
8b7a4f2169
merge main 2023-08-28 14:32:13 +02:00
martabal
77fb3e9ce2
pr feedback 2023-08-19 09:35:37 +02:00
martabal
4e98b1001b
fix: missing changes for job 2023-08-17 09:56:37 +02:00
martabal
6e2ba7acab
merge main 2023-08-16 21:19:17 +02:00
martabal
3674e5b858
pr feedback 2023-08-16 21:13:18 +02:00
martabal
4b1e1f6e83
feat: move the logic to the server 2023-08-15 21:56:05 +02:00
martabal
0e4b00d07a
refactor(web): system-settings 2023-08-14 16:05:09 +02:00
9 changed files with 846 additions and 980 deletions

View file

@ -3,79 +3,35 @@
notificationController, notificationController,
NotificationType, NotificationType,
} from '$lib/components/shared-components/notification/notification'; } from '$lib/components/shared-components/notification/notification';
import { import { AudioCodec, SystemConfigFFmpegDto, ToneMapping, TranscodeHWAccel, TranscodePolicy, VideoCodec } from '@api';
api,
AudioCodec,
SystemConfigFFmpegDto,
ToneMapping,
TranscodeHWAccel,
TranscodePolicy,
VideoCodec,
} from '@api';
import SettingButtonsRow from '../setting-buttons-row.svelte'; import SettingButtonsRow from '../setting-buttons-row.svelte';
import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte'; import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte';
import SettingSelect from '../setting-select.svelte'; import SettingSelect from '../setting-select.svelte';
import SettingSwitch from '../setting-switch.svelte'; import SettingSwitch from '../setting-switch.svelte';
import { isEqual } from 'lodash-es'; import { isEqual } from 'lodash-es';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import { createEventDispatcher } from 'svelte';
export let ffmpegConfig: SystemConfigFFmpegDto; // this is the config that is being edited export let ffmpegConfig: SystemConfigFFmpegDto; // this is the config that is being edited
export let ffmpegDefault: SystemConfigFFmpegDto;
export let savedConfig: SystemConfigFFmpegDto;
export let disabled = false; export let disabled = false;
let savedConfig: SystemConfigFFmpegDto; const dispatch = createEventDispatcher<{
let defaultConfig: SystemConfigFFmpegDto; save: SystemConfigFFmpegDto;
}>();
async function getConfigs() {
[savedConfig, defaultConfig] = await Promise.all([
api.systemConfigApi.getConfig().then((res) => res.data.ffmpeg),
api.systemConfigApi.getDefaults().then((res) => res.data.ffmpeg),
]);
}
async function saveSetting() {
try {
const { data: configs } = await api.systemConfigApi.getConfig();
const result = await api.systemConfigApi.updateConfig({
systemConfigDto: {
...configs,
ffmpeg: ffmpegConfig,
},
});
ffmpegConfig = { ...result.data.ffmpeg };
savedConfig = { ...result.data.ffmpeg };
notificationController.show({
message: 'FFmpeg settings saved',
type: NotificationType.Info,
});
} catch (e) {
console.error('Error [ffmpeg-settings] [saveSetting]', e);
notificationController.show({
message: 'Unable to save settings',
type: NotificationType.Error,
});
}
}
async function reset() { async function reset() {
const { data: resetConfig } = await api.systemConfigApi.getConfig(); ffmpegConfig = { ...savedConfig };
ffmpegConfig = { ...resetConfig.ffmpeg };
savedConfig = { ...resetConfig.ffmpeg };
notificationController.show({ notificationController.show({
message: 'Reset FFmpeg settings to the recent saved settings', message: 'Reset FFmpeg settings to the last saved settings',
type: NotificationType.Info, type: NotificationType.Info,
}); });
} }
async function resetToDefault() { async function resetToDefault() {
const { data: configs } = await api.systemConfigApi.getDefaults(); ffmpegConfig = { ...ffmpegDefault };
ffmpegConfig = { ...configs.ffmpeg };
defaultConfig = { ...configs.ffmpeg };
notificationController.show({ notificationController.show({
message: 'Reset FFmpeg settings to default', message: 'Reset FFmpeg settings to default',
@ -85,8 +41,7 @@
</script> </script>
<div> <div>
{#await getConfigs() then} <div in:fade={{ duration: 300 }}>
<div in:fade={{ duration: 500 }}>
<form autocomplete="off" on:submit|preventDefault> <form autocomplete="off" on:submit|preventDefault>
<div class="ml-4 mt-4 flex flex-col gap-4"> <div class="ml-4 mt-4 flex flex-col gap-4">
<SettingInputField <SettingInputField
@ -269,13 +224,12 @@
<div class="ml-4"> <div class="ml-4">
<SettingButtonsRow <SettingButtonsRow
on:reset={reset} on:reset={reset}
on:save={saveSetting} on:save={() => dispatch('save', ffmpegConfig)}
on:reset-to-default={resetToDefault} on:reset-to-default={resetToDefault}
showResetToDefault={!isEqual(savedConfig, defaultConfig)} showResetToDefault={!isEqual(ffmpegConfig, ffmpegDefault)}
{disabled} {disabled}
/> />
</div> </div>
</form> </form>
</div> </div>
{/await}
</div> </div>

View file

@ -6,51 +6,24 @@
import { api, JobName, SystemConfigJobDto } from '@api'; import { api, JobName, SystemConfigJobDto } from '@api';
import { isEqual } from 'lodash-es'; import { isEqual } from 'lodash-es';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import { handleError } from '../../../../utils/handle-error';
import SettingButtonsRow from '../setting-buttons-row.svelte'; import SettingButtonsRow from '../setting-buttons-row.svelte';
import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte'; import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte';
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher<{
save: SystemConfigJobDto;
}>();
export let jobConfig: SystemConfigJobDto; // this is the config that is being edited export let jobConfig: SystemConfigJobDto; // this is the config that is being edited
export let jobDefault: SystemConfigJobDto;
export let savedConfig: SystemConfigJobDto;
export let disabled = false; export let disabled = false;
let savedConfig: SystemConfigJobDto;
let defaultConfig: SystemConfigJobDto;
const ignoredJobs = [JobName.BackgroundTask, JobName.Search] as JobName[]; const ignoredJobs = [JobName.BackgroundTask, JobName.Search] as JobName[];
const jobNames = Object.values(JobName).filter((jobName) => !ignoredJobs.includes(jobName as JobName)); const jobNames = Object.values(JobName).filter((jobName) => !ignoredJobs.includes(jobName as JobName));
async function getConfigs() {
[savedConfig, defaultConfig] = await Promise.all([
api.systemConfigApi.getConfig().then((res) => res.data.job),
api.systemConfigApi.getDefaults().then((res) => res.data.job),
]);
}
async function saveSetting() {
try {
const { data: configs } = await api.systemConfigApi.getConfig();
const result = await api.systemConfigApi.updateConfig({
systemConfigDto: {
...configs,
job: jobConfig,
},
});
jobConfig = { ...result.data.job };
savedConfig = { ...result.data.job };
notificationController.show({ message: 'Job settings saved', type: NotificationType.Info });
} catch (error) {
handleError(error, 'Unable to save settings');
}
}
async function reset() { async function reset() {
const { data: resetConfig } = await api.systemConfigApi.getConfig(); jobConfig = { ...savedConfig };
jobConfig = { ...resetConfig.job };
savedConfig = { ...resetConfig.job };
notificationController.show({ notificationController.show({
message: 'Reset Job settings to the recent saved settings', message: 'Reset Job settings to the recent saved settings',
@ -59,10 +32,7 @@
} }
async function resetToDefault() { async function resetToDefault() {
const { data: configs } = await api.systemConfigApi.getDefaults(); jobConfig = { ...jobDefault };
jobConfig = { ...configs.job };
defaultConfig = { ...configs.job };
notificationController.show({ notificationController.show({
message: 'Reset Job settings to default', message: 'Reset Job settings to default',
@ -72,8 +42,7 @@
</script> </script>
<div> <div>
{#await getConfigs() then} <div in:fade={{ duration: 300 }}>
<div in:fade={{ duration: 500 }}>
<form autocomplete="off" on:submit|preventDefault> <form autocomplete="off" on:submit|preventDefault>
{#each jobNames as jobName} {#each jobNames as jobName}
<div class="ml-4 mt-4 flex flex-col gap-4"> <div class="ml-4 mt-4 flex flex-col gap-4">
@ -92,13 +61,11 @@
<div class="ml-4"> <div class="ml-4">
<SettingButtonsRow <SettingButtonsRow
on:reset={reset} on:reset={reset}
on:save={saveSetting} on:save={() => dispatch('save', jobConfig)}
on:reset-to-default={resetToDefault} on:reset-to-default={resetToDefault}
showResetToDefault={!isEqual(savedConfig, defaultConfig)} showResetToDefault={!isEqual(jobConfig, jobDefault)}
{disabled}
/> />
</div> </div>
</form> </form>
</div> </div>
{/await}
</div> </div>

View file

@ -3,60 +3,38 @@
notificationController, notificationController,
NotificationType, NotificationType,
} from '$lib/components/shared-components/notification/notification'; } from '$lib/components/shared-components/notification/notification';
import { handleError } from '$lib/utils/handle-error';
import { api, SystemConfigMachineLearningDto } from '@api';
import { isEqual } from 'lodash-es'; import { isEqual } from 'lodash-es';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import SettingButtonsRow from '../setting-buttons-row.svelte'; import SettingButtonsRow from '../setting-buttons-row.svelte';
import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte'; import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte';
import SettingSwitch from '../setting-switch.svelte'; import SettingSwitch from '../setting-switch.svelte';
import type { SystemConfigMachineLearningDto } from '@api';
import { createEventDispatcher } from 'svelte';
import SettingAccordion from '../setting-accordion.svelte'; import SettingAccordion from '../setting-accordion.svelte';
import SettingSelect from '../setting-select.svelte'; import SettingSelect from '../setting-select.svelte';
export let machineLearningConfig: SystemConfigMachineLearningDto; // this is the config that is being edited
export let disabled = false; export let disabled = false;
let savedConfig: SystemConfigMachineLearningDto; export let machineLearningConfig: SystemConfigMachineLearningDto; // this is the config that is being edited
let defaultConfig: SystemConfigMachineLearningDto; export let machineLearningDefault: SystemConfigMachineLearningDto;
export let savedConfig: SystemConfigMachineLearningDto;
async function refreshConfig() { const dispatch = createEventDispatcher<{
[savedConfig, defaultConfig] = await Promise.all([ save: SystemConfigMachineLearningDto;
api.systemConfigApi.getConfig().then((res) => res.data.machineLearning), }>();
api.systemConfigApi.getDefaults().then((res) => res.data.machineLearning),
]);
}
async function reset() { async function reset() {
const { data: resetConfig } = await api.systemConfigApi.getConfig(); machineLearningConfig = { ...savedConfig };
machineLearningConfig = { ...resetConfig.machineLearning };
savedConfig = { ...resetConfig.machineLearning };
notificationController.show({ message: 'Reset to the last saved settings', type: NotificationType.Info }); notificationController.show({ message: 'Reset to the last saved settings', type: NotificationType.Info });
} }
async function saveSetting() {
try {
const { data: current } = await api.systemConfigApi.getConfig();
const result = await api.systemConfigApi.updateConfig({
systemConfigDto: { ...current, machineLearning: machineLearningConfig },
});
machineLearningConfig = { ...result.data.machineLearning };
savedConfig = { ...result.data.machineLearning };
notificationController.show({ message: 'Settings saved', type: NotificationType.Info });
} catch (error) {
handleError(error, 'Unable to save settings');
}
}
async function resetToDefault() { async function resetToDefault() {
machineLearningConfig = { ...defaultConfig }; machineLearningConfig = { ...machineLearningDefault };
notificationController.show({ message: 'Reset settings to defaults', type: NotificationType.Info }); notificationController.show({ message: 'Reset settings to defaults', type: NotificationType.Info });
} }
</script> </script>
<div class="mt-2"> <div class="mt-2">
{#await refreshConfig() then}
<div in:fade={{ duration: 500 }}> <div in:fade={{ duration: 500 }}>
<form autocomplete="off" on:submit|preventDefault class="mx-4 mt-4"> <form autocomplete="off" on:submit|preventDefault class="mx-4 mt-4">
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
@ -193,20 +171,18 @@
min="0" min="0"
max="2" max="2"
disabled={disabled || !machineLearningConfig.enabled || !machineLearningConfig.facialRecognition.enabled} disabled={disabled || !machineLearningConfig.enabled || !machineLearningConfig.facialRecognition.enabled}
isEdited={machineLearningConfig.facialRecognition.maxDistance !== isEdited={machineLearningConfig.facialRecognition.maxDistance !== savedConfig.facialRecognition.maxDistance}
savedConfig.facialRecognition.maxDistance}
/> />
</div> </div>
</SettingAccordion> </SettingAccordion>
<SettingButtonsRow <SettingButtonsRow
on:reset={reset} on:reset={reset}
on:save={saveSetting} on:save={() => dispatch('save', machineLearningConfig)}
on:reset-to-default={resetToDefault} on:reset-to-default={resetToDefault}
showResetToDefault={!isEqual(savedConfig, defaultConfig)} showResetToDefault={!isEqual(savedConfig, machineLearningConfig)}
{disabled} {disabled}
/> />
</form> </form>
</div> </div>
{/await}
</div> </div>

View file

@ -3,21 +3,25 @@
notificationController, notificationController,
NotificationType, NotificationType,
} from '$lib/components/shared-components/notification/notification'; } from '$lib/components/shared-components/notification/notification';
import { handleError } from '$lib/utils/handle-error'; import type { SystemConfigDto, SystemConfigOAuthDto } from '@api';
import { api, SystemConfigOAuthDto } from '@api';
import { isEqual } from 'lodash-es'; import { isEqual } from 'lodash-es';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import ConfirmDisableLogin from '../confirm-disable-login.svelte'; import ConfirmDisableLogin from '../confirm-disable-login.svelte';
import SettingButtonsRow from '../setting-buttons-row.svelte'; import SettingButtonsRow from '../setting-buttons-row.svelte';
import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte'; import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte';
import SettingSwitch from '../setting-switch.svelte'; import SettingSwitch from '../setting-switch.svelte';
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher<{
save: SystemConfigOAuthDto;
}>();
export let config: SystemConfigDto;
export let oauthConfig: SystemConfigOAuthDto; export let oauthConfig: SystemConfigOAuthDto;
export let oauthDefault: SystemConfigOAuthDto;
export let savedConfig: SystemConfigOAuthDto;
export let disabled = false; export let disabled = false;
let savedConfig: SystemConfigOAuthDto;
let defaultConfig: SystemConfigOAuthDto;
const handleToggleOverride = () => { const handleToggleOverride = () => {
// click runs before bind // click runs before bind
const previouslyEnabled = oauthConfig.mobileOverrideEnabled; const previouslyEnabled = oauthConfig.mobileOverrideEnabled;
@ -25,19 +29,8 @@
oauthConfig.mobileRedirectUri = window.location.origin + '/api/oauth/mobile-redirect'; oauthConfig.mobileRedirectUri = window.location.origin + '/api/oauth/mobile-redirect';
} }
}; };
async function getConfigs() {
[savedConfig, defaultConfig] = await Promise.all([
api.systemConfigApi.getConfig().then((res) => res.data.oauth),
api.systemConfigApi.getDefaults().then((res) => res.data.oauth),
]);
}
async function reset() { async function reset() {
const { data: resetConfig } = await api.systemConfigApi.getConfig(); oauthConfig = { ...savedConfig };
oauthConfig = { ...resetConfig.oauth };
savedConfig = { ...resetConfig.oauth };
notificationController.show({ notificationController.show({
message: 'Reset OAuth settings to the last saved settings', message: 'Reset OAuth settings to the last saved settings',
@ -51,6 +44,9 @@
const openConfirmModal = () => { const openConfirmModal = () => {
return new Promise((resolve) => { return new Promise((resolve) => {
handleConfirm = (value: boolean) => { handleConfirm = (value: boolean) => {
if (!value) {
oauthConfig.enabled = !oauthConfig.enabled;
}
isConfirmOpen = false; isConfirmOpen = false;
resolve(value); resolve(value);
}; };
@ -59,10 +55,7 @@
}; };
async function saveSetting() { async function saveSetting() {
try { if (!config.passwordLogin.enabled && savedConfig.enabled && !oauthConfig.enabled) {
const { data: current } = await api.systemConfigApi.getConfig();
if (!current.passwordLogin.enabled && current.oauth.enabled && !oauthConfig.enabled) {
const confirmed = await openConfirmModal(); const confirmed = await openConfirmModal();
if (!confirmed) { if (!confirmed) {
return; return;
@ -73,26 +66,11 @@
oauthConfig.mobileRedirectUri = ''; oauthConfig.mobileRedirectUri = '';
} }
const { data: updated } = await api.systemConfigApi.updateConfig({ dispatch('save', oauthConfig);
systemConfigDto: {
...current,
oauth: oauthConfig,
},
});
oauthConfig = { ...updated.oauth };
savedConfig = { ...updated.oauth };
notificationController.show({ message: 'OAuth settings saved', type: NotificationType.Info });
} catch (error) {
handleError(error, 'Unable to save OAuth settings');
}
} }
async function resetToDefault() { async function resetToDefault() {
const { data: defaultConfig } = await api.systemConfigApi.getDefaults(); oauthConfig = { ...oauthDefault };
oauthConfig = { ...defaultConfig.oauth };
notificationController.show({ notificationController.show({
message: 'Reset OAuth settings to default', message: 'Reset OAuth settings to default',
@ -106,8 +84,7 @@
{/if} {/if}
<div class="mt-2"> <div class="mt-2">
{#await getConfigs() then} <div in:fade={{ duration: 300 }}>
<div in:fade={{ duration: 500 }}>
<form autocomplete="off" on:submit|preventDefault class="mx-4 flex flex-col gap-4 py-4"> <form autocomplete="off" on:submit|preventDefault class="mx-4 flex flex-col gap-4 py-4">
<p class="text-sm dark:text-immich-dark-fg"> <p class="text-sm dark:text-immich-dark-fg">
For more details about this feature, refer to the <a For more details about this feature, refer to the <a
@ -118,14 +95,14 @@
>. >.
</p> </p>
<SettingSwitch {disabled} title="ENABLE" bind:checked={oauthConfig.enabled} /> <SettingSwitch title="ENABLE" bind:checked={oauthConfig.enabled} />
<hr /> <hr />
<SettingInputField <SettingInputField
inputType={SettingInputFieldType.TEXT} inputType={SettingInputFieldType.TEXT}
label="ISSUER URL" label="ISSUER URL"
bind:value={oauthConfig.issuerUrl} bind:value={oauthConfig.issuerUrl}
required={true} required={true}
disabled={disabled || !oauthConfig.enabled} disabled={!oauthConfig.enabled}
isEdited={!(oauthConfig.issuerUrl == savedConfig.issuerUrl)} isEdited={!(oauthConfig.issuerUrl == savedConfig.issuerUrl)}
/> />
@ -134,7 +111,7 @@
label="CLIENT ID" label="CLIENT ID"
bind:value={oauthConfig.clientId} bind:value={oauthConfig.clientId}
required={true} required={true}
disabled={disabled || !oauthConfig.enabled} disabled={!oauthConfig.enabled}
isEdited={!(oauthConfig.clientId == savedConfig.clientId)} isEdited={!(oauthConfig.clientId == savedConfig.clientId)}
/> />
@ -179,7 +156,7 @@
title="AUTO REGISTER" title="AUTO REGISTER"
subtitle="Automatically register new users after signing in with OAuth" subtitle="Automatically register new users after signing in with OAuth"
bind:checked={oauthConfig.autoRegister} bind:checked={oauthConfig.autoRegister}
disabled={disabled || !oauthConfig.enabled} disabled={!oauthConfig.enabled}
/> />
<SettingSwitch <SettingSwitch
@ -212,10 +189,9 @@
on:reset={reset} on:reset={reset}
on:save={saveSetting} on:save={saveSetting}
on:reset-to-default={resetToDefault} on:reset-to-default={resetToDefault}
showResetToDefault={!isEqual(savedConfig, defaultConfig)} showResetToDefault={!isEqual(oauthConfig, oauthDefault)}
{disabled} {disabled}
/> />
</form> </form>
</div> </div>
{/await}
</div> </div>

View file

@ -3,33 +3,33 @@
notificationController, notificationController,
NotificationType, NotificationType,
} from '$lib/components/shared-components/notification/notification'; } from '$lib/components/shared-components/notification/notification';
import { handleError } from '$lib/utils/handle-error'; import type { SystemConfigDto, SystemConfigPasswordLoginDto } from '@api';
import { api, SystemConfigPasswordLoginDto } from '@api';
import { isEqual } from 'lodash-es'; import { isEqual } from 'lodash-es';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import ConfirmDisableLogin from '../confirm-disable-login.svelte'; import ConfirmDisableLogin from '../confirm-disable-login.svelte';
import SettingButtonsRow from '../setting-buttons-row.svelte'; import SettingButtonsRow from '../setting-buttons-row.svelte';
import SettingSwitch from '../setting-switch.svelte'; import SettingSwitch from '../setting-switch.svelte';
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher<{
save: SystemConfigPasswordLoginDto;
}>();
export let config: SystemConfigDto;
export let passwordLoginConfig: SystemConfigPasswordLoginDto; // this is the config that is being edited export let passwordLoginConfig: SystemConfigPasswordLoginDto; // this is the config that is being edited
export let passwordLoginDefault: SystemConfigPasswordLoginDto;
export let savedConfig: SystemConfigPasswordLoginDto;
export let disabled = false; export let disabled = false;
let savedConfig: SystemConfigPasswordLoginDto;
let defaultConfig: SystemConfigPasswordLoginDto;
async function getConfigs() {
[savedConfig, defaultConfig] = await Promise.all([
api.systemConfigApi.getConfig().then((res) => res.data.passwordLogin),
api.systemConfigApi.getDefaults().then((res) => res.data.passwordLogin),
]);
}
let isConfirmOpen = false; let isConfirmOpen = false;
let handleConfirm: (value: boolean) => void; let handleConfirm: (value: boolean) => void;
const openConfirmModal = () => { const openConfirmModal = () => {
return new Promise((resolve) => { return new Promise((resolve) => {
handleConfirm = (value: boolean) => { handleConfirm = (value: boolean) => {
if (!value) {
passwordLoginConfig.enabled = !passwordLoginConfig.enabled;
}
isConfirmOpen = false; isConfirmOpen = false;
resolve(value); resolve(value);
}; };
@ -38,52 +38,30 @@
}; };
async function saveSetting() { async function saveSetting() {
try { if (!config.oauth.enabled && savedConfig.enabled && !passwordLoginConfig.enabled) {
const { data: current } = await api.systemConfigApi.getConfig();
if (!current.oauth.enabled && current.passwordLogin.enabled && !passwordLoginConfig.enabled) {
const confirmed = await openConfirmModal(); const confirmed = await openConfirmModal();
if (!confirmed) { if (!confirmed) {
return; return;
} }
} }
const { data: updated } = await api.systemConfigApi.updateConfig({ dispatch('save', passwordLoginConfig);
systemConfigDto: {
...current,
passwordLogin: passwordLoginConfig,
},
});
passwordLoginConfig = { ...updated.passwordLogin };
savedConfig = { ...updated.passwordLogin };
notificationController.show({ message: 'Settings saved', type: NotificationType.Info });
} catch (error) {
handleError(error, 'Unable to save settings');
}
} }
async function reset() { async function reset() {
const { data: resetConfig } = await api.systemConfigApi.getConfig(); passwordLoginConfig = { ...savedConfig };
passwordLoginConfig = { ...resetConfig.passwordLogin };
savedConfig = { ...resetConfig.passwordLogin };
notificationController.show({ notificationController.show({
message: 'Reset settings to the recent saved settings', message: 'Reset password authentication settings to the last saved settings',
type: NotificationType.Info, type: NotificationType.Info,
}); });
} }
async function resetToDefault() { async function resetToDefault() {
const { data: configs } = await api.systemConfigApi.getDefaults(); passwordLoginConfig = { ...passwordLoginDefault };
passwordLoginConfig = { ...configs.passwordLogin };
defaultConfig = { ...configs.passwordLogin };
notificationController.show({ notificationController.show({
message: 'Reset password settings to default', message: 'Reset password authentication settings to default',
type: NotificationType.Info, type: NotificationType.Info,
}); });
} }
@ -94,8 +72,7 @@
{/if} {/if}
<div> <div>
{#await getConfigs() then} <div in:fade={{ duration: 300 }}>
<div in:fade={{ duration: 500 }}>
<form autocomplete="off" on:submit|preventDefault> <form autocomplete="off" on:submit|preventDefault>
<div class="ml-4 mt-4 flex flex-col gap-4"> <div class="ml-4 mt-4 flex flex-col gap-4">
<div class="ml-4"> <div class="ml-4">
@ -103,6 +80,7 @@
title="ENABLED" title="ENABLED"
{disabled} {disabled}
subtitle="Login with email and password" subtitle="Login with email and password"
isEdited={!(passwordLoginConfig.enabled == savedConfig.enabled)}
bind:checked={passwordLoginConfig.enabled} bind:checked={passwordLoginConfig.enabled}
/> />
@ -110,12 +88,11 @@
on:reset={reset} on:reset={reset}
on:save={saveSetting} on:save={saveSetting}
on:reset-to-default={resetToDefault} on:reset-to-default={resetToDefault}
showResetToDefault={!isEqual(savedConfig, defaultConfig)} showResetToDefault={!isEqual(passwordLoginConfig, passwordLoginDefault)}
{disabled} {disabled}
/> />
</div> </div>
</div> </div>
</form> </form>
</div> </div>
{/await}
</div> </div>

View file

@ -1,10 +1,7 @@
<script lang="ts"> <script lang="ts">
import { api, SystemConfigStorageTemplateDto, SystemConfigTemplateStorageOptionDto, UserResponseDto } from '@api'; import type { SystemConfigStorageTemplateDto, SystemConfigTemplateStorageOptionDto, UserResponseDto } from '@api';
import * as luxon from 'luxon'; import * as luxon from 'luxon';
import handlebar from 'handlebars'; import handlebar from 'handlebars';
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
import { fade } from 'svelte/transition';
import SupportedDatetimePanel from './supported-datetime-panel.svelte';
import SupportedVariablesPanel from './supported-variables-panel.svelte'; import SupportedVariablesPanel from './supported-variables-panel.svelte';
import SettingButtonsRow from '../setting-buttons-row.svelte'; import SettingButtonsRow from '../setting-buttons-row.svelte';
import { isEqual } from 'lodash-es'; import { isEqual } from 'lodash-es';
@ -13,31 +10,20 @@
NotificationType, NotificationType,
} from '$lib/components/shared-components/notification/notification'; } from '$lib/components/shared-components/notification/notification';
import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte'; import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte';
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher<{
save: SystemConfigStorageTemplateDto;
}>();
export let storageConfig: SystemConfigStorageTemplateDto; export let storageConfig: SystemConfigStorageTemplateDto;
export let storageDefault: SystemConfigStorageTemplateDto;
export let user: UserResponseDto; export let user: UserResponseDto;
export let disabled = false; export let templateOptions: SystemConfigTemplateStorageOptionDto;
let savedConfig: SystemConfigStorageTemplateDto; export let savedConfig: SystemConfigStorageTemplateDto;
let defaultConfig: SystemConfigStorageTemplateDto;
let templateOptions: SystemConfigTemplateStorageOptionDto;
let selectedPreset = ''; let selectedPreset = '';
async function getConfigs() {
[savedConfig, defaultConfig, templateOptions] = await Promise.all([
api.systemConfigApi.getConfig().then((res) => res.data.storageTemplate),
api.systemConfigApi.getDefaults().then((res) => res.data.storageTemplate),
api.systemConfigApi.getStorageTemplateOptions().then((res) => res.data),
]);
selectedPreset = savedConfig.template;
}
const getSupportDateTimeFormat = async () => {
const { data } = await api.systemConfigApi.getStorageTemplateOptions();
return data;
};
$: parsedTemplate = () => { $: parsedTemplate = () => {
try { try {
return renderTemplate(storageConfig.template); return renderTemplate(storageConfig.template);
@ -77,48 +63,15 @@
}; };
async function reset() { async function reset() {
const { data: resetConfig } = await api.systemConfigApi.getConfig(); storageConfig = { ...savedConfig };
storageConfig.template = resetConfig.storageTemplate.template;
savedConfig.template = resetConfig.storageTemplate.template;
notificationController.show({ notificationController.show({
message: 'Reset storage template settings to the recent saved settings', message: 'Reset storage template settings to the last saved settings',
type: NotificationType.Info, type: NotificationType.Info,
}); });
} }
async function saveSetting() {
try {
const { data: currentConfig } = await api.systemConfigApi.getConfig();
const result = await api.systemConfigApi.updateConfig({
systemConfigDto: {
...currentConfig,
storageTemplate: storageConfig,
},
});
storageConfig.template = result.data.storageTemplate.template;
savedConfig.template = result.data.storageTemplate.template;
notificationController.show({
message: 'Storage template saved',
type: NotificationType.Info,
});
} catch (e) {
console.error('Error [storage-template-settings] [saveSetting]', e);
notificationController.show({
message: 'Unable to save settings',
type: NotificationType.Error,
});
}
}
async function resetToDefault() { async function resetToDefault() {
const { data: defaultConfig } = await api.systemConfigApi.getDefaults(); storageConfig = { ...storageDefault };
storageConfig.template = defaultConfig.storageTemplate.template;
notificationController.show({ notificationController.show({
message: 'Reset storage template to default', message: 'Reset storage template to default',
@ -132,19 +85,10 @@
</script> </script>
<section class="dark:text-immich-dark-fg"> <section class="dark:text-immich-dark-fg">
{#await getConfigs() then}
<div id="directory-path-builder" class="m-4"> <div id="directory-path-builder" class="m-4">
<h3 class="text-base font-medium text-immich-primary dark:text-immich-dark-primary">Variables</h3> <h3 class="text-base font-medium text-immich-primary dark:text-immich-dark-primary">Variables</h3>
<section class="support-date"> <section class="support-date" />
{#await getSupportDateTimeFormat()}
<LoadingSpinner />
{:then options}
<div transition:fade={{ duration: 200 }}>
<SupportedDatetimePanel {options} />
</div>
{/await}
</section>
<section class="support-date"> <section class="support-date">
<SupportedVariablesPanel /> <SupportedVariablesPanel />
@ -158,8 +102,7 @@
</div> </div>
<p class="text-xs"> <p class="text-xs">
Approximately path length limit : <span Approximately path length limit : <span class="font-semibold text-immich-primary dark:text-immich-dark-primary"
class="font-semibold text-immich-primary dark:text-immich-dark-primary"
>{parsedTemplate().length + user.id.length + 'UPLOAD_LOCATION'.length}</span >{parsedTemplate().length + user.id.length + 'UPLOAD_LOCATION'.length}</span
>/260 >/260
</p> </p>
@ -169,8 +112,7 @@
</p> </p>
<p class="mt-2 rounded-lg bg-gray-200 p-4 py-2 text-xs dark:bg-gray-700 dark:text-immich-dark-fg"> <p class="mt-2 rounded-lg bg-gray-200 p-4 py-2 text-xs dark:bg-gray-700 dark:text-immich-dark-fg">
<span class="text-immich-fg/25 dark:text-immich-dark-fg/50" <span class="text-immich-fg/25 dark:text-immich-dark-fg/50">UPLOAD_LOCATION/{user.storageLabel || user.id}</span
>UPLOAD_LOCATION/{user.storageLabel || user.id}</span
>/{parsedTemplate()}.jpg >/{parsedTemplate()}.jpg
</p> </p>
@ -179,7 +121,6 @@
<label class="text-xs" for="preset-select">PRESET</label> <label class="text-xs" for="preset-select">PRESET</label>
<select <select
class="mt-2 rounded-lg bg-slate-200 p-2 text-sm hover:cursor-pointer dark:bg-gray-600" class="mt-2 rounded-lg bg-slate-200 p-2 text-sm hover:cursor-pointer dark:bg-gray-600"
{disabled}
name="presets" name="presets"
id="preset-select" id="preset-select"
bind:value={selectedPreset} bind:value={selectedPreset}
@ -193,7 +134,6 @@
<div class="flex gap-2 align-bottom"> <div class="flex gap-2 align-bottom">
<SettingInputField <SettingInputField
label="TEMPLATE" label="TEMPLATE"
{disabled}
required required
inputType={SettingInputFieldType.TEXT} inputType={SettingInputFieldType.TEXT}
bind:value={storageConfig.template} bind:value={storageConfig.template}
@ -216,13 +156,11 @@
<SettingButtonsRow <SettingButtonsRow
on:reset={reset} on:reset={reset}
on:save={saveSetting} on:save={() => dispatch('save', savedConfig)}
on:reset-to-default={resetToDefault} on:reset-to-default={resetToDefault}
showResetToDefault={!isEqual(savedConfig, defaultConfig)} showResetToDefault={!isEqual(savedConfig, storageConfig)}
{disabled}
/> />
</form> </form>
</div> </div>
</div> </div>
{/await}
</section> </section>

View file

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import SettingSelect from '$lib/components/admin-page/settings/setting-select.svelte'; import SettingSelect from '$lib/components/admin-page/settings/setting-select.svelte';
import { api, SystemConfigThumbnailDto } from '@api'; import type { SystemConfigThumbnailDto } from '@api';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import { isEqual } from 'lodash-es'; import { isEqual } from 'lodash-es';
import SettingButtonsRow from '$lib/components/admin-page/settings/setting-buttons-row.svelte'; import SettingButtonsRow from '$lib/components/admin-page/settings/setting-buttons-row.svelte';
@ -8,75 +8,38 @@
notificationController, notificationController,
NotificationType, NotificationType,
} from '$lib/components/shared-components/notification/notification'; } from '$lib/components/shared-components/notification/notification';
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher<{
save: SystemConfigThumbnailDto;
}>();
export let thumbnailConfig: SystemConfigThumbnailDto; // this is the config that is being edited export let thumbnailConfig: SystemConfigThumbnailDto; // this is the config that is being edited
export let thumbnailDefault: SystemConfigThumbnailDto;
export let savedConfig: SystemConfigThumbnailDto;
export let disabled = false; export let disabled = false;
let savedConfig: SystemConfigThumbnailDto;
let defaultConfig: SystemConfigThumbnailDto;
async function getConfigs() {
[savedConfig, defaultConfig] = await Promise.all([
api.systemConfigApi.getConfig().then((res) => res.data.thumbnail),
api.systemConfigApi.getDefaults().then((res) => res.data.thumbnail),
]);
}
async function reset() { async function reset() {
const { data: resetConfig } = await api.systemConfigApi.getConfig(); thumbnailConfig = { ...savedConfig };
thumbnailConfig = { ...resetConfig.thumbnail };
savedConfig = { ...resetConfig.thumbnail };
notificationController.show({ notificationController.show({
message: 'Reset thumbnail settings to the recent saved settings', message: 'Reset thumbnail settings to the last saved settings',
type: NotificationType.Info, type: NotificationType.Info,
}); });
} }
async function resetToDefault() { async function resetToDefault() {
const { data: configs } = await api.systemConfigApi.getDefaults(); thumbnailConfig = { ...thumbnailDefault };
thumbnailConfig = { ...configs.thumbnail };
defaultConfig = { ...configs.thumbnail };
notificationController.show({ notificationController.show({
message: 'Reset thumbnail settings to default', message: 'Reset thumbnail settings to default',
type: NotificationType.Info, type: NotificationType.Info,
}); });
} }
async function saveSetting() {
try {
const { data: configs } = await api.systemConfigApi.getConfig();
const result = await api.systemConfigApi.updateConfig({
systemConfigDto: {
...configs,
thumbnail: thumbnailConfig,
},
});
thumbnailConfig = { ...result.data.thumbnail };
savedConfig = { ...result.data.thumbnail };
notificationController.show({
message: 'Thumbnail settings saved',
type: NotificationType.Info,
});
} catch (e) {
console.error('Error [thumbnail-settings] [saveSetting]', e);
notificationController.show({
message: 'Unable to save settings',
type: NotificationType.Error,
});
}
}
</script> </script>
<div> <div>
{#await getConfigs() then} <div in:fade={{ duration: 300 }}>
<div in:fade={{ duration: 500 }}>
<form autocomplete="off" on:submit|preventDefault> <form autocomplete="off" on:submit|preventDefault>
<div class="ml-4 mt-4 flex flex-col gap-4"> <div class="ml-4 mt-4 flex flex-col gap-4">
<SettingSelect <SettingSelect
@ -113,13 +76,12 @@
<div class="ml-4"> <div class="ml-4">
<SettingButtonsRow <SettingButtonsRow
on:reset={reset} on:reset={reset}
on:save={saveSetting} on:save={() => dispatch('save', thumbnailConfig)}
on:reset-to-default={resetToDefault} on:reset-to-default={resetToDefault}
showResetToDefault={!isEqual(savedConfig, defaultConfig)} showResetToDefault={!isEqual(thumbnailConfig, thumbnailDefault)}
{disabled} {disabled}
/> />
</div> </div>
</form> </form>
</div> </div>
{/await}
</div> </div>

View file

@ -2,7 +2,7 @@ import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ parent }) => { export const load: PageServerLoad = async ({ parent, locals }) => {
const { user } = await parent(); const { user } = await parent();
if (!user) { if (!user) {
@ -11,8 +11,14 @@ export const load: PageServerLoad = async ({ parent }) => {
throw redirect(302, AppRoute.PHOTOS); throw redirect(302, AppRoute.PHOTOS);
} }
const { data: config } = await locals.api.systemConfigApi.getConfig();
const { data: defaultConfig } = await locals.api.systemConfigApi.getDefaults();
const { data: templateOptions } = await locals.api.systemConfigApi.getStorageTemplateOptions();
return { return {
user, user,
config,
defaultConfig,
templateOptions,
meta: { meta: {
title: 'System Settings', title: 'System Settings',
}, },

View file

@ -9,21 +9,46 @@
import StorageTemplateSettings from '$lib/components/admin-page/settings/storage-template/storage-template-settings.svelte'; import StorageTemplateSettings from '$lib/components/admin-page/settings/storage-template/storage-template-settings.svelte';
import ThumbnailSettings from '$lib/components/admin-page/settings/thumbnail/thumbnail-settings.svelte'; import ThumbnailSettings from '$lib/components/admin-page/settings/thumbnail/thumbnail-settings.svelte';
import Button from '$lib/components/elements/buttons/button.svelte'; import Button from '$lib/components/elements/buttons/button.svelte';
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
import { downloadManager } from '$lib/stores/download'; import { downloadManager } from '$lib/stores/download';
import { featureFlags } from '$lib/stores/feature-flags.store'; import { featureFlags } from '$lib/stores/feature-flags.store';
import { downloadBlob } from '$lib/utils/asset-utils'; import { downloadBlob } from '$lib/utils/asset-utils';
import { SystemConfigDto, api, copyToClipboard } from '@api'; import { SystemConfigDto, SystemConfigTemplateStorageOptionDto, api, copyToClipboard } from '@api';
import Alert from 'svelte-material-icons/Alert.svelte'; import Alert from 'svelte-material-icons/Alert.svelte';
import ContentCopy from 'svelte-material-icons/ContentCopy.svelte'; import ContentCopy from 'svelte-material-icons/ContentCopy.svelte';
import Download from 'svelte-material-icons/Download.svelte'; import Download from 'svelte-material-icons/Download.svelte';
import type { PageData } from './$types'; import type { PageData } from './$types';
import {
notificationController,
NotificationType,
} from '$lib/components/shared-components/notification/notification';
import { cloneDeep } from 'lodash-es';
export let data: PageData; export let data: PageData;
let config: SystemConfigDto = data.config;
const getConfig = async () => { let currentConfig: SystemConfigDto = cloneDeep(config);
const { data } = await api.systemConfigApi.getConfig(); let defaultConfig: SystemConfigDto = data.defaultConfig;
return data; let templateOptions: SystemConfigTemplateStorageOptionDto = data.templateOptions;
const handleSave = async (config: SystemConfigDto, settingsMessage: string) => {
try {
const { data } = await api.systemConfigApi.updateConfig({
systemConfigDto: config,
});
config = cloneDeep(data);
currentConfig = cloneDeep(data);
notificationController.show({
message: `${settingsMessage} settings saved`,
type: NotificationType.Info,
});
} catch (e) {
console.error(`Error [${settingsMessage}-settings] [saveSetting]`, e);
notificationController.show({
message: 'Unable to save settings',
type: NotificationType.Error,
});
}
}; };
const downloadConfig = (configs: SystemConfigDto) => { const downloadConfig = (configs: SystemConfigDto) => {
@ -42,34 +67,50 @@
<h2 class="text-md text-immich-primary dark:text-immich-dark-primary">Config is currently set by a config file</h2> <h2 class="text-md text-immich-primary dark:text-immich-dark-primary">Config is currently set by a config file</h2>
</div> </div>
{/if} {/if}
<section class="">
{#await getConfig()}
<LoadingSpinner />
{:then configs}
<div class="flex justify-end gap-2"> <div class="flex justify-end gap-2">
<Button size="sm" on:click={() => copyToClipboard(JSON.stringify(configs, null, 2))}> <Button size="sm" on:click={() => copyToClipboard(JSON.stringify(config, null, 2))}>
<ContentCopy size="18" /> <ContentCopy size="18" />
<span class="pl-2">Copy to Clipboard</span> <span class="pl-2">Copy to Clipboard</span>
</Button> </Button>
<Button size="sm" on:click={() => downloadConfig(configs)}> <Button size="sm" on:click={() => downloadConfig(config)}>
<Download size="18" /> <Download size="18" />
<span class="pl-2">Export as JSON</span> <span class="pl-2">Export as JSON</span>
</Button> </Button>
</div> </div>
<SettingAccordion title="Thumbnail Settings" subtitle="Manage the resolution of thumbnail sizes"> <SettingAccordion title="Thumbnail Settings" subtitle="Manage the resolution of thumbnail sizes">
<ThumbnailSettings disabled={$featureFlags.configFile} thumbnailConfig={configs.thumbnail} /> <ThumbnailSettings
bind:savedConfig={currentConfig.thumbnail}
bind:thumbnailConfig={config.thumbnail}
thumbnailDefault={defaultConfig.thumbnail}
disabled={$featureFlags.configFile}
on:save={({ detail: thumbnail }) => {
handleSave(
{
...currentConfig,
thumbnail,
},
'Thumbnail',
);
}}
/>
</SettingAccordion> </SettingAccordion>
<SettingAccordion <SettingAccordion title="FFmpeg Settings" subtitle="Manage the resolution and encoding information of the video files">
title="FFmpeg Settings" <FFmpegSettings
subtitle="Manage the resolution and encoding information of the video files" bind:savedConfig={currentConfig.ffmpeg}
> bind:ffmpegConfig={config.ffmpeg}
<FFmpegSettings disabled={$featureFlags.configFile} ffmpegConfig={configs.ffmpeg} /> ffmpegDefault={defaultConfig.ffmpeg}
</SettingAccordion> disabled={$featureFlags.configFile}
on:save={({ detail: ffmpeg }) => {
<SettingAccordion title="Machine Learning Settings" subtitle="Manage model settings"> handleSave(
<MachineLearningSettings disabled={$featureFlags.configFile} machineLearningConfig={configs.machineLearning} /> {
...currentConfig,
ffmpeg,
},
'FFmpeg',
);
}}
/>
</SettingAccordion> </SettingAccordion>
<SettingAccordion <SettingAccordion
@ -77,15 +118,75 @@
subtitle="Manage job concurrency" subtitle="Manage job concurrency"
isOpen={$page.url.searchParams.get('open') === 'job-settings'} isOpen={$page.url.searchParams.get('open') === 'job-settings'}
> >
<JobSettings disabled={$featureFlags.configFile} jobConfig={configs.job} /> <JobSettings
bind:savedConfig={currentConfig.job}
bind:jobConfig={config.job}
jobDefault={defaultConfig.job}
disabled={$featureFlags.configFile}
on:save={({ detail: job }) => {
handleSave(
{
...currentConfig,
job,
},
'Job Settings',
);
}}
/>
</SettingAccordion> </SettingAccordion>
<SettingAccordion title="Password Authentication" subtitle="Manage login with password settings"> <SettingAccordion title="Password Authentication" subtitle="Manage login with password settings">
<PasswordLoginSettings disabled={$featureFlags.configFile} passwordLoginConfig={configs.passwordLogin} /> <PasswordLoginSettings
bind:savedConfig={currentConfig.passwordLogin}
bind:passwordLoginConfig={config.passwordLogin}
bind:config
disabled={$featureFlags.configFile}
passwordLoginDefault={defaultConfig.passwordLogin}
on:save={({ detail: passwordLogin }) => {
handleSave(
{
...currentConfig,
passwordLogin,
},
'Password Authentication',
);
}}
/>
</SettingAccordion>
<SettingAccordion title="Machine Learning" subtitle="Manage machine learning settings">
<MachineLearningSettings
bind:savedConfig={currentConfig.machineLearning}
bind:machineLearningConfig={config.machineLearning}
machineLearningDefault={defaultConfig.machineLearning}
disabled={$featureFlags.configFile}
on:save={({ detail: machineLearning }) => {
handleSave(
{
...currentConfig,
machineLearning,
},
'Machine Learning',
);
}}
/>
</SettingAccordion> </SettingAccordion>
<SettingAccordion title="OAuth Authentication" subtitle="Manage the login with OAuth settings"> <SettingAccordion title="OAuth Authentication" subtitle="Manage the login with OAuth settings">
<OAuthSettings disabled={$featureFlags.configFile} oauthConfig={configs.oauth} /> <OAuthSettings
bind:savedConfig={currentConfig.oauth}
bind:oauthConfig={config.oauth}
bind:config
disabled={$featureFlags.configFile}
oauthDefault={defaultConfig.oauth}
on:save={({ detail: oauth }) => {
handleSave(
{
...currentConfig,
oauth,
},
'OAuth Authentication',
);
}}
/>
</SettingAccordion> </SettingAccordion>
<SettingAccordion <SettingAccordion
@ -94,10 +195,19 @@
isOpen={$page.url.searchParams.get('open') === 'storage-template'} isOpen={$page.url.searchParams.get('open') === 'storage-template'}
> >
<StorageTemplateSettings <StorageTemplateSettings
disabled={$featureFlags.configFile} savedConfig={currentConfig.storageTemplate}
storageConfig={configs.storageTemplate} storageConfig={config.storageTemplate}
user={data.user} user={data.user}
storageDefault={defaultConfig.storageTemplate}
{templateOptions}
on:save={({ detail: storageTemplate }) => {
handleSave(
{
...currentConfig,
storageTemplate,
},
'Storage Template',
);
}}
/> />
</SettingAccordion> </SettingAccordion>
{/await}
</section>