feat(web, server): Ability to use config file instead of admin UI (#3836)

* implement method to read config file

* getConfig returns config file if present

* return isConfigFile for http requests

* disable elements if config file is used, show message if config file is set, copy existing config to clipboard

* fix allowing partial configuration files

* add new env variable to docs

* fix tests

* minor refactoring, address review

* adapt config type in frontend

* remove unnecessary imports

* move config file reading to system-config repo

* add documentation

* fix code formatting in system settings page

* add validator for config file

* fix formatting in docs

* update generated files

* throw error when trying to update config. e.g. via cli or api

* switch to feature flags for isConfigFile

* refactoring

* refactor: config file

* chore: open api

* feat: always show copy/export buttons

* fix: default flags

* refactor: copy to clipboard

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
This commit is contained in:
Daniel Dietzler 2023-08-25 19:44:52 +02:00 committed by GitHub
parent 20e0c03b39
commit 59bb727636
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 359 additions and 84 deletions

View file

@ -20,6 +20,7 @@
import { fade } from 'svelte/transition';
export let ffmpegConfig: SystemConfigFFmpegDto; // this is the config that is being edited
export let disabled = false;
let savedConfig: SystemConfigFFmpegDto;
let defaultConfig: SystemConfigFFmpegDto;
@ -90,6 +91,7 @@
<div class="ml-4 mt-4 flex flex-col gap-4">
<SettingInputField
inputType={SettingInputFieldType.NUMBER}
{disabled}
label="CONSTANT RATE FACTOR (-crf)"
desc="Video quality level. Typical values are 23 for H.264, 28 for HEVC, and 31 for VP9. Lower is better, but takes longer to encode and produces larger files."
bind:value={ffmpegConfig.crf}
@ -99,6 +101,7 @@
<SettingSelect
label="PRESET (-preset)"
{disabled}
desc="Compression speed. Slower presets produce smaller files, and increase quality when targeting a certain bitrate. VP9 ignores speeds above `faster`."
bind:value={ffmpegConfig.preset}
name="preset"
@ -118,6 +121,7 @@
<SettingSelect
label="AUDIO CODEC"
{disabled}
desc="Opus is the highest quality option, but has lower compatibility with old devices or software."
bind:value={ffmpegConfig.targetAudioCodec}
options={[
@ -131,6 +135,7 @@
<SettingSelect
label="VIDEO CODEC"
{disabled}
desc="VP9 has high efficiency and web compatibility, but takes longer to transcode. HEVC performs similarly, but has lower web compatibility. H.264 is widely compatible and quick to transcode, but produces much larger files."
bind:value={ffmpegConfig.targetVideoCodec}
options={[
@ -144,6 +149,7 @@
<SettingSelect
label="TARGET RESOLUTION"
{disabled}
desc="Higher resolutions can preserve more detail but take longer to encode, have larger file sizes, and can reduce app responsiveness."
bind:value={ffmpegConfig.targetResolution}
options={[
@ -160,6 +166,7 @@
<SettingInputField
inputType={SettingInputFieldType.TEXT}
{disabled}
label="MAX BITRATE"
desc="Setting a max bitrate can make file sizes more predictable at a minor cost to quality. At 720p, typical values are 2600k for VP9 or HEVC, or 4500k for H.264. Disabled if set to 0."
bind:value={ffmpegConfig.maxBitrate}
@ -168,6 +175,7 @@
<SettingInputField
inputType={SettingInputFieldType.NUMBER}
{disabled}
label="THREADS"
desc="Higher values lead to faster encoding, but leave less room for the server to process other tasks while active. This value should not be more than the number of CPU cores. Maximizes utilization if set to 0."
bind:value={ffmpegConfig.threads}
@ -176,6 +184,7 @@
<SettingSelect
label="TRANSCODE POLICY"
{disabled}
desc="Policy for when a video should be transcoded."
bind:value={ffmpegConfig.transcode}
name="transcode"
@ -199,6 +208,7 @@
<SettingSelect
label="HARDWARE ACCELERATION"
{disabled}
desc="Experimental. Much faster, but will have lower quality at the same bitrate. This setting is 'best effort': it will fallback to software transcoding on failure. VP9 may or may not work depending on your hardware."
bind:value={ffmpegConfig.accel}
name="accel"
@ -222,6 +232,7 @@
<SettingSelect
label="TONE-MAPPING"
{disabled}
desc="Attempts to preserve the appearance of HDR videos when converted to SDR. Each algorithm makes different tradeoffs for color, detail and brightness. Hable preserves detail, Mobius preserves color, and Reinhard preserves brightness."
bind:value={ffmpegConfig.tonemap}
name="tonemap"
@ -248,6 +259,7 @@
<SettingSwitch
title="TWO-PASS ENCODING"
{disabled}
subtitle="Transcode in two passes to produce better encoded videos. When max bitrate is enabled (required for it to work with H.264 and HEVC), this mode uses a bitrate range based on the max bitrate and ignores CRF. For VP9, CRF can be used if max bitrate is disabled."
bind:checked={ffmpegConfig.twoPass}
isEdited={!(ffmpegConfig.twoPass === savedConfig.twoPass)}
@ -260,6 +272,7 @@
on:save={saveSetting}
on:reset-to-default={resetToDefault}
showResetToDefault={!isEqual(savedConfig, defaultConfig)}
{disabled}
/>
</div>
</form>

View file

@ -11,6 +11,7 @@
import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte';
export let jobConfig: SystemConfigJobDto; // this is the config that is being edited
export let disabled = false;
let savedConfig: SystemConfigJobDto;
let defaultConfig: SystemConfigJobDto;
@ -78,6 +79,7 @@
<div class="ml-4 mt-4 flex flex-col gap-4">
<SettingInputField
inputType={SettingInputFieldType.NUMBER}
{disabled}
label="{api.getJobName(jobName)} Concurrency"
desc=""
bind:value={jobConfig[jobName].concurrency}
@ -93,6 +95,7 @@
on:save={saveSetting}
on:reset-to-default={resetToDefault}
showResetToDefault={!isEqual(savedConfig, defaultConfig)}
{disabled}
/>
</div>
</form>

View file

@ -11,6 +11,8 @@
import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte';
import SettingSwitch from '../setting-switch.svelte';
export let disabled = false;
let config: SystemConfigDto;
let defaultConfig: SystemConfigDto;
@ -56,6 +58,7 @@
<SettingSwitch
title="Enabled"
subtitle="Use machine learning features"
{disabled}
bind:checked={config.machineLearning.enabled}
/>
@ -67,7 +70,7 @@
desc="URL of machine learning server"
bind:value={config.machineLearning.url}
required={true}
disabled={!config.machineLearning.enabled}
disabled={disabled || !config.machineLearning.enabled}
isEdited={!(config.machineLearning.url === config.machineLearning.url)}
/>
@ -75,20 +78,20 @@
title="SMART SEARCH"
subtitle="Extract CLIP embeddings for smart search"
bind:checked={config.machineLearning.clipEncodeEnabled}
disabled={!config.machineLearning.enabled}
disabled={disabled || !config.machineLearning.enabled}
/>
<SettingSwitch
title="FACIAL RECOGNITION"
subtitle="Recognize and group faces in photos"
disabled={!config.machineLearning.enabled}
disabled={disabled || !config.machineLearning.enabled}
bind:checked={config.machineLearning.facialRecognitionEnabled}
/>
<SettingSwitch
title="IMAGE TAGGING"
subtitle="Tag and classify images"
disabled={!config.machineLearning.enabled}
disabled={disabled || !config.machineLearning.enabled}
bind:checked={config.machineLearning.tagImageEnabled}
/>
@ -97,6 +100,7 @@
on:save={saveSetting}
on:reset-to-default={resetToDefault}
showResetToDefault={!isEqual(config, defaultConfig)}
{disabled}
/>
</form>
</div>

View file

@ -13,6 +13,7 @@
import SettingSwitch from '../setting-switch.svelte';
export let oauthConfig: SystemConfigOAuthDto;
export let disabled = false;
let savedConfig: SystemConfigOAuthDto;
let defaultConfig: SystemConfigOAuthDto;
@ -117,14 +118,14 @@
>.
</p>
<SettingSwitch title="ENABLE" bind:checked={oauthConfig.enabled} />
<SettingSwitch {disabled} title="ENABLE" bind:checked={oauthConfig.enabled} />
<hr />
<SettingInputField
inputType={SettingInputFieldType.TEXT}
label="ISSUER URL"
bind:value={oauthConfig.issuerUrl}
required={true}
disabled={!oauthConfig.enabled}
disabled={disabled || !oauthConfig.enabled}
isEdited={!(oauthConfig.issuerUrl == savedConfig.issuerUrl)}
/>
@ -133,7 +134,7 @@
label="CLIENT ID"
bind:value={oauthConfig.clientId}
required={true}
disabled={!oauthConfig.enabled}
disabled={disabled || !oauthConfig.enabled}
isEdited={!(oauthConfig.clientId == savedConfig.clientId)}
/>
@ -142,7 +143,7 @@
label="CLIENT SECRET"
bind:value={oauthConfig.clientSecret}
required={true}
disabled={!oauthConfig.enabled}
disabled={disabled || !oauthConfig.enabled}
isEdited={!(oauthConfig.clientSecret == savedConfig.clientSecret)}
/>
@ -151,7 +152,7 @@
label="SCOPE"
bind:value={oauthConfig.scope}
required={true}
disabled={!oauthConfig.enabled}
disabled={disabled || !oauthConfig.enabled}
isEdited={!(oauthConfig.scope == savedConfig.scope)}
/>
@ -161,7 +162,7 @@
desc="Automatically set the user's storage label to the value of this claim."
bind:value={oauthConfig.storageLabelClaim}
required={true}
disabled={!oauthConfig.storageLabelClaim}
disabled={disabled || !oauthConfig.storageLabelClaim}
isEdited={!(oauthConfig.storageLabelClaim == savedConfig.storageLabelClaim)}
/>
@ -170,7 +171,7 @@
label="BUTTON TEXT"
bind:value={oauthConfig.buttonText}
required={false}
disabled={!oauthConfig.enabled}
disabled={disabled || !oauthConfig.enabled}
isEdited={!(oauthConfig.buttonText == savedConfig.buttonText)}
/>
@ -178,20 +179,20 @@
title="AUTO REGISTER"
subtitle="Automatically register new users after signing in with OAuth"
bind:checked={oauthConfig.autoRegister}
disabled={!oauthConfig.enabled}
disabled={disabled || !oauthConfig.enabled}
/>
<SettingSwitch
title="AUTO LAUNCH"
subtitle="Start the OAuth login flow automatically upon navigating to the login page"
disabled={!oauthConfig.enabled}
disabled={disabled || !oauthConfig.enabled}
bind:checked={oauthConfig.autoLaunch}
/>
<SettingSwitch
title="MOBILE REDIRECT URI OVERRIDE"
subtitle="Enable when `app.immich:/` is an invalid redirect URI."
disabled={!oauthConfig.enabled}
disabled={disabled || !oauthConfig.enabled}
on:click={() => handleToggleOverride()}
bind:checked={oauthConfig.mobileOverrideEnabled}
/>
@ -202,7 +203,7 @@
label="MOBILE REDIRECT URI"
bind:value={oauthConfig.mobileRedirectUri}
required={true}
disabled={!oauthConfig.enabled}
disabled={disabled || !oauthConfig.enabled}
isEdited={!(oauthConfig.mobileRedirectUri == savedConfig.mobileRedirectUri)}
/>
{/if}
@ -212,6 +213,7 @@
on:save={saveSetting}
on:reset-to-default={resetToDefault}
showResetToDefault={!isEqual(savedConfig, defaultConfig)}
{disabled}
/>
</form>
</div>

View file

@ -12,6 +12,7 @@
import SettingSwitch from '../setting-switch.svelte';
export let passwordLoginConfig: SystemConfigPasswordLoginDto; // this is the config that is being edited
export let disabled = false;
let savedConfig: SystemConfigPasswordLoginDto;
let defaultConfig: SystemConfigPasswordLoginDto;
@ -100,6 +101,7 @@
<div class="ml-4">
<SettingSwitch
title="ENABLED"
{disabled}
subtitle="Login with email and password"
bind:checked={passwordLoginConfig.enabled}
/>
@ -109,6 +111,7 @@
on:save={saveSetting}
on:reset-to-default={resetToDefault}
showResetToDefault={!isEqual(savedConfig, defaultConfig)}
{disabled}
/>
</div>
</div>

View file

@ -5,6 +5,7 @@
const dispatch = createEventDispatcher();
export let showResetToDefault = true;
export let disabled = false;
</script>
<div class="mt-8 flex justify-between gap-2">
@ -20,7 +21,7 @@
</div>
<div class="right">
<Button size="sm" color="gray" on:click={() => dispatch('reset')}>Reset</Button>
<Button size="sm" on:click={() => dispatch('save')}>Save</Button>
<Button {disabled} size="sm" color="gray" on:click={() => dispatch('reset')}>Reset</Button>
<Button {disabled} size="sm" on:click={() => dispatch('save')}>Save</Button>
</div>
</div>

View file

@ -9,6 +9,7 @@
export let name = '';
export let isEdited = false;
export let number = false;
export let disabled = false;
const handleChange = (e: Event) => {
value = (e.target as HTMLInputElement).value;
@ -40,6 +41,7 @@
<select
class="immich-form-input w-full pb-2"
{disabled}
aria-describedby={desc ? `${name}-desc` : undefined}
{name}
id="{name}-select"

View file

@ -16,6 +16,7 @@
export let storageConfig: SystemConfigStorageTemplateDto;
export let user: UserResponseDto;
export let disabled = false;
let savedConfig: SystemConfigStorageTemplateDto;
let defaultConfig: SystemConfigStorageTemplateDto;
@ -178,6 +179,7 @@
<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}
@ -191,6 +193,7 @@
<div class="flex gap-2 align-bottom">
<SettingInputField
label="TEMPLATE"
{disabled}
required
inputType={SettingInputFieldType.TEXT}
bind:value={storageConfig.template}
@ -216,6 +219,7 @@
on:save={saveSetting}
on:reset-to-default={resetToDefault}
showResetToDefault={!isEqual(savedConfig, defaultConfig)}
{disabled}
/>
</form>
</div>

View file

@ -10,6 +10,7 @@
} from '$lib/components/shared-components/notification/notification';
export let thumbnailConfig: SystemConfigThumbnailDto; // this is the config that is being edited
export let disabled = false;
let savedConfig: SystemConfigThumbnailDto;
let defaultConfig: SystemConfigThumbnailDto;
@ -91,6 +92,7 @@
]}
name="resolution"
isEdited={!(thumbnailConfig.webpSize === savedConfig.webpSize)}
{disabled}
/>
<SettingSelect
@ -104,6 +106,7 @@
]}
name="resolution"
isEdited={!(thumbnailConfig.jpegSize === savedConfig.jpegSize)}
{disabled}
/>
</div>
@ -113,6 +116,7 @@
on:save={saveSetting}
on:reset-to-default={resetToDefault}
showResetToDefault={!isEqual(savedConfig, defaultConfig)}
{disabled}
/>
</div>
</form>