WIP
parent
009a37f0a7
commit
ef7a37a26a
|
|
@ -272,7 +272,7 @@
|
|||
"oauth_timeout_description": "Timeout for requests in milliseconds",
|
||||
"ocr_job_description": "Use machine learning to recognize text in images",
|
||||
"password_enable_description": "Login with email and password",
|
||||
"password_settings": "Password Login",
|
||||
"password_settings": "Password login",
|
||||
"password_settings_description": "Manage password login settings",
|
||||
"paths_validated_successfully": "All paths validated successfully",
|
||||
"person_cleanup_job": "Person cleanup",
|
||||
|
|
|
|||
|
|
@ -717,8 +717,8 @@ importers:
|
|||
specifier: file:../open-api/typescript-sdk
|
||||
version: link:../open-api/typescript-sdk
|
||||
'@immich/ui':
|
||||
specifier: ^0.50.0
|
||||
version: 0.50.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)
|
||||
specifier: ^0.50.1
|
||||
version: 0.50.1(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)
|
||||
'@mapbox/mapbox-gl-rtl-text':
|
||||
specifier: 0.2.3
|
||||
version: 0.2.3(mapbox-gl@1.13.3)
|
||||
|
|
@ -2989,8 +2989,8 @@ packages:
|
|||
peerDependencies:
|
||||
svelte: ^5.0.0
|
||||
|
||||
'@immich/ui@0.50.0':
|
||||
resolution: {integrity: sha512-7AW9SRZTAgal8xlkUAxm7o4+pSG7HcKb+Bh9JpWLaDRRdGyPCZMmsNa9CjZglOQ7wkAD07tQ9u4+zezBLe0dlQ==}
|
||||
'@immich/ui@0.50.1':
|
||||
resolution: {integrity: sha512-fNlQGh75ZFa/UZAgJaYk9/ItHOXHNNzN4CunjCmE7WocVVkUZbUxopN9Ku3F5GULSqD/zJ5gNO6PQAZ1ZoSaaQ==}
|
||||
peerDependencies:
|
||||
svelte: ^5.0.0
|
||||
|
||||
|
|
@ -14700,7 +14700,7 @@ snapshots:
|
|||
dependencies:
|
||||
svelte: 5.45.2
|
||||
|
||||
'@immich/ui@0.50.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)':
|
||||
'@immich/ui@0.50.1(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)':
|
||||
dependencies:
|
||||
'@immich/svelte-markdown-preprocess': 0.1.0(svelte@5.45.2)
|
||||
'@internationalized/date': 3.10.0
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
"@formatjs/icu-messageformat-parser": "^2.9.8",
|
||||
"@immich/justified-layout-wasm": "^0.4.3",
|
||||
"@immich/sdk": "file:../open-api/typescript-sdk",
|
||||
"@immich/ui": "^0.50.0",
|
||||
"@immich/ui": "^0.50.1",
|
||||
"@mapbox/mapbox-gl-rtl-text": "0.2.3",
|
||||
"@mdi/js": "^7.4.47",
|
||||
"@photo-sphere-viewer/core": "^5.14.0",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
import { AppRoute } from '$lib/constants';
|
||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
|
||||
import { getSystemConfigActions, handleSystemConfigSave } from '$lib/services/system-config.service';
|
||||
import { getSystemConfigActions, handleSystemConfigSave, resolveSetting } from '$lib/services/system-config.service';
|
||||
import type { SystemConfigContext } from '$lib/types';
|
||||
import type { SystemConfigDto } from '@immich/sdk';
|
||||
import { Button, FormModal, type ModalSize } from '@immich/ui';
|
||||
|
|
@ -19,13 +19,13 @@
|
|||
child: Snippet<[SystemConfigContext]>;
|
||||
};
|
||||
|
||||
let { keys, size = 'medium', onBeforeSave, child }: Props = $props();
|
||||
let { keys, size = 'large', onBeforeSave, child }: Props = $props();
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
const config = $derived(systemConfigManager.value);
|
||||
let configToEdit = $state(systemConfigManager.cloneValue());
|
||||
const { settings } = $derived(getSystemConfigActions($t, featureFlagsManager.value, systemConfigManager.value));
|
||||
const setting = $derived(settings.find((setting) => setting.href === page.url.pathname));
|
||||
const setting = $derived(resolveSetting(settings, page.url.pathname));
|
||||
const showResetToDefault = $derived(!isEqual(pick(configToEdit, keys), pick(systemConfigManager.defaultValue, keys)));
|
||||
|
||||
const handleResetToDefault = () => {
|
||||
|
|
@ -47,14 +47,7 @@
|
|||
</script>
|
||||
|
||||
{#if setting}
|
||||
<FormModal
|
||||
size={size as 'small' | 'medium'}
|
||||
title={setting.title}
|
||||
icon={setting.icon}
|
||||
preventDefault
|
||||
{onClose}
|
||||
{onSubmit}
|
||||
>
|
||||
<FormModal {size} title={setting.title} icon={setting.icon} preventDefault {onClose} {onSubmit}>
|
||||
<div class="flex flex-col gap-5">
|
||||
{@render child({ disabled, config, configToEdit })}
|
||||
{#if showResetToDefault}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
import { AppRoute } from '$lib/constants';
|
||||
import { downloadManager } from '$lib/managers/download-manager.svelte';
|
||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||
import AuthDisableLoginConfirmModal from '$lib/modals/AuthDisableLoginConfirmModal.svelte';
|
||||
import type { SystemConfigContext } from '$lib/types';
|
||||
import { copyToClipboard } from '$lib/utils';
|
||||
import { downloadBlob } from '$lib/utils/asset-utils';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { getFormatter } from '$lib/utils/i18n';
|
||||
import { getConfig, updateConfig, type ServerFeaturesDto, type SystemConfigDto } from '@immich/sdk';
|
||||
import { toastManager, type ActionItem } from '@immich/ui';
|
||||
import { getConfig, unlinkAllOAuthAccountsAdmin, updateConfig, type ServerFeaturesDto, type SystemConfigDto } from '@immich/sdk';
|
||||
import { modalManager, toastManager, type ActionItem } from '@immich/ui';
|
||||
import {
|
||||
mdiAccountOutline,
|
||||
mdiBackupRestore,
|
||||
|
|
@ -18,141 +20,293 @@ import {
|
|||
mdiDownload,
|
||||
mdiFileDocumentOutline,
|
||||
mdiFolderOutline,
|
||||
mdiImageOutline,
|
||||
mdiLockOutline,
|
||||
mdiMapMarkerOutline,
|
||||
mdiPaletteOutline,
|
||||
mdiRestore,
|
||||
mdiRestart,
|
||||
mdiRobotOutline,
|
||||
mdiServerOutline,
|
||||
mdiSync,
|
||||
mdiTrashCanOutline,
|
||||
mdiUpdate,
|
||||
mdiUpload,
|
||||
mdiVideoOutline,
|
||||
mdiVideoOutline
|
||||
} from '@mdi/js';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import type { MessageFormatter } from 'svelte-i18n';
|
||||
|
||||
type SettingsGroup = {
|
||||
title: string,
|
||||
subtitle?: string;
|
||||
items: SettingItem[];
|
||||
}
|
||||
|
||||
type SettingItem = {
|
||||
title: string; subtitle: string; href: string; icon: string;
|
||||
};
|
||||
|
||||
export const resolveSetting = (groups: SettingsGroup[], pathname: string) => {
|
||||
for (const group of groups) {
|
||||
for (const item of group.items) {
|
||||
if (item.href === pathname) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const getSystemConfigActions = (
|
||||
$t: MessageFormatter,
|
||||
featureFlags: ServerFeaturesDto,
|
||||
config: SystemConfigDto,
|
||||
) => {
|
||||
const settings: Array<{ title: string; subtitle: string; href: string; icon: string }> = [
|
||||
const settings: SettingsGroup[] = [
|
||||
{
|
||||
title: $t('admin.authentication_settings'),
|
||||
subtitle: $t('admin.authentication_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/authentication`,
|
||||
icon: mdiLockOutline,
|
||||
|
||||
title: $t('admin.authentication_settings'),
|
||||
subtitle: $t('admin.authentication_settings_description'),
|
||||
|
||||
items: [
|
||||
{
|
||||
title:$t('admin.password_settings'), subtitle: $t('admin.password_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/password`,
|
||||
icon: mdiLockOutline,
|
||||
},
|
||||
{
|
||||
|
||||
title:$t('admin.oauth_settings'), subtitle:$t('admin.oauth_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/oauth`,
|
||||
icon: mdiFileDocumentOutline,
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
title: $t('admin.backup_settings'),
|
||||
subtitle: $t('admin.backup_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/backup`,
|
||||
icon: mdiBackupRestore,
|
||||
title: 'General', items: [
|
||||
// {
|
||||
// title: $t('admin.image_settings'),
|
||||
// subtitle: $t('admin.image_settings_description'),
|
||||
// href: `${AppRoute.ADMIN_SETTINGS}/image`,
|
||||
// icon: mdiImageOutline,
|
||||
// },
|
||||
{
|
||||
title: $t('admin.library_settings'),
|
||||
subtitle: $t('admin.library_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/library`,
|
||||
icon: mdiBookshelf,
|
||||
},
|
||||
// {
|
||||
// title: $t('admin.maintenance_settings'),
|
||||
// subtitle: $t('admin.maintenance_settings_description'),
|
||||
// href: `${AppRoute.ADMIN_SETTINGS}/maintenance`,
|
||||
// icon: mdiRestore,
|
||||
// },
|
||||
{
|
||||
title: $t('admin.map_gps_settings'),
|
||||
subtitle: $t('admin.map_gps_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/location`,
|
||||
icon: mdiMapMarkerOutline,
|
||||
},
|
||||
{
|
||||
title: $t('admin.metadata_settings'),
|
||||
subtitle: $t('admin.metadata_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/metadata`,
|
||||
icon: mdiDatabaseOutline,
|
||||
},
|
||||
{
|
||||
title: $t('admin.notification_settings'),
|
||||
subtitle: $t('admin.notification_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/notifications`,
|
||||
icon: mdiBellOutline,
|
||||
},
|
||||
{
|
||||
title: $t('admin.storage_template_settings'),
|
||||
subtitle: $t('admin.storage_template_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/storage-template`,
|
||||
icon: mdiFolderOutline,
|
||||
},
|
||||
{
|
||||
title: $t('admin.theme_settings'),
|
||||
subtitle: $t('admin.theme_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/theme`,
|
||||
icon: mdiPaletteOutline,
|
||||
},
|
||||
{
|
||||
title: $t('admin.trash_settings'),
|
||||
subtitle: $t('admin.trash_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/trash`,
|
||||
icon: mdiTrashCanOutline,
|
||||
},
|
||||
{
|
||||
title: $t('admin.user_settings'),
|
||||
subtitle: $t('admin.user_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/user`,
|
||||
icon: mdiAccountOutline,
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
title: 'Image', items: [
|
||||
{
|
||||
title: 'General settings',
|
||||
subtitle: $t('admin.transcoding_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/video-transcoding`,
|
||||
icon: mdiVideoOutline,
|
||||
},
|
||||
{
|
||||
title: 'Thumbnail settings',
|
||||
subtitle: $t('admin.transcoding_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/video-transcoding`,
|
||||
icon: mdiVideoOutline,
|
||||
},
|
||||
{
|
||||
title: 'Preview settings',
|
||||
subtitle: $t('admin.transcoding_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/video-transcoding`,
|
||||
icon: mdiVideoOutline,
|
||||
},
|
||||
{
|
||||
title: 'Full-size settings',
|
||||
subtitle: $t('admin.transcoding_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/video-transcoding`,
|
||||
icon: mdiVideoOutline,
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
title: $t('admin.image_settings'),
|
||||
subtitle: $t('admin.image_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/image`,
|
||||
icon: mdiImageOutline,
|
||||
title: 'Video', items: [
|
||||
{
|
||||
title: 'General settings',
|
||||
subtitle: $t('admin.transcoding_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/video-transcoding`,
|
||||
icon: mdiVideoOutline,
|
||||
},
|
||||
{
|
||||
title: 'Transcoding Policies',
|
||||
subtitle: $t('admin.transcoding_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/video-transcoding`,
|
||||
icon: mdiVideoOutline,
|
||||
},
|
||||
{
|
||||
title: 'Hardware Acceleration',
|
||||
subtitle: $t('admin.transcoding_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/video-transcoding`,
|
||||
icon: mdiVideoOutline,
|
||||
},
|
||||
{
|
||||
title: 'Encoding Options',
|
||||
subtitle: $t('admin.transcoding_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/video-transcoding`,
|
||||
icon: mdiVideoOutline,
|
||||
},
|
||||
{
|
||||
title: $t('admin.transcoding_settings'),
|
||||
subtitle: $t('admin.transcoding_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/video-transcoding`,
|
||||
icon: mdiVideoOutline,
|
||||
},
|
||||
{
|
||||
title: 'Advanced Settings',
|
||||
subtitle: $t('admin.transcoding_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/video-transcoding`,
|
||||
icon: mdiVideoOutline,
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
title: 'Machine learning', items: [
|
||||
{
|
||||
title: 'Connection settings',
|
||||
subtitle: $t('admin.machine_learning_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/machine-learning`,
|
||||
icon: mdiRobotOutline,
|
||||
},
|
||||
// {
|
||||
// title: $t('admin.machine_learning_settings'),
|
||||
// subtitle: $t('admin.machine_learning_settings_description'),
|
||||
// href: `${AppRoute.ADMIN_SETTINGS}/machine-learning`,
|
||||
// icon: mdiRobotOutline,
|
||||
// },
|
||||
{
|
||||
title: 'Search',
|
||||
subtitle: $t('admin.machine_learning_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/machine-learning`,
|
||||
icon: mdiRobotOutline,
|
||||
},
|
||||
{
|
||||
title: 'Duplicate Detection',
|
||||
subtitle: $t('admin.machine_learning_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/machine-learning`,
|
||||
icon: mdiRobotOutline,
|
||||
},
|
||||
{
|
||||
title: 'Facial Recognition',
|
||||
subtitle: $t('admin.machine_learning_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/machine-learning`,
|
||||
icon: mdiRobotOutline,
|
||||
},
|
||||
{
|
||||
title: 'OCR',
|
||||
subtitle: $t('admin.machine_learning_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/machine-learning`,
|
||||
icon: mdiRobotOutline,
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
title: 'Job settings', items: [
|
||||
{
|
||||
title: 'General settings',
|
||||
subtitle: $t('admin.server_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/server`,
|
||||
icon: mdiServerOutline,
|
||||
},
|
||||
{
|
||||
title: $t('admin.nightly_tasks_settings'),
|
||||
subtitle: $t('admin.nightly_tasks_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/nightly-tasks`,
|
||||
icon: mdiClockOutline,
|
||||
},
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
title: $t('admin.job_settings'),
|
||||
subtitle: $t('admin.job_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/job`,
|
||||
icon: mdiSync,
|
||||
},
|
||||
{
|
||||
title: $t('admin.library_settings'),
|
||||
subtitle: $t('admin.library_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/library`,
|
||||
icon: mdiBookshelf,
|
||||
},
|
||||
{
|
||||
title: $t('admin.logging_settings'),
|
||||
subtitle: $t('admin.manage_log_settings'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/logging`,
|
||||
icon: mdiFileDocumentOutline,
|
||||
},
|
||||
{
|
||||
title: $t('admin.machine_learning_settings'),
|
||||
subtitle: $t('admin.machine_learning_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/machine-learning`,
|
||||
icon: mdiRobotOutline,
|
||||
},
|
||||
{
|
||||
title: $t('admin.maintenance_settings'),
|
||||
subtitle: $t('admin.maintenance_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/maintenance`,
|
||||
icon: mdiRestore,
|
||||
},
|
||||
{
|
||||
title: $t('admin.map_gps_settings'),
|
||||
subtitle: $t('admin.map_gps_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/location`,
|
||||
icon: mdiMapMarkerOutline,
|
||||
},
|
||||
{
|
||||
title: $t('admin.metadata_settings'),
|
||||
subtitle: $t('admin.metadata_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/metadata`,
|
||||
icon: mdiDatabaseOutline,
|
||||
},
|
||||
{
|
||||
title: $t('admin.nightly_tasks_settings'),
|
||||
subtitle: $t('admin.nightly_tasks_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/nightly-tasks`,
|
||||
icon: mdiClockOutline,
|
||||
},
|
||||
{
|
||||
title: $t('admin.notification_settings'),
|
||||
subtitle: $t('admin.notification_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/notifications`,
|
||||
icon: mdiBellOutline,
|
||||
},
|
||||
{
|
||||
title: $t('admin.server_settings'),
|
||||
subtitle: $t('admin.server_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/server`,
|
||||
icon: mdiServerOutline,
|
||||
},
|
||||
{
|
||||
title: $t('admin.storage_template_settings'),
|
||||
subtitle: $t('admin.storage_template_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/storage-template`,
|
||||
icon: mdiFolderOutline,
|
||||
},
|
||||
{
|
||||
title: $t('admin.theme_settings'),
|
||||
subtitle: $t('admin.theme_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/theme`,
|
||||
icon: mdiPaletteOutline,
|
||||
},
|
||||
{
|
||||
title: $t('admin.trash_settings'),
|
||||
subtitle: $t('admin.trash_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/trash`,
|
||||
icon: mdiTrashCanOutline,
|
||||
},
|
||||
{
|
||||
title: $t('admin.user_settings'),
|
||||
subtitle: $t('admin.user_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/user`,
|
||||
icon: mdiAccountOutline,
|
||||
},
|
||||
{
|
||||
title: $t('admin.version_check_settings'),
|
||||
subtitle: $t('admin.version_check_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/version-check`,
|
||||
icon: mdiUpdate,
|
||||
},
|
||||
{
|
||||
title: $t('admin.transcoding_settings'),
|
||||
subtitle: $t('admin.transcoding_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/video-transcoding`,
|
||||
icon: mdiVideoOutline,
|
||||
title: 'Server settings', items: [
|
||||
{
|
||||
title: 'General settings',
|
||||
subtitle: $t('admin.server_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/server`,
|
||||
icon: mdiServerOutline,
|
||||
},
|
||||
{
|
||||
title: $t('admin.authentication_settings'),
|
||||
subtitle: $t('admin.authentication_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/authentication`,
|
||||
icon: mdiLockOutline,
|
||||
},
|
||||
|
||||
{
|
||||
title: $t('admin.job_settings'),
|
||||
subtitle: $t('admin.job_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/job`,
|
||||
icon: mdiSync,
|
||||
},
|
||||
{
|
||||
title: $t('admin.backup_settings'),
|
||||
subtitle: $t('admin.backup_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/backup`,
|
||||
icon: mdiBackupRestore,
|
||||
},
|
||||
{
|
||||
title: $t('admin.version_check_settings'),
|
||||
subtitle: $t('admin.version_check_settings_description'),
|
||||
href: `${AppRoute.ADMIN_SETTINGS}/version-check`,
|
||||
icon: mdiUpdate,
|
||||
},
|
||||
]
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -213,12 +367,12 @@ export const handleSystemConfigSave = async (update: Partial<SystemConfigDto>) =
|
|||
const jsonReplacer = (_key: string, value: unknown) =>
|
||||
value instanceof Object && !Array.isArray(value)
|
||||
? Object.keys(value)
|
||||
.sort()
|
||||
// eslint-disable-next-line unicorn/no-array-reduce
|
||||
.reduce((sorted: { [key: string]: unknown }, key) => {
|
||||
sorted[key] = (value as { [key: string]: unknown })[key];
|
||||
return sorted;
|
||||
}, {})
|
||||
.sort()
|
||||
// eslint-disable-next-line unicorn/no-array-reduce
|
||||
.reduce((sorted: { [key: string]: unknown }, key) => {
|
||||
sorted[key] = (value as { [key: string]: unknown })[key];
|
||||
return sorted;
|
||||
}, {})
|
||||
: value;
|
||||
|
||||
export const handleCopyToClipboard = async (config: SystemConfigDto) => {
|
||||
|
|
@ -255,3 +409,38 @@ export const handleUploadConfig = () => {
|
|||
});
|
||||
input.remove();
|
||||
};
|
||||
|
||||
|
||||
export const handleUnlinkAllOAuthAccounts = async () => {
|
||||
const $t = await getFormatter();
|
||||
const confirmed = await modalManager.showDialog({
|
||||
icon: mdiRestart,
|
||||
title: $t('admin.unlink_all_oauth_accounts'),
|
||||
prompt: $t('admin.unlink_all_oauth_accounts_prompt'),
|
||||
confirmColor: 'danger',
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await unlinkAllOAuthAccountsAdmin();
|
||||
toastManager.success();
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.something_went_wrong'));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export const onBeforeSave = async ({ configToEdit }: SystemConfigContext) => {
|
||||
const allMethodsDisabled = !configToEdit.oauth.enabled && !configToEdit.passwordLogin.enabled;
|
||||
if (allMethodsDisabled) {
|
||||
const isConfirmed = await modalManager.show(AuthDisableLoginConfirmModal, {});
|
||||
if (!isConfirmed) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
|
||||
import { getSystemConfigActions } from '$lib/services/system-config.service';
|
||||
import { Alert, Button, CommandPaletteContext, Icon, Text } from '@immich/ui';
|
||||
import { Alert, Button, Card, CardHeader, CardTitle, CommandPaletteContext, Container, Icon, Text } from '@immich/ui';
|
||||
import { mdiPencilOutline } from '@mdi/js';
|
||||
import type { Snippet } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
|
@ -22,39 +22,69 @@
|
|||
);
|
||||
|
||||
let searchQuery = $state('');
|
||||
let filteredSettings = $derived(
|
||||
settings.filter(({ title, subtitle }) => {
|
||||
const query = searchQuery.toLowerCase();
|
||||
return title.toLowerCase().includes(query) || subtitle.toLowerCase().includes(query);
|
||||
}),
|
||||
const filteredGroups = $derived(
|
||||
settings
|
||||
.map(({ title, items }) => {
|
||||
const query = searchQuery.toLowerCase();
|
||||
return {
|
||||
title,
|
||||
items: items.filter(
|
||||
({ title, subtitle }) => title.toLowerCase().includes(query) || subtitle.toLowerCase().includes(query),
|
||||
),
|
||||
};
|
||||
})
|
||||
.filter(({ items }) => items.length > 0),
|
||||
);
|
||||
</script>
|
||||
|
||||
<CommandPaletteContext commands={[CopyToClipboard, Upload, Download]} />
|
||||
|
||||
<AdminPageLayout breadcrumbs={[{ title: data.meta.title }]} actions={[CopyToClipboard, Download, Upload]}>
|
||||
<section id="setting-content" class="flex place-content-center sm:mx-4 mt-4">
|
||||
<section class="w-full pb-28 sm:w-5/6 md:w-4xl">
|
||||
{#if featureFlagsManager.value.configFile}
|
||||
<Alert color="warning" class="text-dark my-4" title={$t('admin.config_set_by_file')} />
|
||||
{/if}
|
||||
<div class="mb-4">
|
||||
<SearchBar placeholder={$t('search_settings')} bind:name={searchQuery} showLoadingSpinner={false} />
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
{#each filteredSettings as { title, subtitle, href, icon } (href)}
|
||||
<Button variant="outline" color="secondary" class="flex justify-between border-subtle" {href}>
|
||||
<div class="flex flex-col items-start">
|
||||
<Text size="large" fontWeight="semi-bold" color="primary" class="flex items-center gap-2">
|
||||
<Icon {icon} />
|
||||
{title}
|
||||
</Text>
|
||||
<Text>{subtitle}</Text>
|
||||
<section class="flex place-content-center sm:px-4 mt-4">
|
||||
<section class="w-full pb-28">
|
||||
<Container size="medium" center>
|
||||
{#if featureFlagsManager.value.configFile}
|
||||
<Alert color="warning" class="text-dark my-4" title={$t('admin.config_set_by_file')} />
|
||||
{/if}
|
||||
|
||||
<div class="mb-4">
|
||||
<SearchBar placeholder={$t('search_settings')} bind:name={searchQuery} showLoadingSpinner={false} />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-8">
|
||||
{#each filteredGroups as { title, items } (title)}
|
||||
<div>
|
||||
<Card color="secondary">
|
||||
<CardHeader class="px-5 py-3">
|
||||
<CardTitle>
|
||||
<Text color="primary" fontWeight="semi-bold">{title}</Text>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<div>
|
||||
{#each items as { title, subtitle, href, icon }, i (i)}
|
||||
<Button
|
||||
variant="outline"
|
||||
shape="rectangle"
|
||||
color="secondary"
|
||||
class="flex justify-between border-subtle"
|
||||
{href}
|
||||
>
|
||||
<div class="flex flex-col items-start">
|
||||
<Text fontWeight="semi-bold" class="flex items-center gap-2">
|
||||
<!-- <Icon {icon} /> -->
|
||||
{title}
|
||||
</Text>
|
||||
<Text class="line-clamp-1" color="muted">{subtitle}</Text>
|
||||
</div>
|
||||
<Icon icon={mdiPencilOutline} size="1.25rem" />
|
||||
</Button>
|
||||
{/each}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
<Icon icon={mdiPencilOutline} size="1.5rem" />
|
||||
</Button>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
</section>
|
||||
</AdminPageLayout>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,13 @@
|
|||
<script lang="ts">
|
||||
import SettingInputField from '$lib/components/shared-components/settings/setting-input-field.svelte';
|
||||
import SettingSelect from '$lib/components/shared-components/settings/setting-select.svelte';
|
||||
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
|
||||
import SystemSettingsCard from '$lib/components/SystemSettingsCard.svelte';
|
||||
import { SettingInputFieldType } from '$lib/constants';
|
||||
import FormatMessage from '$lib/elements/FormatMessage.svelte';
|
||||
import AuthDisableLoginConfirmModal from '$lib/modals/AuthDisableLoginConfirmModal.svelte';
|
||||
import SystemSettingsModal from '$lib/modals/SystemSettingsModal.svelte';
|
||||
import type { SystemConfigContext } from '$lib/types';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { OAuthTokenEndpointAuthMethod, unlinkAllOAuthAccountsAdmin, type SystemConfigDto } from '@immich/sdk';
|
||||
import { Button, modalManager, Text, toastManager } from '@immich/ui';
|
||||
import { Button, Field, Input, modalManager, NumberInput, Switch, Text, toastManager } from '@immich/ui';
|
||||
import { mdiRestart } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
|
|
@ -58,11 +55,9 @@
|
|||
<SystemSettingsModal keys={['passwordLogin', 'oauth']} size="large" {onBeforeSave}>
|
||||
{#snippet child({ disabled, config, configToEdit })}
|
||||
<SystemSettingsCard title={$t('admin.password_settings')} subtitle={$t('admin.password_settings_description')}>
|
||||
<SettingSwitch
|
||||
title={$t('admin.password_enable_description')}
|
||||
{disabled}
|
||||
bind:checked={configToEdit.passwordLogin.enabled}
|
||||
/>
|
||||
<Field label={$t('admin.password_enable_description')} {disabled}>
|
||||
<Switch bind:checked={configToEdit.passwordLogin.enabled} />
|
||||
</Field>
|
||||
</SystemSettingsCard>
|
||||
|
||||
<SystemSettingsCard title={$t('admin.oauth_settings')} subtitle={$t('admin.oauth_settings_description')}>
|
||||
|
|
@ -76,11 +71,9 @@
|
|||
</FormatMessage>
|
||||
</Text>
|
||||
|
||||
<SettingSwitch
|
||||
{disabled}
|
||||
title={$t('admin.oauth_enable_description')}
|
||||
bind:checked={configToEdit.oauth.enabled}
|
||||
/>
|
||||
<Field label={$t('admin.oauth_enable_description')} {disabled}>
|
||||
<Switch bind:checked={configToEdit.oauth.enabled} />
|
||||
</Field>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<Text size="small">{$t('admin.unlink_all_oauth_accounts_description')}</Text>
|
||||
|
|
@ -91,32 +84,17 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
label="ISSUER_URL"
|
||||
bind:value={configToEdit.oauth.issuerUrl}
|
||||
required={true}
|
||||
disabled={disabled || !configToEdit.oauth.enabled}
|
||||
isEdited={!(configToEdit.oauth.issuerUrl === config.oauth.issuerUrl)}
|
||||
/>
|
||||
<Field label="ISSUER_URL" required disabled={disabled || !configToEdit.oauth.enabled}>
|
||||
<Input bind:value={configToEdit.oauth.issuerUrl} />
|
||||
</Field>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
label="CLIENT_ID"
|
||||
bind:value={configToEdit.oauth.clientId}
|
||||
required={true}
|
||||
disabled={disabled || !configToEdit.oauth.enabled}
|
||||
isEdited={!(configToEdit.oauth.clientId === config.oauth.clientId)}
|
||||
/>
|
||||
<Field label="CLIENT_ID" required disabled={disabled || !configToEdit.oauth.enabled}>
|
||||
<Input bind:value={configToEdit.oauth.clientId} />
|
||||
</Field>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
label="CLIENT_SECRET"
|
||||
description={$t('admin.oauth_client_secret_description')}
|
||||
bind:value={configToEdit.oauth.clientSecret}
|
||||
disabled={disabled || !configToEdit.oauth.enabled}
|
||||
isEdited={!(configToEdit.oauth.clientSecret === config.oauth.clientSecret)}
|
||||
/>
|
||||
<Field label="CLIENT_SECRET" required disabled={disabled || !configToEdit.oauth.enabled}>
|
||||
<Input bind:value={configToEdit.oauth.clientSecret} />
|
||||
</Field>
|
||||
|
||||
{#if configToEdit.oauth.clientSecret}
|
||||
<SettingSelect
|
||||
|
|
@ -132,125 +110,108 @@
|
|||
/>
|
||||
{/if}
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
label="SCOPE"
|
||||
bind:value={configToEdit.oauth.scope}
|
||||
required={true}
|
||||
disabled={disabled || !configToEdit.oauth.enabled}
|
||||
isEdited={!(configToEdit.oauth.scope === config.oauth.scope)}
|
||||
/>
|
||||
<Field label="SCOPE" required disabled={disabled || !configToEdit.oauth.enabled}>
|
||||
<Input bind:value={configToEdit.oauth.scope} />
|
||||
</Field>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
label="ID_TOKEN_SIGNED_RESPONSE_ALG"
|
||||
bind:value={configToEdit.oauth.signingAlgorithm}
|
||||
required={true}
|
||||
disabled={disabled || !configToEdit.oauth.enabled}
|
||||
isEdited={!(configToEdit.oauth.signingAlgorithm === config.oauth.signingAlgorithm)}
|
||||
/>
|
||||
<Field label="ID_TOKEN_SIGNED_RESPONSE_ALG" required disabled={disabled || !configToEdit.oauth.enabled}>
|
||||
<Input bind:value={configToEdit.oauth.signingAlgorithm} />
|
||||
</Field>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
label="USERINFO_SIGNED_RESPONSE_ALG"
|
||||
bind:value={configToEdit.oauth.profileSigningAlgorithm}
|
||||
required={true}
|
||||
disabled={disabled || !configToEdit.oauth.enabled}
|
||||
isEdited={!(configToEdit.oauth.profileSigningAlgorithm === config.oauth.profileSigningAlgorithm)}
|
||||
/>
|
||||
<Field label="USERINFO_SIGNED_RESPONSE_ALG" required disabled={disabled || !configToEdit.oauth.enabled}>
|
||||
<Input bind:value={configToEdit.oauth.profileSigningAlgorithm} />
|
||||
</Field>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.NUMBER}
|
||||
<Field
|
||||
label={$t('admin.oauth_timeout')}
|
||||
description={$t('admin.oauth_timeout_description')}
|
||||
required={true}
|
||||
bind:value={configToEdit.oauth.timeout}
|
||||
required
|
||||
disabled={disabled || !configToEdit.oauth.enabled}
|
||||
isEdited={!(configToEdit.oauth.timeout === config.oauth.timeout)}
|
||||
/>
|
||||
>
|
||||
<NumberInput bind:value={configToEdit.oauth.timeout} />
|
||||
</Field>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
<Field
|
||||
label={$t('admin.oauth_storage_label_claim')}
|
||||
description={$t('admin.oauth_storage_label_claim_description')}
|
||||
bind:value={configToEdit.oauth.storageLabelClaim}
|
||||
required={true}
|
||||
required
|
||||
disabled={disabled || !configToEdit.oauth.enabled}
|
||||
isEdited={!(configToEdit.oauth.storageLabelClaim === config.oauth.storageLabelClaim)}
|
||||
/>
|
||||
>
|
||||
<Input bind:value={configToEdit.oauth.storageLabelClaim} />
|
||||
</Field>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
<Field
|
||||
label={$t('admin.oauth_role_claim')}
|
||||
description={$t('admin.oauth_role_claim_description')}
|
||||
bind:value={configToEdit.oauth.roleClaim}
|
||||
required={true}
|
||||
required
|
||||
disabled={disabled || !configToEdit.oauth.enabled}
|
||||
isEdited={!(configToEdit.oauth.roleClaim === config.oauth.roleClaim)}
|
||||
/>
|
||||
>
|
||||
<Input bind:value={configToEdit.oauth.roleClaim} />
|
||||
</Field>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
<Field
|
||||
label={$t('admin.oauth_storage_quota_claim')}
|
||||
description={$t('admin.oauth_storage_quota_claim_description')}
|
||||
bind:value={configToEdit.oauth.storageQuotaClaim}
|
||||
required={true}
|
||||
required
|
||||
disabled={disabled || !configToEdit.oauth.enabled}
|
||||
isEdited={!(configToEdit.oauth.storageQuotaClaim === config.oauth.storageQuotaClaim)}
|
||||
/>
|
||||
>
|
||||
<Input bind:value={configToEdit.oauth.storageQuotaClaim} />
|
||||
</Field>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.NUMBER}
|
||||
<Field
|
||||
label={$t('admin.oauth_storage_quota_default')}
|
||||
description={$t('admin.oauth_storage_quota_default_description')}
|
||||
bind:value={configToEdit.oauth.defaultStorageQuota}
|
||||
required={false}
|
||||
disabled={disabled || !configToEdit.oauth.enabled}
|
||||
isEdited={!(configToEdit.oauth.defaultStorageQuota === config.oauth.defaultStorageQuota)}
|
||||
/>
|
||||
>
|
||||
<NumberInput
|
||||
bind:value={
|
||||
() => configToEdit.oauth.defaultStorageQuota ?? undefined,
|
||||
(value) => (configToEdit.oauth.defaultStorageQuota = value ?? null)
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
label={$t('admin.oauth_button_text')}
|
||||
bind:value={configToEdit.oauth.buttonText}
|
||||
required={false}
|
||||
<Field label={$t('admin.oauth_button_text')} disabled={disabled || !configToEdit.oauth.enabled}>
|
||||
<Input bind:value={configToEdit.oauth.buttonText} />
|
||||
</Field>
|
||||
|
||||
<Field
|
||||
label={$t('admin.oauth_auto_register')}
|
||||
description={$t('admin.oauth_auto_register_description')}
|
||||
disabled={disabled || !configToEdit.oauth.enabled}
|
||||
isEdited={!(configToEdit.oauth.buttonText === config.oauth.buttonText)}
|
||||
/>
|
||||
>
|
||||
<Switch bind:checked={configToEdit.oauth.autoRegister} />
|
||||
</Field>
|
||||
|
||||
<SettingSwitch
|
||||
title={$t('admin.oauth_auto_register')}
|
||||
subtitle={$t('admin.oauth_auto_register_description')}
|
||||
bind:checked={configToEdit.oauth.autoRegister}
|
||||
<Field
|
||||
label={$t('admin.oauth_auto_launch')}
|
||||
description={$t('admin.oauth_auto_launch_description')}
|
||||
disabled={disabled || !configToEdit.oauth.enabled}
|
||||
/>
|
||||
>
|
||||
<Switch bind:checked={configToEdit.oauth.autoLaunch} />
|
||||
</Field>
|
||||
|
||||
<SettingSwitch
|
||||
title={$t('admin.oauth_auto_launch')}
|
||||
subtitle={$t('admin.oauth_auto_launch_description')}
|
||||
disabled={disabled || !configToEdit.oauth.enabled}
|
||||
bind:checked={configToEdit.oauth.autoLaunch}
|
||||
/>
|
||||
|
||||
<SettingSwitch
|
||||
title={$t('admin.oauth_mobile_redirect_uri_override')}
|
||||
subtitle={$t('admin.oauth_mobile_redirect_uri_override_description', {
|
||||
<Field
|
||||
label={$t('admin.oauth_mobile_redirect_uri_override')}
|
||||
description={$t('admin.oauth_mobile_redirect_uri_override_description', {
|
||||
values: { callback: 'app.immich:///oauth-callback' },
|
||||
})}
|
||||
disabled={disabled || !configToEdit.oauth.enabled}
|
||||
onToggle={() => handleToggleOverride(configToEdit)}
|
||||
bind:checked={configToEdit.oauth.mobileOverrideEnabled}
|
||||
/>
|
||||
>
|
||||
<Switch
|
||||
bind:checked={configToEdit.oauth.mobileOverrideEnabled}
|
||||
onCheckedChange={() => handleToggleOverride(configToEdit)}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
{#if configToEdit.oauth.mobileOverrideEnabled}
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
<Field
|
||||
label={$t('admin.oauth_mobile_redirect_uri')}
|
||||
bind:value={configToEdit.oauth.mobileRedirectUri}
|
||||
required={true}
|
||||
required
|
||||
disabled={disabled || !configToEdit.oauth.enabled}
|
||||
isEdited={!(configToEdit.oauth.mobileRedirectUri === config.oauth.mobileRedirectUri)}
|
||||
/>
|
||||
>
|
||||
<Input bind:value={configToEdit.oauth.mobileRedirectUri} />
|
||||
</Field>
|
||||
{/if}
|
||||
</SystemSettingsCard>
|
||||
{/snippet}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
<script lang="ts">
|
||||
import SettingInputField from '$lib/components/shared-components/settings/setting-input-field.svelte';
|
||||
import SettingSelect from '$lib/components/shared-components/settings/setting-select.svelte';
|
||||
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
|
||||
import { SettingInputFieldType } from '$lib/constants';
|
||||
import FormatMessage from '$lib/elements/FormatMessage.svelte';
|
||||
import SystemSettingsModal from '$lib/modals/SystemSettingsModal.svelte';
|
||||
import { Field, HelperText, Input, Link, NumberInput, Switch } from '@immich/ui';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
let cronExpressionOptions = $derived([
|
||||
|
|
@ -16,12 +14,10 @@
|
|||
</script>
|
||||
|
||||
<SystemSettingsModal keys={['backup']}>
|
||||
{#snippet child({ disabled, config, configToEdit })}
|
||||
<SettingSwitch
|
||||
title={$t('admin.backup_database_enable_description')}
|
||||
{disabled}
|
||||
bind:checked={configToEdit.backup.database.enabled}
|
||||
/>
|
||||
{#snippet child({ disabled, configToEdit })}
|
||||
<Field label={$t('admin.backup_database_enable_description')} {disabled}>
|
||||
<Switch bind:checked={configToEdit.backup.database.enabled} />
|
||||
</Field>
|
||||
|
||||
<SettingSelect
|
||||
options={cronExpressionOptions}
|
||||
|
|
@ -31,40 +27,25 @@
|
|||
bind:value={configToEdit.backup.database.cronExpression}
|
||||
/>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
required={true}
|
||||
disabled={disabled || !configToEdit.backup.database.enabled}
|
||||
label={$t('admin.cron_expression')}
|
||||
bind:value={configToEdit.backup.database.cronExpression}
|
||||
isEdited={configToEdit.backup.database.cronExpression !== config.backup.database.cronExpression}
|
||||
>
|
||||
{#snippet descriptionSnippet()}
|
||||
<p class="text-sm dark:text-immich-dark-fg">
|
||||
<FormatMessage key="admin.cron_expression_description">
|
||||
{#snippet children({ message })}
|
||||
<a
|
||||
href="https://crontab.guru/#{configToEdit.backup.database.cronExpression.replaceAll(' ', '_')}"
|
||||
class="underline"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{message}
|
||||
<br />
|
||||
</a>
|
||||
{/snippet}
|
||||
</FormatMessage>
|
||||
</p>
|
||||
{/snippet}
|
||||
</SettingInputField>
|
||||
<Field label={$t('admin.cron_expression')} required disabled={disabled || !configToEdit.backup.database.enabled}>
|
||||
<Input bind:value={configToEdit.backup.database.cronExpression} />
|
||||
<HelperText>
|
||||
<FormatMessage key="admin.cron_expression_description">
|
||||
{#snippet children({ message })}
|
||||
<Link href="https://crontab.guru/#{configToEdit.backup.database.cronExpression.replaceAll(' ', '_')}">
|
||||
{message}
|
||||
</Link>
|
||||
{/snippet}
|
||||
</FormatMessage>
|
||||
</HelperText>
|
||||
</Field>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.NUMBER}
|
||||
required={true}
|
||||
<Field
|
||||
required
|
||||
label={$t('admin.backup_keep_last_amount')}
|
||||
disabled={disabled || !configToEdit.backup.database.enabled}
|
||||
bind:value={configToEdit.backup.database.keepLastAmount}
|
||||
isEdited={configToEdit.backup.database.keepLastAmount !== config.backup.database.keepLastAmount}
|
||||
/>
|
||||
>
|
||||
<NumberInput bind:value={configToEdit.backup.database.keepLastAmount}></NumberInput>
|
||||
</Field>
|
||||
{/snippet}
|
||||
</SystemSettingsModal>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
<script lang="ts">
|
||||
import SettingInputField from '$lib/components/shared-components/settings/setting-input-field.svelte';
|
||||
import { SettingInputFieldType } from '$lib/constants';
|
||||
import SystemSettingsModal from '$lib/modals/SystemSettingsModal.svelte';
|
||||
import { getQueueName } from '$lib/utils';
|
||||
import { QueueName, type SystemConfigDto, type SystemConfigJobDto } from '@immich/sdk';
|
||||
import { Field, HelperText, NumberInput } from '@immich/ui';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
const queueNames = [
|
||||
|
|
@ -29,30 +28,23 @@
|
|||
</script>
|
||||
|
||||
<SystemSettingsModal keys={['user']}>
|
||||
{#snippet child({ disabled, config, configToEdit })}
|
||||
{#snippet child({ disabled, configToEdit })}
|
||||
{#each queueNames as queueName (queueName)}
|
||||
<div class="ms-4 mt-4 flex flex-col gap-4">
|
||||
{#if isSystemConfigJobDto(configToEdit, queueName)}
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.NUMBER}
|
||||
{disabled}
|
||||
label={$t('admin.job_concurrency', { values: { job: $getQueueName(queueName) } })}
|
||||
description=""
|
||||
bind:value={configToEdit.job[queueName].concurrency}
|
||||
required={true}
|
||||
isEdited={!(configToEdit.job[queueName].concurrency == config.job[queueName].concurrency)}
|
||||
/>
|
||||
{:else}
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.NUMBER}
|
||||
label={$t('admin.job_concurrency', { values: { job: $getQueueName(queueName) } })}
|
||||
description=""
|
||||
value={1}
|
||||
disabled={true}
|
||||
title={$t('admin.job_not_concurrency_safe')}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{#if isSystemConfigJobDto(configToEdit, queueName)}
|
||||
<Field
|
||||
required
|
||||
{disabled}
|
||||
label={$t('admin.job_concurrency', { values: { job: $getQueueName(queueName) } })}
|
||||
description=""
|
||||
>
|
||||
<NumberInput bind:value={configToEdit.job[queueName].concurrency} />
|
||||
</Field>
|
||||
{:else}
|
||||
<Field label={$t('admin.job_concurrency', { values: { job: $getQueueName(queueName) } })}>
|
||||
<NumberInput value={1} disabled={true} />
|
||||
<HelperText>{$t('admin.job_not_concurrency_safe')}</HelperText>
|
||||
</Field>
|
||||
{/if}
|
||||
{/each}
|
||||
{/snippet}
|
||||
</SystemSettingsModal>
|
||||
|
|
|
|||
|
|
@ -1,20 +1,17 @@
|
|||
<script lang="ts">
|
||||
import SettingInputField from '$lib/components/shared-components/settings/setting-input-field.svelte';
|
||||
import { SettingInputFieldType } from '$lib/constants';
|
||||
import SystemSettingsModal from '$lib/modals/SystemSettingsModal.svelte';
|
||||
import { Field, NumberInput } from '@immich/ui';
|
||||
import { t } from 'svelte-i18n';
|
||||
</script>
|
||||
|
||||
<SystemSettingsModal keys={['user']}>
|
||||
{#snippet child({ disabled, config, configToEdit })}
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.NUMBER}
|
||||
min={1}
|
||||
{#snippet child({ disabled, configToEdit })}
|
||||
<Field
|
||||
label={$t('admin.user_delete_delay_settings')}
|
||||
description={$t('admin.user_delete_delay_settings_description')}
|
||||
bind:value={configToEdit.user.deleteDelay}
|
||||
{disabled}
|
||||
isEdited={configToEdit.user.deleteDelay !== config.user.deleteDelay}
|
||||
/>
|
||||
>
|
||||
<NumberInput bind:value={configToEdit.user.deleteDelay} min={1} />
|
||||
</Field>
|
||||
{/snippet}
|
||||
</SystemSettingsModal>
|
||||
|
|
|
|||
|
|
@ -1,18 +1,17 @@
|
|||
<script lang="ts">
|
||||
import SettingSelect from '$lib/components/shared-components/settings/setting-select.svelte';
|
||||
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
|
||||
import SystemSettingsModal from '$lib/modals/SystemSettingsModal.svelte';
|
||||
import { LogLevel } from '@immich/sdk';
|
||||
import { Field, Switch } from '@immich/ui';
|
||||
import { t } from 'svelte-i18n';
|
||||
</script>
|
||||
|
||||
<SystemSettingsModal keys={['logging']}>
|
||||
{#snippet child({ disabled, config, configToEdit })}
|
||||
<SettingSwitch
|
||||
title={$t('admin.logging_enable_description')}
|
||||
{disabled}
|
||||
bind:checked={configToEdit.logging.enabled}
|
||||
/>
|
||||
<Field required {disabled} label={$t('admin.logging_enable_description')}>
|
||||
<Switch bind:checked={configToEdit.logging.enabled} />
|
||||
</Field>
|
||||
|
||||
<SettingSelect
|
||||
label={$t('level')}
|
||||
desc={$t('admin.logging_level_description')}
|
||||
|
|
|
|||
|
|
@ -1,294 +1,281 @@
|
|||
<script lang="ts">
|
||||
import SystemSettingsCard from '$lib/components/SystemSettingsCard.svelte';
|
||||
import SettingInputField from '$lib/components/shared-components/settings/setting-input-field.svelte';
|
||||
import SettingSelect from '$lib/components/shared-components/settings/setting-select.svelte';
|
||||
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
|
||||
import { SettingInputFieldType } from '$lib/constants';
|
||||
import FormatMessage from '$lib/elements/FormatMessage.svelte';
|
||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import SystemSettingsModal from '$lib/modals/SystemSettingsModal.svelte';
|
||||
import { Button, IconButton } from '@immich/ui';
|
||||
import {
|
||||
Button,
|
||||
Field,
|
||||
HelperText,
|
||||
IconButton,
|
||||
Input,
|
||||
Label,
|
||||
Link,
|
||||
NumberInput,
|
||||
Stack,
|
||||
Switch,
|
||||
Text,
|
||||
} from '@immich/ui';
|
||||
import { mdiPlus, mdiTrashCanOutline } from '@mdi/js';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { t } from 'svelte-i18n';
|
||||
</script>
|
||||
|
||||
<SystemSettingsModal keys={['machineLearning']} size="large">
|
||||
{#snippet child({ disabled, config, configToEdit })}
|
||||
<div class="flex flex-col gap-4">
|
||||
<SettingSwitch
|
||||
title={$t('admin.machine_learning_enabled')}
|
||||
subtitle={$t('admin.machine_learning_enabled_description')}
|
||||
{disabled}
|
||||
bind:checked={configToEdit.machineLearning.enabled}
|
||||
/>
|
||||
<Field
|
||||
label={$t('admin.machine_learning_enabled')}
|
||||
description={$t('admin.machine_learning_enabled_description')}
|
||||
{disabled}
|
||||
>
|
||||
<Switch bind:checked={configToEdit.machineLearning.enabled} />
|
||||
</Field>
|
||||
|
||||
<div>
|
||||
{#each configToEdit.machineLearning.urls as _, i (i)}
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
label={i === 0 ? $t('url') : undefined}
|
||||
description={i === 0 ? $t('admin.machine_learning_url_description') : undefined}
|
||||
bind:value={configToEdit.machineLearning.urls[i]}
|
||||
required={i === 0}
|
||||
disabled={disabled || !configToEdit.machineLearning.enabled}
|
||||
isEdited={i === 0 && !isEqual(configToEdit.machineLearning.urls, config.machineLearning.urls)}
|
||||
>
|
||||
{#snippet trailingSnippet()}
|
||||
{#if configToEdit.machineLearning.urls.length > 1}
|
||||
<IconButton
|
||||
aria-label=""
|
||||
onclick={() => configToEdit.machineLearning.urls.splice(i, 1)}
|
||||
icon={mdiTrashCanOutline}
|
||||
color="danger"
|
||||
/>
|
||||
{/if}
|
||||
{/snippet}
|
||||
</SettingInputField>
|
||||
{/each}
|
||||
</div>
|
||||
<Label label={$t('url')} />
|
||||
<Text size="small" color="muted">{$t('admin.machine_learning_url_description')}</Text>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<Button
|
||||
class="mb-2"
|
||||
size="small"
|
||||
shape="round"
|
||||
leadingIcon={mdiPlus}
|
||||
onclick={() => configToEdit.machineLearning.urls.push('')}
|
||||
disabled={disabled || !configToEdit.machineLearning.enabled}>{$t('add_url')}</Button
|
||||
>
|
||||
</div>
|
||||
|
||||
<SystemSettingsCard
|
||||
title={$t('admin.machine_learning_availability_checks')}
|
||||
subtitle={$t('admin.machine_learning_availability_checks_description')}
|
||||
>
|
||||
<SettingSwitch
|
||||
title={$t('admin.machine_learning_availability_checks_enabled')}
|
||||
bind:checked={configToEdit.machineLearning.availabilityChecks.enabled}
|
||||
<Stack>
|
||||
{#each configToEdit.machineLearning.urls as _, i (i)}
|
||||
<Input
|
||||
bind:value={configToEdit.machineLearning.urls[i]}
|
||||
disabled={disabled || !configToEdit.machineLearning.enabled}
|
||||
/>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.NUMBER}
|
||||
label={$t('admin.machine_learning_availability_checks_interval')}
|
||||
bind:value={configToEdit.machineLearning.availabilityChecks.interval}
|
||||
description={$t('admin.machine_learning_availability_checks_interval_description')}
|
||||
disabled={disabled ||
|
||||
!configToEdit.machineLearning.enabled ||
|
||||
!configToEdit.machineLearning.availabilityChecks.enabled}
|
||||
isEdited={configToEdit.machineLearning.availabilityChecks.interval !==
|
||||
config.machineLearning.availabilityChecks.interval}
|
||||
/>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.NUMBER}
|
||||
label={$t('admin.machine_learning_availability_checks_timeout')}
|
||||
bind:value={configToEdit.machineLearning.availabilityChecks.timeout}
|
||||
description={$t('admin.machine_learning_availability_checks_timeout_description')}
|
||||
disabled={disabled ||
|
||||
!configToEdit.machineLearning.enabled ||
|
||||
!configToEdit.machineLearning.availabilityChecks.enabled}
|
||||
isEdited={configToEdit.machineLearning.availabilityChecks.timeout !==
|
||||
config.machineLearning.availabilityChecks.timeout}
|
||||
/>
|
||||
</SystemSettingsCard>
|
||||
|
||||
<SystemSettingsCard
|
||||
title={$t('admin.machine_learning_smart_search')}
|
||||
subtitle={$t('admin.machine_learning_smart_search_description')}
|
||||
>
|
||||
<SettingSwitch
|
||||
title={$t('admin.machine_learning_smart_search_enabled')}
|
||||
subtitle={$t('admin.machine_learning_smart_search_enabled_description')}
|
||||
bind:checked={configToEdit.machineLearning.clip.enabled}
|
||||
disabled={disabled || !configToEdit.machineLearning.enabled}
|
||||
/>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
label={$t('admin.machine_learning_clip_model')}
|
||||
bind:value={configToEdit.machineLearning.clip.modelName}
|
||||
required={true}
|
||||
disabled={disabled || !configToEdit.machineLearning.enabled || !configToEdit.machineLearning.clip.enabled}
|
||||
isEdited={configToEdit.machineLearning.clip.modelName !== config.machineLearning.clip.modelName}
|
||||
>
|
||||
{#snippet descriptionSnippet()}
|
||||
<p class="immich-form-label pb-2 text-sm">
|
||||
<FormatMessage key="admin.machine_learning_clip_model_description">
|
||||
{#snippet children({ message })}
|
||||
<a target="_blank" href="https://huggingface.co/immich-app"><u>{message}</u></a>
|
||||
{/snippet}
|
||||
</FormatMessage>
|
||||
</p>
|
||||
{#snippet trailingIcon()}
|
||||
{#if configToEdit.machineLearning.urls.length > 1}
|
||||
<IconButton
|
||||
size="small"
|
||||
aria-label={$t('remove')}
|
||||
onclick={() => configToEdit.machineLearning.urls.splice(i, 1)}
|
||||
icon={mdiTrashCanOutline}
|
||||
color="danger"
|
||||
/>
|
||||
{/if}
|
||||
{/snippet}
|
||||
</SettingInputField>
|
||||
</SystemSettingsCard>
|
||||
</Input>
|
||||
{/each}
|
||||
</Stack>
|
||||
|
||||
<SystemSettingsCard
|
||||
title={$t('admin.machine_learning_duplicate_detection')}
|
||||
subtitle={$t('admin.machine_learning_duplicate_detection_setting_description')}
|
||||
<div class="flex justify-end">
|
||||
<Button
|
||||
class="mb-2"
|
||||
size="small"
|
||||
shape="round"
|
||||
leadingIcon={mdiPlus}
|
||||
onclick={() => configToEdit.machineLearning.urls.push('')}
|
||||
disabled={disabled || !configToEdit.machineLearning.enabled}>{$t('add_url')}</Button
|
||||
>
|
||||
<SettingSwitch
|
||||
title={$t('admin.machine_learning_duplicate_detection_enabled')}
|
||||
subtitle={$t('admin.machine_learning_duplicate_detection_enabled_description')}
|
||||
bind:checked={configToEdit.machineLearning.duplicateDetection.enabled}
|
||||
disabled={disabled || !configToEdit.machineLearning.enabled || !configToEdit.machineLearning.clip.enabled}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.NUMBER}
|
||||
label={$t('admin.machine_learning_max_detection_distance')}
|
||||
<SystemSettingsCard
|
||||
title={$t('admin.machine_learning_availability_checks')}
|
||||
subtitle={$t('admin.machine_learning_availability_checks_description')}
|
||||
>
|
||||
<Field
|
||||
label={$t('admin.machine_learning_availability_checks_enabled')}
|
||||
disabled={disabled || !configToEdit.machineLearning.enabled}
|
||||
>
|
||||
<Switch bind:checked={configToEdit.machineLearning.availabilityChecks.enabled} />
|
||||
</Field>
|
||||
|
||||
<Field
|
||||
label={$t('admin.machine_learning_availability_checks_interval')}
|
||||
description={$t('admin.machine_learning_availability_checks_interval_description')}
|
||||
disabled={disabled ||
|
||||
!configToEdit.machineLearning.enabled ||
|
||||
!configToEdit.machineLearning.availabilityChecks.enabled}
|
||||
>
|
||||
<NumberInput bind:value={configToEdit.machineLearning.availabilityChecks.interval} />
|
||||
</Field>
|
||||
|
||||
<Field
|
||||
label={$t('admin.machine_learning_availability_checks_timeout')}
|
||||
description={$t('admin.machine_learning_availability_checks_timeout_description')}
|
||||
disabled={disabled ||
|
||||
!configToEdit.machineLearning.enabled ||
|
||||
!configToEdit.machineLearning.availabilityChecks.enabled}
|
||||
>
|
||||
<NumberInput bind:value={configToEdit.machineLearning.availabilityChecks.timeout} />
|
||||
</Field>
|
||||
</SystemSettingsCard>
|
||||
|
||||
<SystemSettingsCard
|
||||
title={$t('admin.machine_learning_smart_search')}
|
||||
subtitle={$t('admin.machine_learning_smart_search_description')}
|
||||
>
|
||||
<Field
|
||||
label={$t('admin.machine_learning_smart_search_enabled')}
|
||||
description={$t('admin.machine_learning_smart_search_enabled_description')}
|
||||
disabled={disabled || !configToEdit.machineLearning.enabled}
|
||||
>
|
||||
<Switch bind:checked={configToEdit.machineLearning.clip.enabled} />
|
||||
</Field>
|
||||
|
||||
<Field
|
||||
label={$t('admin.machine_learning_clip_model')}
|
||||
required={true}
|
||||
disabled={disabled || !configToEdit.machineLearning.enabled || !configToEdit.machineLearning.clip.enabled}
|
||||
>
|
||||
<Input bind:value={configToEdit.machineLearning.clip.modelName} />
|
||||
<HelperText>
|
||||
<FormatMessage key="admin.machine_learning_clip_model_description">
|
||||
{#snippet children({ message })}
|
||||
<Link href="https://huggingface.co/immich-app"><u>{message}</u></Link>
|
||||
{/snippet}
|
||||
</FormatMessage>
|
||||
</HelperText>
|
||||
</Field>
|
||||
</SystemSettingsCard>
|
||||
|
||||
<SystemSettingsCard
|
||||
title={$t('admin.machine_learning_duplicate_detection')}
|
||||
subtitle={$t('admin.machine_learning_duplicate_detection_setting_description')}
|
||||
>
|
||||
<Field
|
||||
label={$t('admin.machine_learning_duplicate_detection_enabled')}
|
||||
description={$t('admin.machine_learning_duplicate_detection_enabled_description')}
|
||||
disabled={disabled || !configToEdit.machineLearning.enabled || !configToEdit.machineLearning.clip.enabled}
|
||||
>
|
||||
<Switch bind:checked={configToEdit.machineLearning.duplicateDetection.enabled} />
|
||||
</Field>
|
||||
|
||||
<Field
|
||||
label={$t('admin.machine_learning_max_detection_distance')}
|
||||
description={$t('admin.machine_learning_max_detection_distance_description')}
|
||||
disabled={disabled || !featureFlagsManager.value.duplicateDetection}
|
||||
>
|
||||
<NumberInput
|
||||
bind:value={configToEdit.machineLearning.duplicateDetection.maxDistance}
|
||||
step="0.0005"
|
||||
min={0.001}
|
||||
max={0.1}
|
||||
description={$t('admin.machine_learning_max_detection_distance_description')}
|
||||
disabled={disabled || !featureFlagsManager.value.duplicateDetection}
|
||||
isEdited={configToEdit.machineLearning.duplicateDetection.maxDistance !==
|
||||
config.machineLearning.duplicateDetection.maxDistance}
|
||||
/>
|
||||
</SystemSettingsCard>
|
||||
</Field>
|
||||
</SystemSettingsCard>
|
||||
|
||||
<SystemSettingsCard
|
||||
title={$t('admin.machine_learning_facial_recognition')}
|
||||
subtitle={$t('admin.machine_learning_facial_recognition_description')}
|
||||
<SystemSettingsCard
|
||||
title={$t('admin.machine_learning_facial_recognition')}
|
||||
subtitle={$t('admin.machine_learning_facial_recognition_description')}
|
||||
>
|
||||
<Field
|
||||
label={$t('admin.machine_learning_facial_recognition_setting')}
|
||||
description={$t('admin.machine_learning_facial_recognition_setting_description')}
|
||||
disabled={disabled || !configToEdit.machineLearning.enabled}
|
||||
>
|
||||
<SettingSwitch
|
||||
title={$t('admin.machine_learning_facial_recognition_setting')}
|
||||
subtitle={$t('admin.machine_learning_facial_recognition_setting_description')}
|
||||
bind:checked={configToEdit.machineLearning.facialRecognition.enabled}
|
||||
disabled={disabled || !configToEdit.machineLearning.enabled}
|
||||
/>
|
||||
<Switch bind:checked={configToEdit.machineLearning.facialRecognition.enabled} />
|
||||
</Field>
|
||||
|
||||
<SettingSelect
|
||||
label={$t('admin.machine_learning_facial_recognition_model')}
|
||||
desc={$t('admin.machine_learning_facial_recognition_model_description')}
|
||||
name="facial-recognition-model"
|
||||
bind:value={configToEdit.machineLearning.facialRecognition.modelName}
|
||||
options={[
|
||||
{ value: 'antelopev2', text: 'antelopev2' },
|
||||
{ value: 'buffalo_l', text: 'buffalo_l' },
|
||||
{ value: 'buffalo_m', text: 'buffalo_m' },
|
||||
{ value: 'buffalo_s', text: 'buffalo_s' },
|
||||
]}
|
||||
disabled={disabled ||
|
||||
!configToEdit.machineLearning.enabled ||
|
||||
!configToEdit.machineLearning.facialRecognition.enabled}
|
||||
isEdited={configToEdit.machineLearning.facialRecognition.modelName !==
|
||||
config.machineLearning.facialRecognition.modelName}
|
||||
/>
|
||||
<SettingSelect
|
||||
label={$t('admin.machine_learning_facial_recognition_model')}
|
||||
desc={$t('admin.machine_learning_facial_recognition_model_description')}
|
||||
name="facial-recognition-model"
|
||||
bind:value={configToEdit.machineLearning.facialRecognition.modelName}
|
||||
options={[
|
||||
{ value: 'antelopev2', text: 'antelopev2' },
|
||||
{ value: 'buffalo_l', text: 'buffalo_l' },
|
||||
{ value: 'buffalo_m', text: 'buffalo_m' },
|
||||
{ value: 'buffalo_s', text: 'buffalo_s' },
|
||||
]}
|
||||
disabled={disabled ||
|
||||
!configToEdit.machineLearning.enabled ||
|
||||
!configToEdit.machineLearning.facialRecognition.enabled}
|
||||
isEdited={configToEdit.machineLearning.facialRecognition.modelName !==
|
||||
config.machineLearning.facialRecognition.modelName}
|
||||
/>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.NUMBER}
|
||||
label={$t('admin.machine_learning_min_detection_score')}
|
||||
description={$t('admin.machine_learning_min_detection_score_description')}
|
||||
<Field
|
||||
label={$t('admin.machine_learning_min_detection_score')}
|
||||
description={$t('admin.machine_learning_min_detection_score_description')}
|
||||
disabled={disabled ||
|
||||
!configToEdit.machineLearning.enabled ||
|
||||
!configToEdit.machineLearning.facialRecognition.enabled}
|
||||
>
|
||||
<NumberInput
|
||||
bind:value={configToEdit.machineLearning.facialRecognition.minScore}
|
||||
step="0.01"
|
||||
min={0.1}
|
||||
max={1}
|
||||
disabled={disabled ||
|
||||
!configToEdit.machineLearning.enabled ||
|
||||
!configToEdit.machineLearning.facialRecognition.enabled}
|
||||
isEdited={configToEdit.machineLearning.facialRecognition.minScore !==
|
||||
config.machineLearning.facialRecognition.minScore}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.NUMBER}
|
||||
label={$t('admin.machine_learning_max_recognition_distance')}
|
||||
description={$t('admin.machine_learning_max_recognition_distance_description')}
|
||||
<Field
|
||||
label={$t('admin.machine_learning_max_recognition_distance')}
|
||||
description={$t('admin.machine_learning_max_recognition_distance_description')}
|
||||
disabled={disabled ||
|
||||
!configToEdit.machineLearning.enabled ||
|
||||
!configToEdit.machineLearning.facialRecognition.enabled}
|
||||
>
|
||||
<NumberInput
|
||||
bind:value={configToEdit.machineLearning.facialRecognition.maxDistance}
|
||||
step="0.01"
|
||||
min={0.1}
|
||||
max={2}
|
||||
disabled={disabled ||
|
||||
!configToEdit.machineLearning.enabled ||
|
||||
!configToEdit.machineLearning.facialRecognition.enabled}
|
||||
isEdited={configToEdit.machineLearning.facialRecognition.maxDistance !==
|
||||
config.machineLearning.facialRecognition.maxDistance}
|
||||
/>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.NUMBER}
|
||||
label={$t('admin.machine_learning_min_recognized_faces')}
|
||||
description={$t('admin.machine_learning_min_recognized_faces_description')}
|
||||
bind:value={configToEdit.machineLearning.facialRecognition.minFaces}
|
||||
step="1"
|
||||
min={1}
|
||||
disabled={disabled ||
|
||||
!configToEdit.machineLearning.enabled ||
|
||||
!configToEdit.machineLearning.facialRecognition.enabled}
|
||||
isEdited={configToEdit.machineLearning.facialRecognition.minFaces !==
|
||||
config.machineLearning.facialRecognition.minFaces}
|
||||
/>
|
||||
</SystemSettingsCard>
|
||||
|
||||
<SystemSettingsCard
|
||||
title={$t('admin.machine_learning_ocr')}
|
||||
subtitle={$t('admin.machine_learning_ocr_description')}
|
||||
</Field>
|
||||
<Field
|
||||
label={$t('admin.machine_learning_min_recognized_faces')}
|
||||
description={$t('admin.machine_learning_min_recognized_faces_description')}
|
||||
disabled={disabled ||
|
||||
!configToEdit.machineLearning.enabled ||
|
||||
!configToEdit.machineLearning.facialRecognition.enabled}
|
||||
>
|
||||
<SettingSwitch
|
||||
title={$t('admin.machine_learning_ocr_enabled')}
|
||||
subtitle={$t('admin.machine_learning_ocr_enabled_description')}
|
||||
bind:checked={configToEdit.machineLearning.ocr.enabled}
|
||||
disabled={disabled || !configToEdit.machineLearning.enabled}
|
||||
/>
|
||||
<NumberInput bind:value={configToEdit.machineLearning.facialRecognition.minFaces} step="1" min={1} />
|
||||
</Field>
|
||||
</SystemSettingsCard>
|
||||
|
||||
<SettingSelect
|
||||
label={$t('admin.machine_learning_ocr_model')}
|
||||
desc={$t('admin.machine_learning_ocr_model_description')}
|
||||
name="ocr-model"
|
||||
bind:value={configToEdit.machineLearning.ocr.modelName}
|
||||
options={[
|
||||
{ text: 'PP-OCRv5_server (Chinese, Japanese and English)', value: 'PP-OCRv5_server' },
|
||||
{ text: 'PP-OCRv5_mobile (Chinese, Japanese and English)', value: 'PP-OCRv5_mobile' },
|
||||
{ text: 'PP-OCRv5_mobile (English-only)', value: 'EN__PP-OCRv5_mobile' },
|
||||
{ text: 'PP-OCRv5_mobile (Greek and English)', value: 'EL__PP-OCRv5_mobile' },
|
||||
{ text: 'PP-OCRv5_mobile (Korean and English)', value: 'KOREAN__PP-OCRv5_mobile' },
|
||||
{ text: 'PP-OCRv5_mobile (Latin script languages)', value: 'LATIN__PP-OCRv5_mobile' },
|
||||
{ text: 'PP-OCRv5_mobile (Russian, Belarusian, Ukrainian and English)', value: 'ESLAV__PP-OCRv5_mobile' },
|
||||
{ text: 'PP-OCRv5_mobile (Thai and English)', value: 'TH__PP-OCRv5_mobile' },
|
||||
]}
|
||||
disabled={disabled || !configToEdit.machineLearning.enabled || !configToEdit.machineLearning.ocr.enabled}
|
||||
isEdited={configToEdit.machineLearning.ocr.modelName !== config.machineLearning.ocr.modelName}
|
||||
/>
|
||||
<SystemSettingsCard
|
||||
title={$t('admin.machine_learning_ocr')}
|
||||
subtitle={$t('admin.machine_learning_ocr_description')}
|
||||
>
|
||||
<Field
|
||||
label={$t('admin.machine_learning_ocr_enabled')}
|
||||
description={$t('admin.machine_learning_ocr_enabled_description')}
|
||||
disabled={disabled || !configToEdit.machineLearning.enabled}
|
||||
>
|
||||
<Switch bind:checked={configToEdit.machineLearning.ocr.enabled} />
|
||||
</Field>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.NUMBER}
|
||||
label={$t('admin.machine_learning_ocr_min_detection_score')}
|
||||
description={$t('admin.machine_learning_ocr_min_detection_score_description')}
|
||||
bind:value={configToEdit.machineLearning.ocr.minDetectionScore}
|
||||
step="0.1"
|
||||
min={0.1}
|
||||
max={1}
|
||||
disabled={disabled || !configToEdit.machineLearning.enabled || !configToEdit.machineLearning.ocr.enabled}
|
||||
isEdited={configToEdit.machineLearning.ocr.minDetectionScore !== config.machineLearning.ocr.minDetectionScore}
|
||||
/>
|
||||
<SettingSelect
|
||||
label={$t('admin.machine_learning_ocr_model')}
|
||||
desc={$t('admin.machine_learning_ocr_model_description')}
|
||||
name="ocr-model"
|
||||
bind:value={configToEdit.machineLearning.ocr.modelName}
|
||||
options={[
|
||||
{ text: 'PP-OCRv5_server (Chinese, Japanese and English)', value: 'PP-OCRv5_server' },
|
||||
{ text: 'PP-OCRv5_mobile (Chinese, Japanese and English)', value: 'PP-OCRv5_mobile' },
|
||||
{ text: 'PP-OCRv5_mobile (English-only)', value: 'EN__PP-OCRv5_mobile' },
|
||||
{ text: 'PP-OCRv5_mobile (Greek and English)', value: 'EL__PP-OCRv5_mobile' },
|
||||
{ text: 'PP-OCRv5_mobile (Korean and English)', value: 'KOREAN__PP-OCRv5_mobile' },
|
||||
{ text: 'PP-OCRv5_mobile (Latin script languages)', value: 'LATIN__PP-OCRv5_mobile' },
|
||||
{ text: 'PP-OCRv5_mobile (Russian, Belarusian, Ukrainian and English)', value: 'ESLAV__PP-OCRv5_mobile' },
|
||||
{ text: 'PP-OCRv5_mobile (Thai and English)', value: 'TH__PP-OCRv5_mobile' },
|
||||
]}
|
||||
disabled={disabled || !configToEdit.machineLearning.enabled || !configToEdit.machineLearning.ocr.enabled}
|
||||
isEdited={configToEdit.machineLearning.ocr.modelName !== config.machineLearning.ocr.modelName}
|
||||
/>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.NUMBER}
|
||||
label={$t('admin.machine_learning_ocr_min_recognition_score')}
|
||||
description={$t('admin.machine_learning_ocr_min_score_recognition_description')}
|
||||
bind:value={configToEdit.machineLearning.ocr.minRecognitionScore}
|
||||
step="0.1"
|
||||
min={0.1}
|
||||
max={1}
|
||||
disabled={disabled || !configToEdit.machineLearning.enabled || !configToEdit.machineLearning.ocr.enabled}
|
||||
isEdited={configToEdit.machineLearning.ocr.minRecognitionScore !==
|
||||
config.machineLearning.ocr.minRecognitionScore}
|
||||
/>
|
||||
<Field
|
||||
label={$t('admin.machine_learning_ocr_min_detection_score')}
|
||||
description={$t('admin.machine_learning_ocr_min_detection_score_description')}
|
||||
disabled={disabled || !configToEdit.machineLearning.enabled || !configToEdit.machineLearning.ocr.enabled}
|
||||
>
|
||||
<NumberInput bind:value={configToEdit.machineLearning.ocr.minDetectionScore} step="0.1" min={0.1} max={1} />
|
||||
</Field>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.NUMBER}
|
||||
label={$t('admin.machine_learning_ocr_max_resolution')}
|
||||
description={$t('admin.machine_learning_ocr_max_resolution_description')}
|
||||
bind:value={configToEdit.machineLearning.ocr.maxResolution}
|
||||
min={1}
|
||||
disabled={disabled || !configToEdit.machineLearning.enabled || !configToEdit.machineLearning.ocr.enabled}
|
||||
isEdited={configToEdit.machineLearning.ocr.maxResolution !== config.machineLearning.ocr.maxResolution}
|
||||
/>
|
||||
</SystemSettingsCard>
|
||||
</div>
|
||||
<Field
|
||||
label={$t('admin.machine_learning_ocr_min_recognition_score')}
|
||||
description={$t('admin.machine_learning_ocr_min_score_recognition_description')}
|
||||
disabled={disabled || !configToEdit.machineLearning.enabled || !configToEdit.machineLearning.ocr.enabled}
|
||||
>
|
||||
<NumberInput bind:value={configToEdit.machineLearning.ocr.minRecognitionScore} step="0.1" min={0.1} max={1} />
|
||||
</Field>
|
||||
|
||||
<Field
|
||||
label={$t('admin.machine_learning_ocr_max_resolution')}
|
||||
description={$t('admin.machine_learning_ocr_max_resolution_description')}
|
||||
disabled={disabled || !configToEdit.machineLearning.enabled || !configToEdit.machineLearning.ocr.enabled}
|
||||
>
|
||||
<NumberInput bind:value={configToEdit.machineLearning.ocr.maxResolution} min={1} />
|
||||
</Field>
|
||||
</SystemSettingsCard>
|
||||
{/snippet}
|
||||
</SystemSettingsModal>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
|
||||
import { SettingInputFieldType } from '$lib/constants';
|
||||
import SystemSettingsModal from '$lib/modals/SystemSettingsModal.svelte';
|
||||
import { Field, Switch } from '@immich/ui';
|
||||
import { t } from 'svelte-i18n';
|
||||
</script>
|
||||
|
||||
|
|
@ -18,12 +19,13 @@
|
|||
{disabled}
|
||||
isEdited={!(configToEdit.nightlyTasks.startTime === config.nightlyTasks.startTime)}
|
||||
/>
|
||||
<SettingSwitch
|
||||
title={$t('admin.nightly_tasks_database_cleanup_setting')}
|
||||
subtitle={$t('admin.nightly_tasks_database_cleanup_setting_description')}
|
||||
bind:checked={configToEdit.nightlyTasks.databaseCleanup}
|
||||
<Field
|
||||
label={$t('admin.nightly_tasks_database_cleanup_setting')}
|
||||
description={$t('admin.nightly_tasks_database_cleanup_setting_description')}
|
||||
{disabled}
|
||||
/>
|
||||
>
|
||||
<Switch bind:checked={configToEdit.nightlyTasks.databaseCleanup} />
|
||||
</Field>
|
||||
<SettingSwitch
|
||||
title={$t('admin.nightly_tasks_missing_thumbnails_setting')}
|
||||
subtitle={$t('admin.nightly_tasks_missing_thumbnails_setting_description')}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,173 @@
|
|||
<script lang="ts">
|
||||
import SettingSelect from '$lib/components/shared-components/settings/setting-select.svelte';
|
||||
import FormatMessage from '$lib/elements/FormatMessage.svelte';
|
||||
import SystemSettingsModal from '$lib/modals/SystemSettingsModal.svelte';
|
||||
import { handleUnlinkAllOAuthAccounts, onBeforeSave } from '$lib/services/system-config.service';
|
||||
import { OAuthTokenEndpointAuthMethod, type SystemConfigDto } from '@immich/sdk';
|
||||
import { Alert, Button, Field, Input, NumberInput, Switch, Text } from '@immich/ui';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
const handleToggleOverride = (configToEdit: SystemConfigDto) => {
|
||||
// click runs before bind
|
||||
const previouslyEnabled = configToEdit.oauth.mobileOverrideEnabled;
|
||||
if (!previouslyEnabled && !configToEdit.oauth.mobileRedirectUri) {
|
||||
configToEdit.oauth.mobileRedirectUri = globalThis.location.origin + '/api/oauth/mobile-redirect';
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<SystemSettingsModal keys={['oauth']} size="large" {onBeforeSave}>
|
||||
{#snippet child({ disabled, config, configToEdit })}
|
||||
<Alert color="warning">
|
||||
<div>test</div>
|
||||
<div class="flex flex-col gap-2 w-full">
|
||||
<Text size="small">{$t('admin.unlink_all_oauth_accounts_description')}</Text>
|
||||
<div class="flex justify-end">
|
||||
<Button size="small" variant="outline" color="warning" onclick={handleUnlinkAllOAuthAccounts}
|
||||
>{$t('admin.unlink_all_oauth_accounts')}</Button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</Alert>
|
||||
|
||||
<Text>
|
||||
<FormatMessage key="admin.oauth_settings_more_details">
|
||||
{#snippet children({ message })}
|
||||
<a href="https://docs.immich.app/administration/oauth" class="underline" target="_blank" rel="noreferrer">
|
||||
{message}
|
||||
</a>
|
||||
{/snippet}
|
||||
</FormatMessage>
|
||||
</Text>
|
||||
|
||||
<Field label={$t('admin.oauth_enable_description')} {disabled}>
|
||||
<Switch bind:checked={configToEdit.oauth.enabled} />
|
||||
</Field>
|
||||
|
||||
<Field label="ISSUER_URL" required disabled={disabled || !configToEdit.oauth.enabled}>
|
||||
<Input bind:value={configToEdit.oauth.issuerUrl} />
|
||||
</Field>
|
||||
|
||||
<Field label="CLIENT_ID" required disabled={disabled || !configToEdit.oauth.enabled}>
|
||||
<Input bind:value={configToEdit.oauth.clientId} />
|
||||
</Field>
|
||||
|
||||
<Field label="CLIENT_SECRET" required disabled={disabled || !configToEdit.oauth.enabled}>
|
||||
<Input bind:value={configToEdit.oauth.clientSecret} />
|
||||
</Field>
|
||||
|
||||
{#if configToEdit.oauth.clientSecret}
|
||||
<SettingSelect
|
||||
label="TOKEN_ENDPOINT_AUTH_METHOD"
|
||||
bind:value={configToEdit.oauth.tokenEndpointAuthMethod}
|
||||
disabled={disabled || !configToEdit.oauth.enabled || !configToEdit.oauth.clientSecret}
|
||||
isEdited={!(configToEdit.oauth.tokenEndpointAuthMethod === config.oauth.tokenEndpointAuthMethod)}
|
||||
options={[
|
||||
{ value: OAuthTokenEndpointAuthMethod.ClientSecretPost, text: 'client_secret_post' },
|
||||
{ value: OAuthTokenEndpointAuthMethod.ClientSecretBasic, text: 'client_secret_basic' },
|
||||
]}
|
||||
name="tokenEndpointAuthMethod"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<Field label="SCOPE" required disabled={disabled || !configToEdit.oauth.enabled}>
|
||||
<Input bind:value={configToEdit.oauth.scope} />
|
||||
</Field>
|
||||
|
||||
<Field label="ID_TOKEN_SIGNED_RESPONSE_ALG" required disabled={disabled || !configToEdit.oauth.enabled}>
|
||||
<Input bind:value={configToEdit.oauth.signingAlgorithm} />
|
||||
</Field>
|
||||
|
||||
<Field label="USERINFO_SIGNED_RESPONSE_ALG" required disabled={disabled || !configToEdit.oauth.enabled}>
|
||||
<Input bind:value={configToEdit.oauth.profileSigningAlgorithm} />
|
||||
</Field>
|
||||
|
||||
<Field
|
||||
label={$t('admin.oauth_timeout')}
|
||||
description={$t('admin.oauth_timeout_description')}
|
||||
required
|
||||
disabled={disabled || !configToEdit.oauth.enabled}
|
||||
>
|
||||
<NumberInput bind:value={configToEdit.oauth.timeout} />
|
||||
</Field>
|
||||
|
||||
<Field
|
||||
label={$t('admin.oauth_storage_label_claim')}
|
||||
description={$t('admin.oauth_storage_label_claim_description')}
|
||||
required
|
||||
disabled={disabled || !configToEdit.oauth.enabled}
|
||||
>
|
||||
<Input bind:value={configToEdit.oauth.storageLabelClaim} />
|
||||
</Field>
|
||||
|
||||
<Field
|
||||
label={$t('admin.oauth_role_claim')}
|
||||
description={$t('admin.oauth_role_claim_description')}
|
||||
required
|
||||
disabled={disabled || !configToEdit.oauth.enabled}
|
||||
>
|
||||
<Input bind:value={configToEdit.oauth.roleClaim} />
|
||||
</Field>
|
||||
|
||||
<Field
|
||||
label={$t('admin.oauth_storage_quota_claim')}
|
||||
description={$t('admin.oauth_storage_quota_claim_description')}
|
||||
required
|
||||
disabled={disabled || !configToEdit.oauth.enabled}
|
||||
>
|
||||
<Input bind:value={configToEdit.oauth.storageQuotaClaim} />
|
||||
</Field>
|
||||
|
||||
<Field
|
||||
label={$t('admin.oauth_storage_quota_default')}
|
||||
description={$t('admin.oauth_storage_quota_default_description')}
|
||||
disabled={disabled || !configToEdit.oauth.enabled}
|
||||
>
|
||||
<NumberInput
|
||||
bind:value={
|
||||
() => configToEdit.oauth.defaultStorageQuota ?? undefined,
|
||||
(value) => (configToEdit.oauth.defaultStorageQuota = value ?? null)
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field label={$t('admin.oauth_button_text')} disabled={disabled || !configToEdit.oauth.enabled}>
|
||||
<Input bind:value={configToEdit.oauth.buttonText} />
|
||||
</Field>
|
||||
|
||||
<Field
|
||||
label={$t('admin.oauth_auto_register')}
|
||||
description={$t('admin.oauth_auto_register_description')}
|
||||
disabled={disabled || !configToEdit.oauth.enabled}
|
||||
>
|
||||
<Switch bind:checked={configToEdit.oauth.autoRegister} />
|
||||
</Field>
|
||||
|
||||
<Field
|
||||
label={$t('admin.oauth_auto_launch')}
|
||||
description={$t('admin.oauth_auto_launch_description')}
|
||||
disabled={disabled || !configToEdit.oauth.enabled}
|
||||
>
|
||||
<Switch bind:checked={configToEdit.oauth.autoLaunch} />
|
||||
</Field>
|
||||
|
||||
<Field
|
||||
label={$t('admin.oauth_mobile_redirect_uri_override')}
|
||||
description={$t('admin.oauth_mobile_redirect_uri_override_description', {
|
||||
values: { callback: 'app.immich:///oauth-callback' },
|
||||
})}
|
||||
disabled={disabled || !configToEdit.oauth.enabled}
|
||||
>
|
||||
<Switch
|
||||
bind:checked={configToEdit.oauth.mobileOverrideEnabled}
|
||||
onCheckedChange={() => handleToggleOverride(configToEdit)}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
{#if configToEdit.oauth.mobileOverrideEnabled}
|
||||
<Field label={$t('admin.oauth_mobile_redirect_uri')} required disabled={disabled || !configToEdit.oauth.enabled}>
|
||||
<Input bind:value={configToEdit.oauth.mobileRedirectUri} />
|
||||
</Field>
|
||||
{/if}
|
||||
{/snippet}
|
||||
</SystemSettingsModal>
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<script lang="ts">
|
||||
import SystemSettingsModal from '$lib/modals/SystemSettingsModal.svelte';
|
||||
import { onBeforeSave } from '$lib/services/system-config.service';
|
||||
import { Field, Switch } from '@immich/ui';
|
||||
import { t } from 'svelte-i18n';
|
||||
</script>
|
||||
|
||||
<SystemSettingsModal keys={['passwordLogin']} size="small" {onBeforeSave}>
|
||||
{#snippet child({ disabled, configToEdit })}
|
||||
<Field label={$t('admin.password_enable_description')} {disabled}>
|
||||
<Switch bind:checked={configToEdit.passwordLogin.enabled} />
|
||||
</Field>
|
||||
{/snippet}
|
||||
</SystemSettingsModal>
|
||||
|
|
@ -3,18 +3,18 @@
|
|||
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
|
||||
import { SettingInputFieldType } from '$lib/constants';
|
||||
import SystemSettingsModal from '$lib/modals/SystemSettingsModal.svelte';
|
||||
import { Field, Input } from '@immich/ui';
|
||||
import { t } from 'svelte-i18n';
|
||||
</script>
|
||||
|
||||
<SystemSettingsModal keys={['server']}>
|
||||
{#snippet child({ disabled, config, configToEdit })}
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
<Field
|
||||
label={$t('admin.server_external_domain_settings')}
|
||||
description={$t('admin.server_external_domain_settings_description')}
|
||||
bind:value={configToEdit.server.externalDomain}
|
||||
isEdited={configToEdit.server.externalDomain !== config.server.externalDomain}
|
||||
/>
|
||||
>
|
||||
<Input bind:value={configToEdit.server.externalDomain} />
|
||||
</Field>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
|
|
|
|||
Loading…
Reference in New Issue