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

View file

@ -6,51 +6,24 @@
import { api, JobName, SystemConfigJobDto } from '@api';
import { isEqual } from 'lodash-es';
import { fade } from 'svelte/transition';
import { handleError } from '../../../../utils/handle-error';
import SettingButtonsRow from '../setting-buttons-row.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 jobDefault: SystemConfigJobDto;
export let savedConfig: SystemConfigJobDto;
export let disabled = false;
let savedConfig: SystemConfigJobDto;
let defaultConfig: SystemConfigJobDto;
const ignoredJobs = [JobName.BackgroundTask, JobName.Search] 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() {
const { data: resetConfig } = await api.systemConfigApi.getConfig();
jobConfig = { ...resetConfig.job };
savedConfig = { ...resetConfig.job };
jobConfig = { ...savedConfig };
notificationController.show({
message: 'Reset Job settings to the recent saved settings',
@ -59,10 +32,7 @@
}
async function resetToDefault() {
const { data: configs } = await api.systemConfigApi.getDefaults();
jobConfig = { ...configs.job };
defaultConfig = { ...configs.job };
jobConfig = { ...jobDefault };
notificationController.show({
message: 'Reset Job settings to default',
@ -72,8 +42,7 @@
</script>
<div>
{#await getConfigs() then}
<div in:fade={{ duration: 500 }}>
<div in:fade={{ duration: 300 }}>
<form autocomplete="off" on:submit|preventDefault>
{#each jobNames as jobName}
<div class="ml-4 mt-4 flex flex-col gap-4">
@ -92,13 +61,11 @@
<div class="ml-4">
<SettingButtonsRow
on:reset={reset}
on:save={saveSetting}
on:save={() => dispatch('save', jobConfig)}
on:reset-to-default={resetToDefault}
showResetToDefault={!isEqual(savedConfig, defaultConfig)}
{disabled}
showResetToDefault={!isEqual(jobConfig, jobDefault)}
/>
</div>
</form>
</div>
{/await}
</div>

View file

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

View file

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

View file

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

View file

@ -1,10 +1,7 @@
<script lang="ts">
import { api, SystemConfigStorageTemplateDto, SystemConfigTemplateStorageOptionDto, UserResponseDto } from '@api';
import type { SystemConfigStorageTemplateDto, SystemConfigTemplateStorageOptionDto, UserResponseDto } from '@api';
import * as luxon from 'luxon';
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 SettingButtonsRow from '../setting-buttons-row.svelte';
import { isEqual } from 'lodash-es';
@ -13,31 +10,20 @@
NotificationType,
} from '$lib/components/shared-components/notification/notification';
import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte';
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher<{
save: SystemConfigStorageTemplateDto;
}>();
export let storageConfig: SystemConfigStorageTemplateDto;
export let storageDefault: SystemConfigStorageTemplateDto;
export let user: UserResponseDto;
export let disabled = false;
export let templateOptions: SystemConfigTemplateStorageOptionDto;
let savedConfig: SystemConfigStorageTemplateDto;
let defaultConfig: SystemConfigStorageTemplateDto;
let templateOptions: SystemConfigTemplateStorageOptionDto;
export let savedConfig: SystemConfigStorageTemplateDto;
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 = () => {
try {
return renderTemplate(storageConfig.template);
@ -77,48 +63,15 @@
};
async function reset() {
const { data: resetConfig } = await api.systemConfigApi.getConfig();
storageConfig.template = resetConfig.storageTemplate.template;
savedConfig.template = resetConfig.storageTemplate.template;
storageConfig = { ...savedConfig };
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,
});
}
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() {
const { data: defaultConfig } = await api.systemConfigApi.getDefaults();
storageConfig.template = defaultConfig.storageTemplate.template;
storageConfig = { ...storageDefault };
notificationController.show({
message: 'Reset storage template to default',
@ -132,19 +85,10 @@
</script>
<section class="dark:text-immich-dark-fg">
{#await getConfigs() then}
<div id="directory-path-builder" class="m-4">
<h3 class="text-base font-medium text-immich-primary dark:text-immich-dark-primary">Variables</h3>
<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 />
@ -158,8 +102,7 @@
</div>
<p class="text-xs">
Approximately path length limit : <span
class="font-semibold text-immich-primary dark:text-immich-dark-primary"
Approximately path length limit : <span class="font-semibold text-immich-primary dark:text-immich-dark-primary"
>{parsedTemplate().length + user.id.length + 'UPLOAD_LOCATION'.length}</span
>/260
</p>
@ -169,8 +112,7 @@
</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">
<span class="text-immich-fg/25 dark:text-immich-dark-fg/50"
>UPLOAD_LOCATION/{user.storageLabel || user.id}</span
<span class="text-immich-fg/25 dark:text-immich-dark-fg/50">UPLOAD_LOCATION/{user.storageLabel || user.id}</span
>/{parsedTemplate()}.jpg
</p>
@ -179,7 +121,6 @@
<label class="text-xs" for="preset-select">PRESET</label>
<select
class="mt-2 rounded-lg bg-slate-200 p-2 text-sm hover:cursor-pointer dark:bg-gray-600"
{disabled}
name="presets"
id="preset-select"
bind:value={selectedPreset}
@ -193,7 +134,6 @@
<div class="flex gap-2 align-bottom">
<SettingInputField
label="TEMPLATE"
{disabled}
required
inputType={SettingInputFieldType.TEXT}
bind:value={storageConfig.template}
@ -216,13 +156,11 @@
<SettingButtonsRow
on:reset={reset}
on:save={saveSetting}
on:save={() => dispatch('save', savedConfig)}
on:reset-to-default={resetToDefault}
showResetToDefault={!isEqual(savedConfig, defaultConfig)}
{disabled}
showResetToDefault={!isEqual(savedConfig, storageConfig)}
/>
</form>
</div>
</div>
{/await}
</section>

View file

@ -1,6 +1,6 @@
<script lang="ts">
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 { isEqual } from 'lodash-es';
import SettingButtonsRow from '$lib/components/admin-page/settings/setting-buttons-row.svelte';
@ -8,75 +8,38 @@
notificationController,
NotificationType,
} 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 thumbnailDefault: SystemConfigThumbnailDto;
export let savedConfig: SystemConfigThumbnailDto;
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() {
const { data: resetConfig } = await api.systemConfigApi.getConfig();
thumbnailConfig = { ...resetConfig.thumbnail };
savedConfig = { ...resetConfig.thumbnail };
thumbnailConfig = { ...savedConfig };
notificationController.show({
message: 'Reset thumbnail settings to the recent saved settings',
message: 'Reset thumbnail settings to the last saved settings',
type: NotificationType.Info,
});
}
async function resetToDefault() {
const { data: configs } = await api.systemConfigApi.getDefaults();
thumbnailConfig = { ...configs.thumbnail };
defaultConfig = { ...configs.thumbnail };
thumbnailConfig = { ...thumbnailDefault };
notificationController.show({
message: 'Reset thumbnail settings to default',
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>
<div>
{#await getConfigs() then}
<div in:fade={{ duration: 500 }}>
<div in:fade={{ duration: 300 }}>
<form autocomplete="off" on:submit|preventDefault>
<div class="ml-4 mt-4 flex flex-col gap-4">
<SettingSelect
@ -113,13 +76,12 @@
<div class="ml-4">
<SettingButtonsRow
on:reset={reset}
on:save={saveSetting}
on:save={() => dispatch('save', thumbnailConfig)}
on:reset-to-default={resetToDefault}
showResetToDefault={!isEqual(savedConfig, defaultConfig)}
showResetToDefault={!isEqual(thumbnailConfig, thumbnailDefault)}
{disabled}
/>
</div>
</form>
</div>
{/await}
</div>

View file

@ -2,7 +2,7 @@ import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ parent }) => {
export const load: PageServerLoad = async ({ parent, locals }) => {
const { user } = await parent();
if (!user) {
@ -11,8 +11,14 @@ export const load: PageServerLoad = async ({ parent }) => {
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 {
user,
config,
defaultConfig,
templateOptions,
meta: {
title: 'System Settings',
},

View file

@ -9,21 +9,46 @@
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 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 { featureFlags } from '$lib/stores/feature-flags.store';
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 ContentCopy from 'svelte-material-icons/ContentCopy.svelte';
import Download from 'svelte-material-icons/Download.svelte';
import type { PageData } from './$types';
import {
notificationController,
NotificationType,
} from '$lib/components/shared-components/notification/notification';
import { cloneDeep } from 'lodash-es';
export let data: PageData;
let config: SystemConfigDto = data.config;
const getConfig = async () => {
const { data } = await api.systemConfigApi.getConfig();
return data;
let currentConfig: SystemConfigDto = cloneDeep(config);
let defaultConfig: SystemConfigDto = data.defaultConfig;
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) => {
@ -42,62 +67,147 @@
<h2 class="text-md text-immich-primary dark:text-immich-dark-primary">Config is currently set by a config file</h2>
</div>
{/if}
<section class="">
{#await getConfig()}
<LoadingSpinner />
{:then configs}
<div class="flex justify-end gap-2">
<Button size="sm" on:click={() => copyToClipboard(JSON.stringify(configs, null, 2))}>
<div class="flex justify-end gap-2">
<Button size="sm" on:click={() => copyToClipboard(JSON.stringify(config, null, 2))}>
<ContentCopy size="18" />
<span class="pl-2">Copy to Clipboard</span>
</Button>
<Button size="sm" on:click={() => downloadConfig(configs)}>
<Button size="sm" on:click={() => downloadConfig(config)}>
<Download size="18" />
<span class="pl-2">Export as JSON</span>
</Button>
</div>
<SettingAccordion title="Thumbnail Settings" subtitle="Manage the resolution of thumbnail sizes">
<ThumbnailSettings disabled={$featureFlags.configFile} thumbnailConfig={configs.thumbnail} />
</SettingAccordion>
</div>
<SettingAccordion title="Thumbnail Settings" subtitle="Manage the resolution of thumbnail sizes">
<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
title="FFmpeg Settings"
subtitle="Manage the resolution and encoding information of the video files"
>
<FFmpegSettings disabled={$featureFlags.configFile} ffmpegConfig={configs.ffmpeg} />
</SettingAccordion>
<SettingAccordion title="FFmpeg Settings" subtitle="Manage the resolution and encoding information of the video files">
<FFmpegSettings
bind:savedConfig={currentConfig.ffmpeg}
bind:ffmpegConfig={config.ffmpeg}
ffmpegDefault={defaultConfig.ffmpeg}
disabled={$featureFlags.configFile}
on:save={({ detail: ffmpeg }) => {
handleSave(
{
...currentConfig,
ffmpeg,
},
'FFmpeg',
);
}}
/>
</SettingAccordion>
<SettingAccordion title="Machine Learning Settings" subtitle="Manage model settings">
<MachineLearningSettings disabled={$featureFlags.configFile} machineLearningConfig={configs.machineLearning} />
</SettingAccordion>
<SettingAccordion
<SettingAccordion
title="Job Settings"
subtitle="Manage job concurrency"
isOpen={$page.url.searchParams.get('open') === 'job-settings'}
>
<JobSettings disabled={$featureFlags.configFile} jobConfig={configs.job} />
</SettingAccordion>
>
<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 title="Password Authentication" subtitle="Manage login with password settings">
<PasswordLoginSettings disabled={$featureFlags.configFile} passwordLoginConfig={configs.passwordLogin} />
</SettingAccordion>
<SettingAccordion title="Password Authentication" subtitle="Manage login with password settings">
<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 title="OAuth Authentication" subtitle="Manage the login with OAuth settings">
<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 title="OAuth Authentication" subtitle="Manage the login with OAuth settings">
<OAuthSettings disabled={$featureFlags.configFile} oauthConfig={configs.oauth} />
</SettingAccordion>
<SettingAccordion
<SettingAccordion
title="Storage Template"
subtitle="Manage the folder structure and file name of the upload asset"
isOpen={$page.url.searchParams.get('open') === 'storage-template'}
>
>
<StorageTemplateSettings
disabled={$featureFlags.configFile}
storageConfig={configs.storageTemplate}
savedConfig={currentConfig.storageTemplate}
storageConfig={config.storageTemplate}
user={data.user}
storageDefault={defaultConfig.storageTemplate}
{templateOptions}
on:save={({ detail: storageTemplate }) => {
handleSave(
{
...currentConfig,
storageTemplate,
},
'Storage Template',
);
}}
/>
</SettingAccordion>
{/await}
</section>
</SettingAccordion>