refactor: config queries (#32498)
(cherry picked from commit b45ec0f55abb9dc39fbbb39dcf5296d45a726ba0)
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { BrowserContext } from '@playwright/test';
|
||||
import { playwrightHost } from 'src/../playwright.config.js';
|
||||
import { adminConfig, publicConfig, userConfig } from 'src/ui/mock-network/config-fixtures.js';
|
||||
|
||||
export const setupBaseMockApiRoutes = async (context: BrowserContext, adminUserId: string) => {
|
||||
await context.addCookies([
|
||||
@@ -113,6 +114,27 @@ export const setupBaseMockApiRoutes = async (context: BrowserContext, adminUserI
|
||||
},
|
||||
});
|
||||
});
|
||||
await context.route('**/api/public/config', async (route) => {
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
json: publicConfig,
|
||||
});
|
||||
});
|
||||
await context.route('**/api/config', async (route) => {
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
json: userConfig,
|
||||
});
|
||||
});
|
||||
await context.route('**/api/admin/config', async (route) => {
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
json: adminConfig,
|
||||
});
|
||||
});
|
||||
await context.route('**/api/server/features', async (route) => {
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
export const publicConfig = {
|
||||
ffmpeg: { realtime: { enabled: false } },
|
||||
map: {
|
||||
enabled: true,
|
||||
lightStyle: 'https://tiles.immich.cloud/v1/style/light.json',
|
||||
darkStyle: 'https://tiles.immich.cloud/v1/style/dark.json',
|
||||
},
|
||||
oauth: { autoLaunch: false, buttonText: 'Login with OAuth', enabled: false },
|
||||
passkey: { enabled: true },
|
||||
passwordLogin: { enabled: true },
|
||||
server: { loginPageMessage: '' },
|
||||
};
|
||||
|
||||
export const userConfig = {
|
||||
ffmpeg: { realtime: { enabled: false, videoCodecs: ['h264', 'hevc'], resolutions: [480, 720, 1080] } },
|
||||
machineLearning: {
|
||||
enabled: false,
|
||||
clip: { enabled: true },
|
||||
duplicateDetection: { enabled: true },
|
||||
facialRecognition: { enabled: true, minFaces: 3 },
|
||||
ocr: { enabled: true },
|
||||
},
|
||||
map: {
|
||||
enabled: true,
|
||||
lightStyle: 'https://tiles.immich.cloud/v1/style/light.json',
|
||||
darkStyle: 'https://tiles.immich.cloud/v1/style/dark.json',
|
||||
},
|
||||
reverseGeocoding: { enabled: true },
|
||||
oauth: { autoLaunch: false, buttonText: 'Login with OAuth', enabled: false, accountManagementUrl: '' },
|
||||
passkey: { enabled: true },
|
||||
passwordLogin: { enabled: true },
|
||||
image: { thumbnail: { size: 250 }, preview: { size: 1440 }, fullsize: { enabled: false } },
|
||||
trash: { enabled: true, days: 30 },
|
||||
notifications: { smtp: { enabled: false } },
|
||||
server: { externalDomain: '', loginPageMessage: '', publicUsers: true },
|
||||
user: { deleteDelay: 7 },
|
||||
};
|
||||
|
||||
export const adminConfig = {
|
||||
backup: { database: { enabled: true, cronExpression: '0 02 * * *', keepLastAmount: 14 } },
|
||||
ffmpeg: {
|
||||
crf: 23,
|
||||
threads: 0,
|
||||
preset: 'ultrafast',
|
||||
targetVideoCodec: 'h264',
|
||||
acceptedVideoCodecs: ['h264'],
|
||||
targetAudioCodec: 'aac',
|
||||
acceptedAudioCodecs: ['aac', 'mp3', 'opus'],
|
||||
acceptedContainers: ['mov', 'ogg', 'webm'],
|
||||
targetResolution: '720',
|
||||
maxBitrate: '0',
|
||||
bframes: -1,
|
||||
refs: 0,
|
||||
gopSize: 0,
|
||||
temporalAQ: false,
|
||||
cqMode: 'auto',
|
||||
twoPass: false,
|
||||
preferredHwDevice: 'auto',
|
||||
transcode: 'required',
|
||||
tonemap: 'hable',
|
||||
accel: 'disabled',
|
||||
accelDecode: true,
|
||||
realtime: { enabled: false, videoCodecs: ['h264', 'hevc'], resolutions: [480, 720, 1080] },
|
||||
},
|
||||
integrityChecks: {
|
||||
missingFiles: { enabled: true, cronExpression: '0 03 * * *' },
|
||||
untrackedFiles: { enabled: true, cronExpression: '0 03 * * *' },
|
||||
checksumFiles: { enabled: true, cronExpression: '0 03 * * *', timeLimit: 3_600_000, percentageLimit: 1 },
|
||||
},
|
||||
job: {
|
||||
thumbnailGeneration: { concurrency: 3 },
|
||||
metadataExtraction: { concurrency: 5 },
|
||||
videoConversion: { concurrency: 1 },
|
||||
faceDetection: { concurrency: 2 },
|
||||
smartSearch: { concurrency: 2 },
|
||||
backgroundTask: { concurrency: 5 },
|
||||
migration: { concurrency: 5 },
|
||||
search: { concurrency: 5 },
|
||||
sidecar: { concurrency: 5 },
|
||||
library: { concurrency: 5 },
|
||||
notifications: { concurrency: 5 },
|
||||
ocr: { concurrency: 1 },
|
||||
workflow: { concurrency: 5 },
|
||||
editor: { concurrency: 2 },
|
||||
integrityCheck: { concurrency: 1 },
|
||||
},
|
||||
logging: { enabled: true, level: 'log' },
|
||||
machineLearning: {
|
||||
enabled: false,
|
||||
urls: ['http://immich-machine-learning:3003'],
|
||||
availabilityChecks: { enabled: true, timeout: 2000, interval: 30_000 },
|
||||
clip: { enabled: true, modelName: 'ViT-B-32__openai' },
|
||||
duplicateDetection: { enabled: true, maxDistance: 0.01 },
|
||||
facialRecognition: { enabled: true, modelName: 'buffalo_l', minScore: 0.7, maxDistance: 0.5, minFaces: 3 },
|
||||
ocr: {
|
||||
enabled: true,
|
||||
modelName: 'PP-OCRv5_mobile',
|
||||
minDetectionScore: 0.5,
|
||||
minRecognitionScore: 0.8,
|
||||
maxResolution: 736,
|
||||
},
|
||||
},
|
||||
map: {
|
||||
enabled: true,
|
||||
lightStyle: 'https://tiles.immich.cloud/v1/style/light.json',
|
||||
darkStyle: 'https://tiles.immich.cloud/v1/style/dark.json',
|
||||
},
|
||||
reverseGeocoding: { enabled: true },
|
||||
metadata: { faces: { import: false } },
|
||||
oauth: {
|
||||
autoLaunch: false,
|
||||
autoRegister: true,
|
||||
buttonText: 'Login with OAuth',
|
||||
clientId: '',
|
||||
clientSecret: '',
|
||||
defaultStorageQuota: null,
|
||||
enabled: false,
|
||||
issuerUrl: '',
|
||||
accountManagementUrl: '',
|
||||
endSessionEndpoint: '',
|
||||
mobileOverrideEnabled: false,
|
||||
mobileRedirectUri: '',
|
||||
prompt: '',
|
||||
scope: 'openid email profile',
|
||||
signingAlgorithm: 'RS256',
|
||||
profileSigningAlgorithm: 'none',
|
||||
storageLabelClaim: 'preferred_username',
|
||||
storageQuotaClaim: 'immich_quota',
|
||||
roleClaim: 'immich_role',
|
||||
tokenEndpointAuthMethod: 'client_secret_post',
|
||||
timeout: 30_000,
|
||||
allowInsecureRequests: false,
|
||||
},
|
||||
passkey: { enabled: true, domain: null, additionalDomains: [] },
|
||||
passwordLogin: { enabled: true },
|
||||
storageTemplate: {
|
||||
enabled: false,
|
||||
hashVerificationEnabled: true,
|
||||
template: '{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}',
|
||||
},
|
||||
image: {
|
||||
thumbnail: { format: 'webp', size: 250, quality: 80, progressive: false },
|
||||
preview: { format: 'jpeg', size: 1440, quality: 80, progressive: false },
|
||||
colorspace: 'p3',
|
||||
extractEmbedded: false,
|
||||
fullsize: { enabled: false, format: 'jpeg', quality: 80, progressive: false },
|
||||
},
|
||||
newVersionCheck: { enabled: true, channel: 'stable' },
|
||||
nightlyTasks: {
|
||||
startTime: '00:00',
|
||||
databaseCleanup: true,
|
||||
generateMemories: true,
|
||||
syncQuotaUsage: true,
|
||||
missingThumbnails: true,
|
||||
clusterNewFaces: true,
|
||||
},
|
||||
trash: { enabled: true, days: 30 },
|
||||
theme: { customCss: '' },
|
||||
library: { scan: { enabled: true, cronExpression: '0 0 * * *' }, watch: { enabled: false } },
|
||||
server: { externalDomain: '', loginPageMessage: '', publicUsers: true },
|
||||
notifications: {
|
||||
smtp: {
|
||||
enabled: false,
|
||||
from: '',
|
||||
replyTo: '',
|
||||
transport: { ignoreCert: false, host: '', port: 587, secure: false, username: '', password: '' },
|
||||
},
|
||||
},
|
||||
templates: { email: { welcomeTemplate: '', albumInviteTemplate: '', albumUpdateTemplate: '' } },
|
||||
user: { deleteDelay: 7 },
|
||||
};
|
||||
@@ -31,7 +31,8 @@ import type { MessageFormatter } from 'svelte-i18n';
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/state';
|
||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import { readPreloadedQuery } from '$lib/queries/client';
|
||||
import { getUserConfigQuery } from '$lib/queries/config-user.query';
|
||||
import { Route } from '$lib/route';
|
||||
import { copyToClipboard } from '$lib/utils';
|
||||
|
||||
@@ -92,7 +93,7 @@ export const getPagesProvider = ($t: MessageFormatter) => {
|
||||
title: $t('map'),
|
||||
icon: mdiMapOutline,
|
||||
onAction: () => goto(Route.map()),
|
||||
$if: () => authManager.authenticated && featureFlagsManager.value.map,
|
||||
$if: () => authManager.authenticated && readPreloadedQuery(getUserConfigQuery()).map.enabled,
|
||||
},
|
||||
{
|
||||
title: $t('people'),
|
||||
@@ -173,7 +174,7 @@ export const getPagesProvider = ($t: MessageFormatter) => {
|
||||
title: $t('trash'),
|
||||
icon: mdiTrashCanOutline,
|
||||
onAction: () => goto(Route.trash()),
|
||||
$if: () => authManager.authenticated && featureFlagsManager.value.trash,
|
||||
$if: () => authManager.authenticated && readPreloadedQuery(getUserConfigQuery()).trash.enabled,
|
||||
},
|
||||
{
|
||||
title: $t('admin.user_settings'),
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import { createPreloadedQuery, executeMutation } from '$lib/queries/client';
|
||||
import { getConfigQuery, updateConfigMutation } from '$lib/queries/system-config.query';
|
||||
import { getAdminConfigQuery, updateAdminConfigMutation } from '$lib/queries/config-admin.query';
|
||||
import StorageTemplateVariablesModal from '$lib/modals/StorageTemplateVariablesModal.svelte';
|
||||
import { Route } from '$lib/route';
|
||||
import { getStorageTemplateOptions, type SystemConfigTemplateStorageOptionDto } from '@immich/sdk';
|
||||
@@ -40,7 +40,7 @@
|
||||
type StorageTemplateOption = (typeof StorageTemplateOption)[keyof typeof StorageTemplateOption];
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const query = createPreloadedQuery(getAdminConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
let configToEdit = $state(cloneDeep(config));
|
||||
|
||||
@@ -155,7 +155,7 @@
|
||||
|
||||
onDestroy(() => {
|
||||
if (saveOnClose) {
|
||||
void executeMutation(updateConfigMutation, { storageTemplate: configToEdit.storageTemplate });
|
||||
void executeMutation(updateAdminConfigMutation, { storageTemplate: configToEdit.storageTemplate });
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
import Timeline from '$lib/components/timeline/Timeline.svelte';
|
||||
import { assetMultiSelectManager } from '$lib/managers/asset-multi-select-manager.svelte';
|
||||
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
|
||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
||||
import { handleDownloadAlbum } from '$lib/services/album.service';
|
||||
import { getGlobalActions } from '$lib/services/app.service';
|
||||
@@ -23,6 +22,11 @@
|
||||
import ControlAppBar from '../shared-components/ControlAppBar.svelte';
|
||||
import ThemeButton from '../shared-components/ThemeButton.svelte';
|
||||
import AlbumSummary from './AlbumSummary.svelte';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getPublicConfigQuery } from '$lib/queries/config-public.query';
|
||||
|
||||
const query = createPreloadedQuery(getPublicConfigQuery);
|
||||
const publicConfig = $derived(query.data);
|
||||
|
||||
interface Props {
|
||||
sharedLink: SharedLinkResponseDto;
|
||||
@@ -144,7 +148,7 @@
|
||||
icon={mdiDownload}
|
||||
/>
|
||||
{/if}
|
||||
{#if sharedLink.showMetadata && featureFlagsManager.value.map}
|
||||
{#if sharedLink.showMetadata && publicConfig.map.enabled}
|
||||
<AlbumMap {album} />
|
||||
{/if}
|
||||
<ThemeButton />
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import type { UserConfigDto } from '@immich/sdk';
|
||||
import '@testing-library/jest-dom';
|
||||
import { getResizeObserverMock } from '$lib/__mocks__/resize-observer.mock';
|
||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import { queryClient } from '$lib/queries/client';
|
||||
import { getUserConfigQuery } from '$lib/queries/config-user.query';
|
||||
import { renderWithTooltips } from '$tests/helpers';
|
||||
import { assetFactory } from '@test-data/factories/asset-factory';
|
||||
import { preferencesFactory } from '@test-data/factories/preferences-factory';
|
||||
@@ -60,6 +63,16 @@ describe('AssetViewerNavBar component', () => {
|
||||
const user = userAdminFactory.build({ id: ownerId });
|
||||
const asset = assetFactory.build({ ownerId, isTrashed: false });
|
||||
authManager.setUser(user);
|
||||
queryClient.setQueryData(getUserConfigQuery().queryKey, {
|
||||
machineLearning: {
|
||||
enabled: true,
|
||||
clip: { enabled: true },
|
||||
duplicateDetection: { enabled: true },
|
||||
facialRecognition: { enabled: true },
|
||||
ocr: { enabled: true },
|
||||
},
|
||||
trash: { enabled: true, days: 30 },
|
||||
} as UserConfigDto);
|
||||
|
||||
const preferences = preferencesFactory.build({ cast: { gCastEnabled: false } });
|
||||
authManager.setPreferences(preferences);
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
import { timeToLoadTheMap } from '$lib/constants';
|
||||
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
|
||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import { Route } from '$lib/route';
|
||||
import { locale } from '$lib/stores/preferences.store';
|
||||
import { getAssetMediaUrl } from '$lib/utils';
|
||||
@@ -34,6 +33,11 @@
|
||||
import AlbumListItemDetails from './AlbumListItemDetails.svelte';
|
||||
import DetailPanelPeople from '$lib/components/asset-viewer/DetailPanelPeople.svelte';
|
||||
import { faceManager } from '$lib/stores/face.svelte';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getPublicConfigQuery } from '$lib/queries/config-public.query';
|
||||
|
||||
const query = createPreloadedQuery(getPublicConfigQuery);
|
||||
const publicConfig = $derived(query.data);
|
||||
|
||||
interface Props {
|
||||
asset: AssetResponseDto;
|
||||
@@ -278,7 +282,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{#if latlng && featureFlagsManager.value.map}
|
||||
{#if latlng && publicConfig.map.enabled}
|
||||
<div class="h-90">
|
||||
{#await import('$lib/components/shared-components/map/Map.svelte')}
|
||||
{#await delay(timeToLoadTheMap) then}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
import { assetViewerFadeDuration } from '$lib/constants';
|
||||
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
|
||||
import { castManager } from '$lib/managers/cast-manager.svelte';
|
||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import { mediaCapabilitiesManager } from '$lib/managers/media-capabilities-manager.svelte';
|
||||
import { autoPlayVideo, lang, loopVideo as loopVideoPreference } from '$lib/stores/preferences.store';
|
||||
@@ -45,6 +44,11 @@
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
import './immich-time-range';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getPublicConfigQuery } from '$lib/queries/config-public.query';
|
||||
|
||||
const query = createPreloadedQuery(getPublicConfigQuery);
|
||||
const publicConfig = $derived(query.data);
|
||||
|
||||
interface Props {
|
||||
asset: AssetResponseDto;
|
||||
@@ -78,7 +82,7 @@
|
||||
let isLoading = $state(true);
|
||||
let hasLoadedMetadata = $state(false);
|
||||
let assetFileUrl = $derived.by(() => {
|
||||
if (featureFlagsManager.value.realtimeTranscoding) {
|
||||
if (publicConfig.ffmpeg.realtime.enabled) {
|
||||
return getAssetHlsUrl(assetId);
|
||||
}
|
||||
|
||||
@@ -376,7 +380,7 @@
|
||||
style:aspect-ratio={aspectRatio}
|
||||
defaultduration={asset.duration! / 1000}
|
||||
>
|
||||
{#if featureFlagsManager.value.realtimeTranscoding}
|
||||
{#if publicConfig.ffmpeg.realtime.enabled}
|
||||
<hls-video
|
||||
bind:this={videoPlayer}
|
||||
slot="media"
|
||||
@@ -440,7 +444,7 @@
|
||||
<span slot="title">{$t('media_chrome.playback_rate')}</span>
|
||||
</media-playback-rate-menu>
|
||||
</media-settings-menu-item>
|
||||
{#if featureFlagsManager.value.realtimeTranscoding}
|
||||
{#if publicConfig.ffmpeg.realtime.enabled}
|
||||
<media-settings-menu-item class="mx-1 rounded-lg p-1 ps-2">
|
||||
{$t('video_quality')}
|
||||
<Icon slot="suffix" icon={mdiChevronRight} class="m-2" />
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
import { afterNavigate } from '$app/navigation';
|
||||
import OnEvents from '$lib/components/OnEvents.svelte';
|
||||
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
|
||||
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
|
||||
import MapSettingsModal from '$lib/modals/MapSettingsModal.svelte';
|
||||
import { mapSettings } from '$lib/stores/preferences.store';
|
||||
import { getAssetMediaUrl, handlePromiseError } from '$lib/utils';
|
||||
@@ -48,6 +47,11 @@
|
||||
ScaleControl,
|
||||
} from 'svelte-maplibre';
|
||||
import type { SelectionBBox } from './types';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getPublicConfigQuery } from '$lib/queries/config-public.query';
|
||||
|
||||
const query = createPreloadedQuery(getPublicConfigQuery);
|
||||
const publicConfig = $derived(query.data);
|
||||
|
||||
interface Props {
|
||||
mapMarkers?: MapMarkerResponseDto[];
|
||||
@@ -111,9 +115,7 @@
|
||||
let abortController: AbortController;
|
||||
|
||||
const mapTheme = $derived($mapSettings.allowDarkMode ? themeManager.value : Theme.Light);
|
||||
const styleUrl = $derived(
|
||||
mapTheme === Theme.Dark ? serverConfigManager.value.mapDarkStyleUrl : serverConfigManager.value.mapLightStyleUrl,
|
||||
);
|
||||
const styleUrl = $derived(mapTheme === Theme.Dark ? publicConfig.map.darkStyle : publicConfig.map.lightStyle);
|
||||
|
||||
export function addClipMapMarker(lng: number, lat: number) {
|
||||
if (!map) {
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { getSearchTypeTitle } from './search-bar-utils';
|
||||
import SearchButton from './SearchButton.svelte';
|
||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import { searchManager } from '$lib/managers/search-manager.svelte';
|
||||
import { Text } from '@immich/ui';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getUserConfigQuery } from '$lib/queries/config-user.query';
|
||||
|
||||
const query = createPreloadedQuery(getUserConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
let queryType = $derived(searchManager.filter.queryType);
|
||||
|
||||
@@ -17,7 +21,7 @@
|
||||
<fieldset>
|
||||
<Text class="mb-5">{$t('search_type_description')}</Text>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{#if featureFlagsManager.value.smartSearch}
|
||||
{#if config.machineLearning.clip.enabled}
|
||||
<SearchButton checked active={queryType === 'smart'} onclick={() => setType('smart')}>
|
||||
{$t('context')}
|
||||
</SearchButton>
|
||||
@@ -31,7 +35,7 @@
|
||||
<SearchButton checked active={queryType === 'fullPath'} onclick={() => setType('fullPath')}>
|
||||
{getSearchTypeTitle('fullPath')}
|
||||
</SearchButton>
|
||||
{#if featureFlagsManager.value.ocr}
|
||||
{#if config.machineLearning.ocr.enabled}
|
||||
<SearchButton checked active={queryType === 'ocr'} onclick={() => setType('ocr')}>
|
||||
{getSearchTypeTitle('ocr')}
|
||||
</SearchButton>
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigDefaultsQuery, getConfigQuery, updateConfigMutation } from '$lib/queries/system-config.query';
|
||||
import {
|
||||
getAdminConfigDefaultsQuery,
|
||||
getAdminConfigQuery,
|
||||
updateAdminConfigMutation,
|
||||
} from '$lib/queries/config-admin.query';
|
||||
import type { AdminConfigDto } from '@immich/sdk';
|
||||
import { Button, toastManager } from '@immich/ui';
|
||||
import { createMutation } from '@tanstack/svelte-query';
|
||||
@@ -16,19 +20,24 @@
|
||||
|
||||
let { disabled, keys, configToEdit = $bindable(), onBeforeSave }: Props = $props();
|
||||
|
||||
const config = createPreloadedQuery(getConfigQuery);
|
||||
const defaults = createPreloadedQuery(getConfigDefaultsQuery);
|
||||
const save = createMutation(updateConfigMutation);
|
||||
const queries = {
|
||||
config: createPreloadedQuery(getAdminConfigQuery),
|
||||
defaults: createPreloadedQuery(getAdminConfigDefaultsQuery),
|
||||
};
|
||||
|
||||
const showResetToDefault = $derived(!isEqual(pick(config.data, keys), pick(defaults.data, keys)));
|
||||
const config = $derived(queries.config.data);
|
||||
const defaults = $derived(queries.defaults.data);
|
||||
const save = createMutation(updateAdminConfigMutation);
|
||||
|
||||
const showResetToDefault = $derived(!isEqual(pick(config, keys), pick(defaults, keys)));
|
||||
|
||||
const handleReset = () => {
|
||||
configToEdit = cloneDeep(config.data);
|
||||
configToEdit = cloneDeep(config);
|
||||
toastManager.info($t('admin.reset_settings_to_recent_saved'));
|
||||
};
|
||||
|
||||
const handleResetToDefault = () => {
|
||||
configToEdit = { ...configToEdit, ...cloneDeep(pick(defaults.data, keys)) };
|
||||
configToEdit = { ...configToEdit, ...cloneDeep(pick(defaults, keys)) };
|
||||
toastManager.info($t('admin.reset_settings_to_default'));
|
||||
};
|
||||
|
||||
|
||||
@@ -13,10 +13,13 @@
|
||||
|
||||
const { serverVersion, connected } = websocketStore;
|
||||
|
||||
const aboutQuery = createQuery(getAboutInfoQuery);
|
||||
const info = $derived(aboutQuery.data);
|
||||
const historyQuery = createQuery(getVersionHistoryQuery);
|
||||
const versions = $derived(historyQuery.data ?? []);
|
||||
const queries = {
|
||||
about: createQuery(getAboutInfoQuery),
|
||||
history: createQuery(getVersionHistoryQuery),
|
||||
};
|
||||
|
||||
const info = $derived(queries.about.data);
|
||||
const versions = $derived(queries.history.data ?? []);
|
||||
let isMain = $derived(info?.sourceRef === 'main' && info.repository === 'immich-app/immich');
|
||||
let version = $derived($serverVersion ? semverToName($serverVersion) : null);
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import RecentAlbums from '$lib/components/shared-components/side-bar/RecentAlbums.svelte';
|
||||
import Sidebar from '$lib/components/sidebar/Sidebar.svelte';
|
||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import { Route } from '$lib/route';
|
||||
import { recentAlbumsDropdown } from '$lib/stores/preferences.store';
|
||||
import { NavbarGroup, NavbarItem } from '@immich/ui';
|
||||
@@ -37,6 +36,11 @@
|
||||
} from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fly } from 'svelte/transition';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getUserConfigQuery } from '$lib/queries/config-user.query';
|
||||
|
||||
const query = createPreloadedQuery(getUserConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
</script>
|
||||
|
||||
<Sidebar ariaLabel={$t('primary')}>
|
||||
@@ -44,7 +48,7 @@
|
||||
|
||||
<NavbarItem title={$t('explore')} href={Route.explore()} icon={mdiMagnify} />
|
||||
|
||||
{#if featureFlagsManager.value.map}
|
||||
{#if config.map.enabled}
|
||||
<NavbarItem title={$t('map')} href={Route.map()} icon={mdiMapOutline} activeIcon={mdiMap} />
|
||||
{/if}
|
||||
|
||||
@@ -111,7 +115,7 @@
|
||||
|
||||
<NavbarItem title={$t('locked_folder')} href={Route.locked()} icon={mdiLockOutline} activeIcon={mdiLock} />
|
||||
|
||||
{#if featureFlagsManager.value.trash}
|
||||
{#if config.trash.enabled}
|
||||
<NavbarItem title={$t('trash')} href={Route.trash()} icon={mdiTrashCanOutline} activeIcon={mdiTrashCan} />
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ import { goto } from '$app/navigation';
|
||||
import { page } from '$app/state';
|
||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||
import { queryClient } from '$lib/queries/client';
|
||||
import { getAdminConfigQuery } from '$lib/queries/config-admin.query';
|
||||
import { getUserConfigQuery } from '$lib/queries/config-user.query';
|
||||
import { getAboutInfoQuery } from '$lib/queries/server.query';
|
||||
import { Route } from '$lib/route';
|
||||
import { isSharedLinkRoute } from '$lib/utils/navigation';
|
||||
@@ -62,7 +64,14 @@ class AuthManager {
|
||||
|
||||
async refresh() {
|
||||
try {
|
||||
const [user, preferences] = await Promise.all([getMyUser(), getMyPreferences()]);
|
||||
const [user, preferences] = await Promise.all([
|
||||
getMyUser(),
|
||||
getMyPreferences(),
|
||||
queryClient.query(getUserConfigQuery()),
|
||||
]);
|
||||
if (user.isAdmin) {
|
||||
await queryClient.query(getAdminConfigQuery());
|
||||
}
|
||||
this.#preferences = preferences;
|
||||
this.#user = user;
|
||||
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import { getServerConfig, type ServerConfigDto } from '@immich/sdk';
|
||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||
|
||||
class ServerConfigManager {
|
||||
#value?: ServerConfigDto = $state();
|
||||
|
||||
constructor() {
|
||||
eventManager.on({
|
||||
SystemConfigUpdate: () => this.loadServerConfig(),
|
||||
});
|
||||
}
|
||||
|
||||
async init() {
|
||||
await this.loadServerConfig();
|
||||
}
|
||||
|
||||
get value() {
|
||||
if (!this.#value) {
|
||||
throw new Error('Server config manager must be initialized first');
|
||||
}
|
||||
|
||||
return this.#value;
|
||||
}
|
||||
|
||||
async loadServerConfig() {
|
||||
this.#value = await getServerConfig();
|
||||
}
|
||||
}
|
||||
|
||||
export const serverConfigManager = new ServerConfigManager();
|
||||
@@ -1,9 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import { handleResetPinCode } from '$lib/services/user.service';
|
||||
import { BasicModal, Field, FormModal, HelperText, PasswordInput, Stack, type ModalSize } from '@immich/ui';
|
||||
import { mdiLockReset } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getUserConfigQuery } from '$lib/queries/config-user.query';
|
||||
|
||||
const query = createPreloadedQuery(getUserConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
type Props = {
|
||||
onClose: (success?: true) => void;
|
||||
@@ -23,7 +27,7 @@
|
||||
const common = $derived({ title: $t('reset'), size: 'small' as ModalSize, icon: mdiLockReset, onClose });
|
||||
</script>
|
||||
|
||||
{#if featureFlagsManager.value.passwordLogin}
|
||||
{#if config.passwordLogin.enabled}
|
||||
<FormModal {...common} submitColor="danger" submitText={$t('reset')} disabled={!password} {onSubmit}>
|
||||
<Stack gap={4}>
|
||||
<div>{$t('reset_pin_code_description')}</div>
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
<script lang="ts">
|
||||
import FormatMessage from '$lib/elements/FormatMessage.svelte';
|
||||
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
|
||||
import { executeMutation } from '$lib/queries/client';
|
||||
import { createPreloadedQuery, executeMutation } from '$lib/queries/client';
|
||||
import { deleteUserAdminMutation } from '$lib/queries/user-admin.query';
|
||||
import { type UserAdminResponseDto } from '@immich/sdk';
|
||||
import { Alert, Checkbox, ConfirmModal, Field, Input, Label, Text } from '@immich/ui';
|
||||
import { mdiTrashCanOutline } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { getUserConfigQuery } from '$lib/queries/config-user.query';
|
||||
|
||||
const query = createPreloadedQuery(getUserConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
type Props = {
|
||||
user: UserAdminResponseDto;
|
||||
@@ -48,10 +51,7 @@
|
||||
{/snippet}
|
||||
</FormatMessage>
|
||||
{:else}
|
||||
<FormatMessage
|
||||
key="admin.user_delete_delay"
|
||||
values={{ user: user.name, delay: serverConfigManager.value.userDeleteDelay }}
|
||||
>
|
||||
<FormatMessage key="admin.user_delete_delay" values={{ user: user.name, delay: config.user.deleteDelay }}>
|
||||
{#snippet children({ message })}
|
||||
<b>{message}</b>
|
||||
{/snippet}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { toastManager } from '@immich/ui';
|
||||
import {
|
||||
createQuery,
|
||||
hashKey,
|
||||
MutationCache,
|
||||
mutationOptions,
|
||||
MutationObserver,
|
||||
QueryClient,
|
||||
type CreateMutationOptions,
|
||||
type DataTag,
|
||||
type DefaultError,
|
||||
type DefinedCreateQueryResult,
|
||||
type MutateOptions,
|
||||
@@ -17,6 +17,8 @@ import {
|
||||
import { noop } from 'lodash-es';
|
||||
import type { Translations } from 'svelte-i18n';
|
||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||
import { getPublicConfigQuery } from '$lib/queries/config-public.query';
|
||||
import { getServerConfigQuery } from '$lib/queries/server.query';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { getFormatter } from '$lib/utils/i18n';
|
||||
|
||||
@@ -67,7 +69,15 @@ const createQueryClient = () => {
|
||||
}),
|
||||
});
|
||||
|
||||
eventManager.on({ AuthLogout: () => client.clear() });
|
||||
const publicQueryHashes = new Set(
|
||||
[getServerConfigQuery(), getPublicConfigQuery()].map(({ queryKey }) => hashKey(queryKey)),
|
||||
);
|
||||
eventManager.on({
|
||||
AuthLogout: () => {
|
||||
client.removeQueries({ predicate: ({ queryHash }) => !publicQueryHashes.has(queryHash) });
|
||||
client.getMutationCache().clear();
|
||||
},
|
||||
});
|
||||
|
||||
return client;
|
||||
};
|
||||
@@ -79,35 +89,42 @@ export const asMutation = <TArgs extends unknown[], TData, TError = DefaultError
|
||||
options?: Omit<CreateMutationOptions<TData, TError, TArgs, TOnMutateResult>, 'mutationFn' | 'mutationKey'>,
|
||||
) => mutationOptions({ ...options, mutationFn: (args: TArgs) => fn(...args) });
|
||||
|
||||
export const createPreloadedQuery = <
|
||||
TQueryFnData,
|
||||
TError = DefaultError,
|
||||
TData = TQueryFnData,
|
||||
TQueryKey extends QueryKey = QueryKey,
|
||||
>(
|
||||
optionsFn: () => UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey> & {
|
||||
queryKey: DataTag<TQueryKey, TQueryFnData, TError>;
|
||||
},
|
||||
const getPreloadedData = <TQueryFnData>(queryKey: QueryKey) => {
|
||||
const data = queryClient.getQueryData<TQueryFnData, QueryKey, NonUndefinedGuard<TQueryFnData>>(queryKey);
|
||||
if (data === undefined) {
|
||||
const error = new Error(`${queryKey.join('/')} must be loaded first`);
|
||||
console.error(error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
export const readPreloadedQuery = <TQueryFnData, TError, TData, TQueryKey extends QueryKey>(
|
||||
options: UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>,
|
||||
): TData => {
|
||||
const data = getPreloadedData<TQueryFnData>(options.queryKey);
|
||||
return options.select ? options.select(data) : (data as unknown as TData);
|
||||
};
|
||||
|
||||
export const createPreloadedQuery = <TQueryFnData, TError, TData, TQueryKey extends QueryKey>(
|
||||
optionsFn: () => UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>,
|
||||
): DefinedCreateQueryResult<TData, TError> =>
|
||||
createQuery(() => {
|
||||
const options = optionsFn();
|
||||
const initialData = queryClient.getQueryData<TQueryFnData, TQueryKey, NonUndefinedGuard<TQueryFnData>>(
|
||||
options.queryKey,
|
||||
);
|
||||
if (initialData === undefined) {
|
||||
throw new Error(`${options.queryKey.join('/')} must be loaded first`);
|
||||
}
|
||||
createQuery(
|
||||
() => {
|
||||
const options = optionsFn();
|
||||
return { ...options, initialData: getPreloadedData<TQueryFnData>(options.queryKey) };
|
||||
},
|
||||
() => queryClient,
|
||||
);
|
||||
|
||||
return { ...options, initialData };
|
||||
});
|
||||
type MutationArgs<TData, TError, TVariables, TOnMutateResult> = TVariables extends void
|
||||
? [variables?: TVariables, mutateOptions?: MutateOptions<TData, TError, TVariables, TOnMutateResult>]
|
||||
: [variables: TVariables, mutateOptions?: MutateOptions<TData, TError, TVariables, TOnMutateResult>];
|
||||
|
||||
type MutationArgs<TData, TError, TVariables, TContext> = TVariables extends void
|
||||
? [variables?: TVariables, mutateOptions?: MutateOptions<TData, TError, TVariables, TContext>]
|
||||
: [variables: TVariables, mutateOptions?: MutateOptions<TData, TError, TVariables, TContext>];
|
||||
|
||||
export const executeMutation = <TData, TError, TVariables, TContext>(
|
||||
options: () => CreateMutationOptions<TData, TError, TVariables, TContext>,
|
||||
...[variables, mutateOptions]: MutationArgs<TData, TError, TVariables, TContext>
|
||||
export const executeMutation = <TData, TError, TVariables, TOnMutateResult>(
|
||||
options: () => CreateMutationOptions<TData, TError, TVariables, TOnMutateResult>,
|
||||
...[variables, mutateOptions]: MutationArgs<TData, TError, TVariables, TOnMutateResult>
|
||||
) => {
|
||||
const observer = new MutationObserver(queryClient, options());
|
||||
const unsubscribe = observer.subscribe(noop);
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { getAdminConfig, getAdminConfigDefaults, updateAdminConfig, type AdminConfigDto } from '@immich/sdk';
|
||||
import { toastManager } from '@immich/ui';
|
||||
import { mutationOptions, queryOptions } from '@tanstack/svelte-query';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||
import { queryClient } from '$lib/queries/client';
|
||||
import { getPublicConfigQuery } from '$lib/queries/config-public.query';
|
||||
import { getUserConfigQuery } from '$lib/queries/config-user.query';
|
||||
import { getFormatter } from '$lib/utils/i18n';
|
||||
|
||||
export const getAdminConfigQuery = () =>
|
||||
queryOptions({
|
||||
queryKey: ['config', 'admin'],
|
||||
queryFn: ({ signal }) => getAdminConfig({ signal }),
|
||||
});
|
||||
|
||||
export const getAdminConfigDefaultsQuery = () =>
|
||||
queryOptions({
|
||||
queryKey: ['config', 'admin', 'defaults'],
|
||||
queryFn: ({ signal }) => getAdminConfigDefaults({ signal }),
|
||||
});
|
||||
|
||||
export const updateAdminConfigMutation = () =>
|
||||
mutationOptions({
|
||||
mutationFn: async (update: Partial<AdminConfigDto>) => {
|
||||
const config = await getAdminConfig();
|
||||
const adminConfigDto = { ...config, ...update };
|
||||
|
||||
if (isEqual(config, adminConfigDto)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return updateAdminConfig({ adminConfigDto });
|
||||
},
|
||||
onSuccess: async (config) => {
|
||||
if (!config) {
|
||||
return;
|
||||
}
|
||||
|
||||
queryClient.setQueryData(getAdminConfigQuery().queryKey, config);
|
||||
await Promise.all([
|
||||
queryClient.invalidateQueries({ queryKey: getUserConfigQuery().queryKey }),
|
||||
queryClient.invalidateQueries({ queryKey: getPublicConfigQuery().queryKey }),
|
||||
]);
|
||||
eventManager.emit('SystemConfigUpdate', config);
|
||||
|
||||
const $t = await getFormatter();
|
||||
toastManager.primary($t('settings_saved'));
|
||||
},
|
||||
meta: { error: { toast: 'errors.unable_to_save_settings' } },
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import { getPublicConfig } from '@immich/sdk';
|
||||
import { queryOptions } from '@tanstack/svelte-query';
|
||||
|
||||
export const getPublicConfigQuery = () =>
|
||||
queryOptions({
|
||||
queryKey: ['config', 'public'],
|
||||
queryFn: ({ signal }) => getPublicConfig({ signal }),
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
import { getUserConfig, type UserConfigDto } from '@immich/sdk';
|
||||
import { queryOptions } from '@tanstack/svelte-query';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
const withEffectiveFlags = (dto: UserConfigDto) => {
|
||||
const config = cloneDeep(dto);
|
||||
const { machineLearning } = config;
|
||||
|
||||
if (!machineLearning.enabled) {
|
||||
machineLearning.clip.enabled = false;
|
||||
machineLearning.facialRecognition.enabled = false;
|
||||
machineLearning.ocr.enabled = false;
|
||||
}
|
||||
|
||||
if (!machineLearning.clip.enabled) {
|
||||
machineLearning.duplicateDetection.enabled = false;
|
||||
}
|
||||
|
||||
return config;
|
||||
};
|
||||
|
||||
export const getUserConfigQuery = () =>
|
||||
queryOptions({
|
||||
queryKey: ['config', 'user'],
|
||||
queryFn: ({ signal }) => getUserConfig({ signal }),
|
||||
select: withEffectiveFlags,
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getAboutInfo, getServerStatistics, getStorage, getVersionHistory } from '@immich/sdk';
|
||||
import { getAboutInfo, getServerConfig, getServerStatistics, getStorage, getVersionHistory } from '@immich/sdk';
|
||||
import { queryOptions } from '@tanstack/svelte-query';
|
||||
|
||||
export const getAboutInfoQuery = () =>
|
||||
@@ -19,6 +19,12 @@ export const getStorageQuery = () =>
|
||||
queryFn: ({ signal }) => getStorage({ signal }),
|
||||
});
|
||||
|
||||
export const getServerConfigQuery = () =>
|
||||
queryOptions({
|
||||
queryKey: ['server', 'config'],
|
||||
queryFn: ({ signal }) => getServerConfig({ signal }),
|
||||
});
|
||||
|
||||
export const getServerStatisticsQuery = () =>
|
||||
queryOptions({
|
||||
queryKey: ['server', 'statistics'],
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import { getConfig, getConfigDefaults, updateConfig, type AdminConfigDto } from '@immich/sdk';
|
||||
import { toastManager } from '@immich/ui';
|
||||
import { mutationOptions, queryOptions } from '@tanstack/svelte-query';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||
import { queryClient } from '$lib/queries/client';
|
||||
import { getFormatter } from '$lib/utils/i18n';
|
||||
|
||||
export const getConfigQuery = () =>
|
||||
queryOptions({
|
||||
queryKey: ['system-config'],
|
||||
queryFn: ({ signal }) => getConfig({ signal }),
|
||||
});
|
||||
|
||||
export const getConfigDefaultsQuery = () =>
|
||||
queryOptions({
|
||||
queryKey: ['system-config', 'defaults'],
|
||||
queryFn: ({ signal }) => getConfigDefaults({ signal }),
|
||||
});
|
||||
|
||||
export const updateConfigMutation = () =>
|
||||
mutationOptions({
|
||||
mutationFn: async (update: Partial<AdminConfigDto>) => {
|
||||
const config = await getConfig();
|
||||
const adminConfigDto = { ...config, ...update };
|
||||
|
||||
if (isEqual(config, adminConfigDto)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return updateConfig({ adminConfigDto });
|
||||
},
|
||||
onSuccess: async (config) => {
|
||||
if (!config) {
|
||||
return;
|
||||
}
|
||||
|
||||
queryClient.setQueryData(getConfigQuery().queryKey, config);
|
||||
eventManager.emit('SystemConfigUpdate', config);
|
||||
|
||||
const $t = await getFormatter();
|
||||
toastManager.primary($t('settings_saved'));
|
||||
},
|
||||
meta: { error: { toast: 'errors.unable_to_save_settings' } },
|
||||
});
|
||||
@@ -1,7 +1,9 @@
|
||||
import { getAssetInfo } from '@immich/sdk';
|
||||
import { getAssetInfo, type UserConfigDto } from '@immich/sdk';
|
||||
import { toastManager } from '@immich/ui';
|
||||
import { vitest } from 'vitest';
|
||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import { queryClient } from '$lib/queries/client';
|
||||
import { getUserConfigQuery } from '$lib/queries/config-user.query';
|
||||
import { getAssetActions, handleDownloadAsset } from '$lib/services/asset.service';
|
||||
import { setSharedLink } from '$lib/utils';
|
||||
import { getFormatter } from '$lib/utils/i18n';
|
||||
@@ -31,16 +33,19 @@ vitest.mock('$lib/utils', async () => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock(import('$lib/managers/feature-flags-manager.svelte'), function () {
|
||||
return {
|
||||
featureFlagsManager: { init: vi.fn(), loadFeatureFlags: vi.fn(), value: {} } as never,
|
||||
};
|
||||
});
|
||||
|
||||
describe('AssetService', () => {
|
||||
describe('getAssetActions', () => {
|
||||
beforeEach(() => {
|
||||
authManager.setPreferences(preferencesFactory.build());
|
||||
queryClient.setQueryData(getUserConfigQuery().queryKey, {
|
||||
machineLearning: {
|
||||
enabled: false,
|
||||
clip: { enabled: false },
|
||||
duplicateDetection: { enabled: false },
|
||||
facialRecognition: { enabled: false },
|
||||
ocr: { enabled: false },
|
||||
},
|
||||
} as UserConfigDto);
|
||||
});
|
||||
|
||||
it('should allow shared link downloads if the user owns the asset and shared link downloads are disabled', () => {
|
||||
|
||||
@@ -48,11 +48,12 @@ import { assetMultiSelectManager } from '$lib/managers/asset-multi-select-manage
|
||||
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
|
||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import AssetAddToAlbumModal from '$lib/modals/AssetAddToAlbumModal.svelte';
|
||||
import AssetTagModal from '$lib/modals/AssetTagModal.svelte';
|
||||
import ProfileImageCropperModal from '$lib/modals/ProfileImageCropperModal.svelte';
|
||||
import SharedLinkCreateModal from '$lib/modals/SharedLinkCreateModal.svelte';
|
||||
import { readPreloadedQuery } from '$lib/queries/client';
|
||||
import { getUserConfigQuery } from '$lib/queries/config-user.query';
|
||||
import { Route } from '$lib/route';
|
||||
import { SlideshowState, slideshowStore } from '$lib/stores/slideshow.store';
|
||||
import { getAssetMediaUrl, getSharedLink, sleep } from '$lib/utils';
|
||||
@@ -149,7 +150,7 @@ export const getAssetActions = (
|
||||
const authUser = authManager.authenticated ? authManager.user : undefined;
|
||||
const isOwner = !!(authUser && authUser.id === asset.ownerId);
|
||||
const isAlbumOwner = !!(authUser && authUser.id === album?.albumUsers[0].user.id);
|
||||
const smartSearchEnabled = featureFlagsManager.value.smartSearch;
|
||||
const smartSearchEnabled = !!authUser && readPreloadedQuery(getUserConfigQuery()).machineLearning.clip.enabled;
|
||||
|
||||
const Share: ActionItem = {
|
||||
title: $t('share'),
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import type { ServerConfigDto } from '@immich/sdk';
|
||||
import { asUrl } from '$lib/services/shared-link.service';
|
||||
import { sharedLinkFactory } from '@test-data/factories/shared-link-factory';
|
||||
|
||||
vi.mock(import('$lib/managers/server-config-manager.svelte'), () => ({
|
||||
serverConfigManager: {
|
||||
value: { externalDomain: 'http://localhost:2283' } as ServerConfigDto,
|
||||
init: vi.fn(),
|
||||
loadServerConfig: vi.fn(),
|
||||
},
|
||||
vi.mock(import('$lib/queries/client'), async (importOriginal) => ({
|
||||
...(await importOriginal()),
|
||||
readPreloadedQuery: vi.fn().mockReturnValue({ server: { externalDomain: 'http://localhost:2283' } }),
|
||||
}));
|
||||
|
||||
describe('SharedLinkService', () => {
|
||||
|
||||
@@ -13,8 +13,9 @@ import { mdiContentCopy, mdiLink, mdiPencilOutline, mdiQrcode, mdiTrashCanOutlin
|
||||
import type { MessageFormatter } from 'svelte-i18n';
|
||||
import { goto } from '$app/navigation';
|
||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
|
||||
import QrCodeModal from '$lib/modals/QrCodeModal.svelte';
|
||||
import { readPreloadedQuery } from '$lib/queries/client';
|
||||
import { getUserConfigQuery } from '$lib/queries/config-user.query';
|
||||
import { Route } from '$lib/route';
|
||||
import { copyToClipboard } from '$lib/utils';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
@@ -61,7 +62,7 @@ export const getSharedLinkActions = ($t: MessageFormatter, sharedLink: SharedLin
|
||||
|
||||
export const asUrl = (sharedLink: SharedLinkResponseDto) => {
|
||||
const path = Route.viewSharedLink(sharedLink);
|
||||
return new URL(path, serverConfigManager.value.externalDomain || location.origin).href;
|
||||
return new URL(path, readPreloadedQuery(getUserConfigQuery()).server.externalDomain || location.origin).href;
|
||||
};
|
||||
|
||||
export const handleCreateSharedLink = async (dto: SharedLinkCreateDto) => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { ActionItem } from '@immich/ui';
|
||||
import { mdiContentCopy, mdiDownload, mdiUpload } from '@mdi/js';
|
||||
import type { MessageFormatter } from 'svelte-i18n';
|
||||
import { executeMutation } from '$lib/queries/client';
|
||||
import { updateConfigMutation } from '$lib/queries/system-config.query';
|
||||
import { updateAdminConfigMutation } from '$lib/queries/config-admin.query';
|
||||
import { copyToClipboard, downloadJson } from '$lib/utils';
|
||||
|
||||
export const getSystemConfigActions = (
|
||||
@@ -55,7 +55,7 @@ const handleUploadConfig = () => {
|
||||
}
|
||||
const reader = async () => {
|
||||
const text = await file.text();
|
||||
await executeMutation(updateConfigMutation, JSON.parse(text));
|
||||
await executeMutation(updateAdminConfigMutation, JSON.parse(text));
|
||||
};
|
||||
reader()
|
||||
.catch((error) => console.error('Error handling JSON config upload', error))
|
||||
|
||||
@@ -13,11 +13,11 @@ import { DateTime } from 'luxon';
|
||||
import type { MessageFormatter } from 'svelte-i18n';
|
||||
import { goto } from '$app/navigation';
|
||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
|
||||
import PasswordResetSuccessModal from '$lib/modals/PasswordResetSuccessModal.svelte';
|
||||
import UserDeleteConfirmModal from '$lib/modals/UserDeleteConfirmModal.svelte';
|
||||
import UserRestoreConfirmModal from '$lib/modals/UserRestoreConfirmModal.svelte';
|
||||
import { executeMutation } from '$lib/queries/client';
|
||||
import { executeMutation, readPreloadedQuery } from '$lib/queries/client';
|
||||
import { getUserConfigQuery } from '$lib/queries/config-user.query';
|
||||
import { updateUserAdminMutation } from '$lib/queries/user-admin.query';
|
||||
import { Route } from '$lib/route';
|
||||
import type { HeaderButtonActionItem } from '$lib/types';
|
||||
@@ -58,8 +58,10 @@ export const getUserAdminActions = ($t: MessageFormatter, user: UserAdminRespons
|
||||
shortcutOptions: { ignoreInputFields: true },
|
||||
};
|
||||
|
||||
const getDeleteDate = (deletedAt: string): Date =>
|
||||
DateTime.fromISO(deletedAt).plus({ days: serverConfigManager.value.userDeleteDelay }).toJSDate();
|
||||
const getDeleteDate = (deletedAt: string): Date => {
|
||||
const userConfig = readPreloadedQuery(getUserConfigQuery());
|
||||
return DateTime.fromISO(deletedAt).plus({ days: userConfig.user.deleteDelay }).toJSDate();
|
||||
};
|
||||
|
||||
const Restore: HeaderButtonActionItem = {
|
||||
icon: mdiDeleteRestore,
|
||||
@@ -96,7 +98,9 @@ const generatePassword = (length: number = 16) => {
|
||||
const characterSet = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,.-{}+!#$%/()=?';
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
let randomNumber = crypto.getRandomValues(new Uint32Array(1))[0];
|
||||
const buffer = new Uint32Array(1);
|
||||
crypto.getRandomValues(buffer);
|
||||
let randomNumber = buffer[0];
|
||||
randomNumber /= 2 ** 32;
|
||||
randomNumber = Math.floor(randomNumber * characterSet.length);
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import { page } from '$app/state';
|
||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||
import { queryClient } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import { getServerConfigQuery } from '$lib/queries/server.query';
|
||||
import { searchUsersAdminQuery } from '$lib/queries/user-admin.query';
|
||||
import { Route } from '$lib/route';
|
||||
import { maintenanceStore } from '$lib/stores/maintenance.store';
|
||||
@@ -78,10 +78,10 @@ websocket
|
||||
void queryClient.invalidateQueries({ queryKey: ['server'] });
|
||||
}
|
||||
})
|
||||
.on(
|
||||
'on_config_update',
|
||||
() => void queryClient.invalidateQueries({ queryKey: getConfigQuery().queryKey, exact: true }),
|
||||
)
|
||||
.on('on_config_update', () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ['config'] });
|
||||
void queryClient.invalidateQueries({ queryKey: getServerConfigQuery().queryKey });
|
||||
})
|
||||
.on('AppRestartV1', (mode) => websocketStore.serverRestarting.set(mode))
|
||||
.on('MaintenanceStatusV1', (status) => {
|
||||
maintenanceStore.status.set(status);
|
||||
|
||||
@@ -2,8 +2,8 @@ import { setServerLicense, setUserLicense, type LicenseResponseDto } from '@immi
|
||||
import { PUBLIC_IMMICH_BUY_HOST, PUBLIC_IMMICH_PAY_HOST } from '$env/static/public';
|
||||
import type { ImmichProduct } from '$lib/constants';
|
||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
|
||||
import { queryClient } from '$lib/queries/client';
|
||||
import { queryClient, readPreloadedQuery } from '$lib/queries/client';
|
||||
import { getUserConfigQuery } from '$lib/queries/config-user.query';
|
||||
import { getAboutInfoQuery } from '$lib/queries/server.query';
|
||||
|
||||
export const activateProduct = async (licenseKey: string, activationKey: string): Promise<LicenseResponseDto> => {
|
||||
@@ -34,6 +34,9 @@ export const getActivationKey = async (licenseKey: string): Promise<string> => {
|
||||
export const getLicenseLink = (license: ImmichProduct) => {
|
||||
const url = new URL('/', PUBLIC_IMMICH_BUY_HOST);
|
||||
url.searchParams.append('productId', license);
|
||||
url.searchParams.append('instanceUrl', serverConfigManager.value.externalDomain || globalThis.origin);
|
||||
url.searchParams.append(
|
||||
'instanceUrl',
|
||||
readPreloadedQuery(getUserConfigQuery()).server.externalDomain || globalThis.origin,
|
||||
);
|
||||
return url.href;
|
||||
};
|
||||
|
||||
@@ -2,7 +2,9 @@ import { setFetch } from '@immich/sdk';
|
||||
import { memoize } from 'lodash-es';
|
||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
|
||||
import { queryClient } from '$lib/queries/client';
|
||||
import { getPublicConfigQuery } from '$lib/queries/config-public.query';
|
||||
import { getServerConfigQuery } from '$lib/queries/server.query';
|
||||
import { initLanguage } from '$lib/utils';
|
||||
|
||||
type Fetch = typeof fetch;
|
||||
@@ -13,11 +15,11 @@ async function _init(fetch: Fetch) {
|
||||
// https://github.com/oazapfts/oazapfts/blob/main/README.md#fetch-options
|
||||
setFetch(fetch);
|
||||
await initLanguage();
|
||||
await serverConfigManager.init();
|
||||
const serverConfig = await queryClient.query(getServerConfigQuery());
|
||||
await authManager.load();
|
||||
|
||||
if (!serverConfigManager.value.maintenanceMode) {
|
||||
await featureFlagsManager.init();
|
||||
if (!serverConfig.maintenanceMode) {
|
||||
await Promise.all([queryClient.query(getPublicConfigQuery()), featureFlagsManager.init()]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-2
@@ -29,7 +29,6 @@
|
||||
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
|
||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
||||
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
||||
import { Route } from '$lib/route';
|
||||
@@ -68,6 +67,11 @@
|
||||
import AlbumTitle from './AlbumTitle.svelte';
|
||||
import ActionMenuItem from '$lib/components/ActionMenuItem.svelte';
|
||||
import { delay } from '$lib/utils/asset-utils';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getUserConfigQuery } from '$lib/queries/config-user.query';
|
||||
|
||||
const query = createPreloadedQuery(getUserConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
interface Props {
|
||||
data: PageData;
|
||||
@@ -518,7 +522,7 @@
|
||||
|
||||
<ActionButton action={Actions.Share} />
|
||||
|
||||
{#if featureFlagsManager.value.map}
|
||||
{#if config.map.enabled}
|
||||
<AlbumMap {album} />
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
import { timeToLoadTheMap } from '$lib/constants';
|
||||
import Portal from '$lib/elements/Portal.svelte';
|
||||
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
|
||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import { Route } from '$lib/route';
|
||||
import { handlePromiseError } from '$lib/utils';
|
||||
import { delay } from '$lib/utils/asset-utils';
|
||||
@@ -15,6 +14,11 @@
|
||||
import { LoadingSpinner } from '@immich/ui';
|
||||
import { onDestroy } from 'svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getUserConfigQuery } from '$lib/queries/config-user.query';
|
||||
|
||||
const query = createPreloadedQuery(getUserConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
interface Props {
|
||||
data: PageData;
|
||||
@@ -35,9 +39,11 @@
|
||||
assetViewerManager.showAssetViewer(false);
|
||||
});
|
||||
|
||||
if (!featureFlagsManager.value.map) {
|
||||
handlePromiseError(goto(Route.photos()));
|
||||
}
|
||||
$effect(() => {
|
||||
if (!config.map.enabled) {
|
||||
handlePromiseError(goto(Route.photos()));
|
||||
}
|
||||
});
|
||||
|
||||
async function onViewAssets(assetIds: string[]) {
|
||||
await assetViewerManager.setAssetId(assetIds[0]);
|
||||
@@ -53,7 +59,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if featureFlagsManager.value.map}
|
||||
{#if config.map.enabled}
|
||||
<UserPageLayout title={data.meta.title}>
|
||||
<div class="isolate flex size-full flex-col sm:flex-row">
|
||||
<div
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
import AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte';
|
||||
import { QueryParameter } from '$lib/constants';
|
||||
import { assetMultiSelectManager } from '$lib/managers/asset-multi-select-manager.svelte';
|
||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import { searchManager } from '$lib/managers/search-manager.svelte';
|
||||
import type { Viewport } from '$lib/managers/timeline-manager/types';
|
||||
import { Route } from '$lib/route';
|
||||
@@ -44,6 +43,11 @@
|
||||
import { mdiArrowLeft, mdiClose, mdiDotsVertical, mdiImageOffOutline, mdiSelectAll } from '@mdi/js';
|
||||
import { onMount, tick, untrack } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getUserConfigQuery } from '$lib/queries/config-user.query';
|
||||
|
||||
const query = createPreloadedQuery(getUserConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
const viewport: Viewport = $state({ width: 0, height: 0 });
|
||||
let searchResultsElement: HTMLElement | undefined = $state();
|
||||
@@ -62,7 +66,6 @@
|
||||
|
||||
type SearchTerms = MetadataSearchDto & Pick<SmartSearchDto, 'query' | 'queryAssetId'>;
|
||||
let searchQuery = $derived(page.url.searchParams.get(QueryParameter.QUERY));
|
||||
let smartSearchEnabled = $derived(featureFlagsManager.value.smartSearch);
|
||||
let terms = $derived<SearchTerms>(searchQuery ? JSON.parse(searchQuery) : {});
|
||||
let searchTermKeys = $derived(getObjectKeys(terms));
|
||||
|
||||
@@ -139,7 +142,7 @@
|
||||
|
||||
try {
|
||||
const { albums, assets } =
|
||||
('query' in searchDto || 'queryAssetId' in searchDto) && smartSearchEnabled
|
||||
('query' in searchDto || 'queryAssetId' in searchDto) && config.machineLearning.clip.enabled
|
||||
? await searchSmart({
|
||||
smartSearchDto: { visibility: AssetVisibility.Timeline, ...searchDto, language: $lang },
|
||||
})
|
||||
|
||||
@@ -9,14 +9,17 @@
|
||||
import AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte';
|
||||
import Timeline from '$lib/components/timeline/Timeline.svelte';
|
||||
import { assetMultiSelectManager } from '$lib/managers/asset-multi-select-manager.svelte';
|
||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
|
||||
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
||||
import { Route } from '$lib/route';
|
||||
import { getTrashActions } from '$lib/services/trash.service';
|
||||
import { handlePromiseError } from '$lib/utils';
|
||||
import { t } from 'svelte-i18n';
|
||||
import type { PageData } from './$types';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getUserConfigQuery } from '$lib/queries/config-user.query';
|
||||
|
||||
const query = createPreloadedQuery(getUserConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
type Props = {
|
||||
data: PageData;
|
||||
@@ -27,7 +30,7 @@
|
||||
let timelineManager = $state<TimelineManager>() as TimelineManager;
|
||||
const options = { isTrashed: true };
|
||||
|
||||
if (!featureFlagsManager.value.trash) {
|
||||
if (!config.trash.enabled) {
|
||||
handlePromiseError(goto(Route.photos()));
|
||||
}
|
||||
|
||||
@@ -43,7 +46,7 @@
|
||||
const { Empty, RestoreAll } = $derived(getTrashActions($t, timelineManager?.assetCount ?? 0));
|
||||
</script>
|
||||
|
||||
{#if featureFlagsManager.value.trash}
|
||||
{#if config.trash.enabled}
|
||||
<UserPageLayout
|
||||
hideNavbar={assetMultiSelectManager.selectionActive}
|
||||
actions={assetMultiSelectManager.selectionActive ? [] : [Empty, RestoreAll]}
|
||||
@@ -59,7 +62,7 @@
|
||||
>
|
||||
<p class="p-4 font-medium text-gray-500/60 dark:text-gray-300/60">
|
||||
{$t('trashed_items_will_be_permanently_deleted_after', {
|
||||
values: { days: serverConfigManager.value.trashDays },
|
||||
values: { days: config.trash.days },
|
||||
})}
|
||||
</p>
|
||||
{#snippet empty()}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script lang="ts">
|
||||
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
|
||||
import SettingAccordion from '$lib/components/shared-components/settings/SettingAccordion.svelte';
|
||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
@@ -7,6 +6,11 @@
|
||||
import { Button, Field, NumberInput, Select, Switch, toastManager } from '@immich/ui';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getUserConfigQuery } from '$lib/queries/config-user.query';
|
||||
|
||||
const query = createPreloadedQuery(getUserConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
// Albums
|
||||
let defaultAssetOrder = $state(authManager.preferences.albums?.defaultAssetOrder ?? AssetOrder.Desc);
|
||||
@@ -23,7 +27,9 @@
|
||||
// People
|
||||
let peopleEnabled = $state(authManager.preferences.people?.enabled ?? false);
|
||||
let peopleSidebar = $state(authManager.preferences.people?.sidebarWeb ?? false);
|
||||
let peopleMinFaces = $state(authManager.preferences.people?.minimumFaces ?? serverConfigManager.value.minFaces);
|
||||
let peopleMinFaces = $state(
|
||||
authManager.preferences.people?.minimumFaces ?? config.machineLearning.facialRecognition.minFaces,
|
||||
);
|
||||
let peopleUpdateStrategy = $state(authManager.preferences.people?.updateStrategy ?? PersonUpdateStrategy.Everyone);
|
||||
|
||||
// Ratings
|
||||
|
||||
+14
-5
@@ -1,14 +1,23 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
|
||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import { oauth } from '$lib/utils';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { Button, LoadingSpinner, toastManager } from '@immich/ui';
|
||||
import { onMount } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getUserConfigQuery } from '$lib/queries/config-user.query';
|
||||
import { getServerConfigQuery } from '$lib/queries/server.query';
|
||||
|
||||
const queries = {
|
||||
config: createPreloadedQuery(getUserConfigQuery),
|
||||
serverConfig: createPreloadedQuery(getServerConfigQuery),
|
||||
};
|
||||
|
||||
const config = $derived(queries.config.data);
|
||||
const serverConfig = $derived(queries.serverConfig.data);
|
||||
|
||||
let loading = $state(true);
|
||||
|
||||
@@ -47,10 +56,10 @@
|
||||
<div class="flex place-content-center place-items-center">
|
||||
<LoadingSpinner />
|
||||
</div>
|
||||
{:else if featureFlagsManager.value.oauth}
|
||||
{:else if config.oauth.enabled}
|
||||
{#if authManager.user.oauthId}
|
||||
{#if serverConfigManager.value.oauthAccountManagementUrl}
|
||||
<Button shape="round" size="small" href={serverConfigManager.value.oauthAccountManagementUrl}
|
||||
{#if serverConfig.oauthAccountManagementUrl}
|
||||
<Button shape="round" size="small" href={serverConfig.oauthAccountManagementUrl}
|
||||
>{$t('manage_oauth_account')}</Button
|
||||
>
|
||||
{/if}
|
||||
@@ -7,7 +7,6 @@
|
||||
import UserPurchaseSettings from './UserPurchaseSettings.svelte';
|
||||
import UserUsageStatistic from './UserUsageStatistic.svelte';
|
||||
import { OpenQueryParam, QueryParameter } from '$lib/constants';
|
||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import { oauth } from '$lib/utils';
|
||||
import { type ApiKeyResponseDto, type PasskeyResponseDto, type SessionResponseDto } from '@immich/sdk';
|
||||
import {
|
||||
@@ -32,11 +31,16 @@
|
||||
import AppUpdateSettings from './AppUpdateSettings.svelte';
|
||||
import ChangePasswordSettings from './ChangePasswordSettings.svelte';
|
||||
import DeviceList from './DeviceList.svelte';
|
||||
import OauthSettings from './OauthSettings.svelte';
|
||||
import OAuthSettings from './OAuthSettings.svelte';
|
||||
import SharingSettings from './SharingSettings.svelte';
|
||||
import UserApiKeyList from './UserApiKeyList.svelte';
|
||||
import UserPasskeyList from './UserPasskeyList.svelte';
|
||||
import UserProfileSettings from './UserProfileSettings.svelte';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getUserConfigQuery } from '$lib/queries/config-user.query';
|
||||
|
||||
const query = createPreloadedQuery(getUserConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
interface Props {
|
||||
keys?: ApiKeyResponseDto[];
|
||||
@@ -127,7 +131,7 @@
|
||||
<NotificationsSettings />
|
||||
</SettingAccordion>
|
||||
|
||||
{#if featureFlagsManager.value.oauth}
|
||||
{#if config.oauth.enabled}
|
||||
<SettingAccordion
|
||||
icon={mdiTwoFactorAuthentication}
|
||||
key={OpenQueryParam.OAUTH}
|
||||
@@ -135,7 +139,7 @@
|
||||
subtitle={$t('manage_your_oauth_connection')}
|
||||
isOpen={oauthOpen || undefined}
|
||||
>
|
||||
<OauthSettings />
|
||||
<OAuthSettings />
|
||||
</SettingAccordion>
|
||||
{/if}
|
||||
|
||||
|
||||
+8
-4
@@ -6,7 +6,6 @@
|
||||
import LinkToDocs from './LinkToDocs.svelte';
|
||||
import DuplicatesCompareControl from './DuplicatesCompareControl.svelte';
|
||||
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
|
||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import { languageManager } from '$lib/managers/language-manager.svelte';
|
||||
import ShortcutsModal from '$lib/modals/ShortcutsModal.svelte';
|
||||
import { Route } from '$lib/route';
|
||||
@@ -26,6 +25,11 @@
|
||||
} from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
import type { PageData } from './$types';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getUserConfigQuery } from '$lib/queries/config-user.query';
|
||||
|
||||
const query = createPreloadedQuery(getUserConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
interface Props {
|
||||
data: PageData;
|
||||
@@ -90,14 +94,14 @@
|
||||
return;
|
||||
}
|
||||
|
||||
const message = featureFlagsManager.value.trash
|
||||
const message = config.trash.enabled
|
||||
? $t('assets_moved_to_trash_count', { values: { count: trashedCount } })
|
||||
: $t('permanently_deleted_assets_count', { values: { count: trashedCount } });
|
||||
toastManager.primary(message);
|
||||
};
|
||||
|
||||
const handleResolve = async (duplicateId: string, duplicateAssetIds: string[], trashIds: string[]) => {
|
||||
const forceDelete = !featureFlagsManager.value.trash;
|
||||
const forceDelete = !config.trash.enabled;
|
||||
const shouldConfirmDelete = trashIds.length > 0 && forceDelete;
|
||||
|
||||
return withConfirmation(
|
||||
@@ -141,7 +145,7 @@
|
||||
});
|
||||
|
||||
let prompt, confirmText;
|
||||
if (featureFlagsManager.value.trash) {
|
||||
if (config.trash.enabled) {
|
||||
prompt = $t('bulk_trash_duplicates_confirmation', { values: { count: idsToDelete.length } });
|
||||
confirmText = $t('confirm');
|
||||
} else {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { commandPaletteManager } from '@immich/ui';
|
||||
import { goto } from '$app/navigation';
|
||||
import { languageManager } from '$lib/managers/language-manager.svelte';
|
||||
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
|
||||
import { readPreloadedQuery } from '$lib/queries/client';
|
||||
import { getServerConfigQuery } from '$lib/queries/server.query';
|
||||
import { maintenanceCreateUrl, maintenanceReturnUrl, maintenanceShouldRedirect } from '$lib/utils/maintenance';
|
||||
import { init } from '$lib/utils/server';
|
||||
import type { LayoutLoad } from './$types';
|
||||
@@ -14,10 +15,9 @@ export const load = (async ({ fetch, url }) => {
|
||||
try {
|
||||
await init(fetch);
|
||||
|
||||
if (maintenanceShouldRedirect(serverConfigManager.value.maintenanceMode, url)) {
|
||||
await goto(
|
||||
serverConfigManager.value.maintenanceMode ? maintenanceCreateUrl(url) : maintenanceReturnUrl(url.searchParams),
|
||||
);
|
||||
const { maintenanceMode } = readPreloadedQuery(getServerConfigQuery());
|
||||
if (maintenanceShouldRedirect(maintenanceMode, url)) {
|
||||
await goto(maintenanceMode ? maintenanceCreateUrl(url) : maintenanceReturnUrl(url.searchParams));
|
||||
}
|
||||
} catch (initError) {
|
||||
error = initError;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
|
||||
import { readPreloadedQuery } from '$lib/queries/client';
|
||||
import { getServerConfigQuery } from '$lib/queries/server.query';
|
||||
import { Route } from '$lib/route';
|
||||
import { getFormatter } from '$lib/utils/i18n';
|
||||
import { init } from '$lib/utils/server';
|
||||
@@ -13,7 +14,8 @@ export const load = (async ({ fetch }) => {
|
||||
try {
|
||||
await init(fetch);
|
||||
|
||||
if (serverConfigManager.value.maintenanceMode) {
|
||||
const serverConfig = readPreloadedQuery(getServerConfigQuery());
|
||||
if (serverConfig.maintenanceMode) {
|
||||
redirect(307, Route.maintenanceMode());
|
||||
}
|
||||
|
||||
@@ -22,7 +24,7 @@ export const load = (async ({ fetch }) => {
|
||||
redirect(307, Route.photos());
|
||||
}
|
||||
|
||||
if (serverConfigManager.value.isInitialized) {
|
||||
if (serverConfig.isInitialized) {
|
||||
// Redirect to login page if there exists an admin account (i.e. server is initialized)
|
||||
redirect(307, Route.login());
|
||||
}
|
||||
|
||||
@@ -18,11 +18,13 @@
|
||||
|
||||
const { library }: Props = $props();
|
||||
|
||||
const ownerQuery = createPreloadedQuery(() => getUserAdminQuery(library.ownerId));
|
||||
const owner = $derived(ownerQuery.data);
|
||||
const queries = {
|
||||
owner: createPreloadedQuery(() => getUserAdminQuery(library.ownerId)),
|
||||
statistics: createQuery(() => getLibraryStatisticsQuery(library.id)),
|
||||
};
|
||||
|
||||
const query = createQuery(() => getLibraryStatisticsQuery(library.id));
|
||||
const stats = $derived(query.data);
|
||||
const owner = $derived(queries.owner.data);
|
||||
const stats = $derived(queries.statistics.data);
|
||||
|
||||
const actions = $derived.by(() => {
|
||||
const { Detail, Scan, Edit, Delete } = getLibraryActions($t, library);
|
||||
|
||||
@@ -23,11 +23,13 @@
|
||||
|
||||
const { children, params }: LayoutProps = $props();
|
||||
|
||||
const libraryQuery = createPreloadedQuery(() => getLibraryQuery(params.id));
|
||||
const library = $derived(libraryQuery.data);
|
||||
const queries = {
|
||||
library: createPreloadedQuery(() => getLibraryQuery(params.id)),
|
||||
statistics: createQuery(() => getLibraryStatisticsQuery(params.id)),
|
||||
};
|
||||
|
||||
const statisticsQuery = createQuery(() => getLibraryStatisticsQuery(library.id));
|
||||
const stats = $derived(statisticsQuery.data);
|
||||
const library = $derived(queries.library.data);
|
||||
const stats = $derived(queries.statistics.data);
|
||||
|
||||
const [usage, usageUnit] = $derived(stats ? getBytesWithUnit(stats.usage) : [undefined, undefined]);
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<script lang="ts">
|
||||
import QueueCard from './QueueCard.svelte';
|
||||
import QueueStorageMigrationDescription from './QueueStorageMigrationDescription.svelte';
|
||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import { queueManager } from '$lib/managers/queue-manager.svelte';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getUserConfigQuery } from '$lib/queries/config-user.query';
|
||||
import { asQueueItem } from '$lib/services/queue.service';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import {
|
||||
@@ -21,7 +22,8 @@
|
||||
};
|
||||
|
||||
let { queues }: Props = $props();
|
||||
const featureFlags = featureFlagsManager.value;
|
||||
const query = createPreloadedQuery(getUserConfigQuery);
|
||||
const { machineLearning } = $derived(query.data);
|
||||
|
||||
type QueueDetails = {
|
||||
description?: Component;
|
||||
@@ -32,7 +34,7 @@
|
||||
handleCommand?: (jobId: QueueName, jobCommand: QueueCommandDto) => Promise<void>;
|
||||
};
|
||||
|
||||
const queueDetails: Partial<Record<QueueName, QueueDetails>> = {
|
||||
const queueDetails: Partial<Record<QueueName, QueueDetails>> = $derived({
|
||||
[QueueName.ThumbnailGeneration]: {
|
||||
allText: $t('all'),
|
||||
missingText: $t('missing'),
|
||||
@@ -47,33 +49,32 @@
|
||||
[QueueName.Sidecar]: {
|
||||
allText: $t('sync'),
|
||||
missingText: $t('discover'),
|
||||
disabled: !featureFlags.sidecar,
|
||||
},
|
||||
[QueueName.SmartSearch]: {
|
||||
allText: $t('all'),
|
||||
missingText: $t('missing'),
|
||||
disabled: !featureFlags.smartSearch,
|
||||
disabled: !machineLearning.clip.enabled,
|
||||
},
|
||||
[QueueName.DuplicateDetection]: {
|
||||
allText: $t('all'),
|
||||
missingText: $t('missing'),
|
||||
disabled: !featureFlags.duplicateDetection,
|
||||
disabled: !machineLearning.duplicateDetection.enabled,
|
||||
},
|
||||
[QueueName.FaceDetection]: {
|
||||
allText: $t('reset'),
|
||||
refreshText: $t('refresh'),
|
||||
missingText: $t('missing'),
|
||||
disabled: !featureFlags.facialRecognition,
|
||||
disabled: !machineLearning.facialRecognition.enabled,
|
||||
},
|
||||
[QueueName.FacialRecognition]: {
|
||||
allText: $t('reset'),
|
||||
missingText: $t('missing'),
|
||||
disabled: !featureFlags.facialRecognition,
|
||||
disabled: !machineLearning.facialRecognition.enabled,
|
||||
},
|
||||
[QueueName.Ocr]: {
|
||||
allText: $t('all'),
|
||||
missingText: $t('missing'),
|
||||
disabled: !featureFlags.ocr,
|
||||
disabled: !machineLearning.ocr.enabled,
|
||||
},
|
||||
[QueueName.VideoConversion]: {
|
||||
allText: $t('all'),
|
||||
@@ -86,9 +87,9 @@
|
||||
[QueueName.Migration]: {
|
||||
missingText: $t('start'),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
let queueList = Object.entries(queueDetails) as [QueueName, QueueDetails][];
|
||||
const queueList = $derived(Object.entries(queueDetails) as [QueueName, QueueDetails][]);
|
||||
|
||||
const handleCommand = async (name: QueueName, dto: QueueCommandDto) => {
|
||||
const item = asQueueItem($t, { name });
|
||||
|
||||
@@ -21,11 +21,13 @@
|
||||
import { mdiCameraIris, mdiChartPie, mdiPlayCircle } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
const usersQuery = createPreloadedQuery(searchUsersAdminQuery);
|
||||
const users = $derived(usersQuery.data);
|
||||
const queries = {
|
||||
users: createPreloadedQuery(searchUsersAdminQuery),
|
||||
statistics: createQuery(() => ({ ...getServerStatisticsQuery(), refetchInterval: 5000 })),
|
||||
};
|
||||
|
||||
const query = createQuery(() => ({ ...getServerStatisticsQuery(), refetchInterval: 5000 }));
|
||||
const stats = $derived(query.data);
|
||||
const users = $derived(queries.users.data);
|
||||
const stats = $derived(queries.statistics.data);
|
||||
|
||||
const getStorageUsageWithUnit = (usage: number) => {
|
||||
const TiB = 1024 ** 4;
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
import SearchBar from '$lib/elements/SearchBar.svelte';
|
||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import { getAdminConfigQuery } from '$lib/queries/config-admin.query';
|
||||
import { getSystemConfigActions } from '$lib/services/system-config.service';
|
||||
import { Alert, CommandPaletteDefaultProvider, Container } from '@immich/ui';
|
||||
import {
|
||||
@@ -57,7 +57,8 @@
|
||||
|
||||
const { data }: Props = $props();
|
||||
|
||||
const config = createPreloadedQuery(getConfigQuery);
|
||||
const query = createPreloadedQuery(getAdminConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
const settings: Array<{
|
||||
component: Component;
|
||||
@@ -205,14 +206,12 @@
|
||||
|
||||
let filteredSettings = $derived(
|
||||
settings.filter(({ title, subtitle }) => {
|
||||
const query = searchQuery.toLowerCase();
|
||||
return title.toLowerCase().includes(query) || subtitle.toLowerCase().includes(query);
|
||||
const term = searchQuery.toLowerCase();
|
||||
return title.toLowerCase().includes(term) || subtitle.toLowerCase().includes(term);
|
||||
}),
|
||||
);
|
||||
|
||||
const { CopyToClipboard, Upload, Download } = $derived(
|
||||
getSystemConfigActions($t, featureFlagsManager.value, config.data),
|
||||
);
|
||||
const { CopyToClipboard, Upload, Download } = $derived(getSystemConfigActions($t, featureFlagsManager.value, config));
|
||||
</script>
|
||||
|
||||
<CommandPaletteDefaultProvider name={$t('admin.system_settings')} actions={[CopyToClipboard, Upload, Download]} />
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { queryClient } from '$lib/queries/client';
|
||||
import { getConfigDefaultsQuery, getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import { getAdminConfigDefaultsQuery } from '$lib/queries/config-admin.query';
|
||||
import { authenticate } from '$lib/utils/auth';
|
||||
import { getFormatter } from '$lib/utils/i18n';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load = (async ({ url }) => {
|
||||
await authenticate(url, { admin: true });
|
||||
await Promise.all([queryClient.query(getConfigQuery()), queryClient.query(getConfigDefaultsQuery())]);
|
||||
await queryClient.query(getAdminConfigDefaultsQuery());
|
||||
const $t = await getFormatter();
|
||||
|
||||
return {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import AuthDisableLoginConfirmModal from '$lib/modals/AuthDisableLoginConfirmModal.svelte';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import { getAdminConfigQuery } from '$lib/queries/config-admin.query';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { OAuthTokenEndpointAuthMethod, unlinkAllOAuthAccountsAdmin } from '@immich/sdk';
|
||||
import { Button, IconButton, Link, modalManager, Text, toastManager } from '@immich/ui';
|
||||
@@ -18,7 +18,7 @@
|
||||
import { fade } from 'svelte/transition';
|
||||
import SettingSelect from './SettingSelect.svelte';
|
||||
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const query = createPreloadedQuery(getAdminConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import { getAdminConfigQuery } from '$lib/queries/config-admin.query';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
|
||||
import SettingInputField from '$lib/components/shared-components/settings/SettingInputField.svelte';
|
||||
@@ -13,7 +13,7 @@
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const query = createPreloadedQuery(getAdminConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import { getAdminConfigQuery } from '$lib/queries/config-admin.query';
|
||||
import SettingAccordion from '$lib/components/shared-components/settings/SettingAccordion.svelte';
|
||||
import SettingCheckboxes from './SettingCheckboxes.svelte';
|
||||
import SettingInputField from '$lib/components/shared-components/settings/SettingInputField.svelte';
|
||||
@@ -26,7 +26,7 @@
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const query = createPreloadedQuery(getAdminConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import { getAdminConfigQuery } from '$lib/queries/config-admin.query';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import SettingSelect from './SettingSelect.svelte';
|
||||
import { Colorspace, ImageFormat } from '@immich/sdk';
|
||||
@@ -14,7 +14,7 @@
|
||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const query = createPreloadedQuery(getAdminConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import { getAdminConfigQuery } from '$lib/queries/config-admin.query';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import SettingAccordion from '$lib/components/shared-components/settings/SettingAccordion.svelte';
|
||||
import SettingInputField from '$lib/components/shared-components/settings/SettingInputField.svelte';
|
||||
@@ -13,7 +13,7 @@
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const query = createPreloadedQuery(getAdminConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import { getAdminConfigQuery } from '$lib/queries/config-admin.query';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
|
||||
import SettingInputField from '$lib/components/shared-components/settings/SettingInputField.svelte';
|
||||
@@ -10,7 +10,7 @@
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const query = createPreloadedQuery(getAdminConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import { getAdminConfigQuery } from '$lib/queries/config-admin.query';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import SettingAccordion from '$lib/components/shared-components/settings/SettingAccordion.svelte';
|
||||
import SettingInputField from '$lib/components/shared-components/settings/SettingInputField.svelte';
|
||||
@@ -14,7 +14,7 @@
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const query = createPreloadedQuery(getAdminConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import { getAdminConfigQuery } from '$lib/queries/config-admin.query';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
|
||||
import SettingSelect from './SettingSelect.svelte';
|
||||
@@ -10,7 +10,7 @@
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const query = createPreloadedQuery(getAdminConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import { getAdminConfigQuery } from '$lib/queries/config-admin.query';
|
||||
import SettingAccordion from '$lib/components/shared-components/settings/SettingAccordion.svelte';
|
||||
import SettingInputField from '$lib/components/shared-components/settings/SettingInputField.svelte';
|
||||
import SettingSelect from './SettingSelect.svelte';
|
||||
@@ -15,7 +15,7 @@
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const query = createPreloadedQuery(getAdminConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
@@ -171,7 +171,7 @@
|
||||
min={0.001}
|
||||
max={0.1}
|
||||
description={$t('admin.machine_learning_max_detection_distance_description')}
|
||||
disabled={disabled || !featureFlagsManager.value.duplicateDetection}
|
||||
disabled={disabled || !config.machineLearning.duplicateDetection.enabled}
|
||||
isEdited={configToEdit.machineLearning.duplicateDetection.maxDistance !==
|
||||
config.machineLearning.duplicateDetection.maxDistance}
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import { getAdminConfigQuery } from '$lib/queries/config-admin.query';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import SettingAccordion from '$lib/components/shared-components/settings/SettingAccordion.svelte';
|
||||
import SettingInputField from '$lib/components/shared-components/settings/SettingInputField.svelte';
|
||||
@@ -13,7 +13,7 @@
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const query = createPreloadedQuery(getAdminConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import { getAdminConfigQuery } from '$lib/queries/config-admin.query';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
|
||||
import SettingSwitch from '$lib/components/shared-components/settings/SettingSwitch.svelte';
|
||||
@@ -8,7 +8,7 @@
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const query = createPreloadedQuery(getAdminConfigQuery);
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
let configToEdit = $state(cloneDeep(query.data));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import { getAdminConfigQuery } from '$lib/queries/config-admin.query';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
|
||||
import SettingSwitch from '$lib/components/shared-components/settings/SettingSwitch.svelte';
|
||||
@@ -10,7 +10,7 @@
|
||||
import SettingSelect from './SettingSelect.svelte';
|
||||
import { ReleaseChannel } from '@immich/sdk';
|
||||
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const query = createPreloadedQuery(getAdminConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import { getAdminConfigQuery } from '$lib/queries/config-admin.query';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
|
||||
import SettingInputField from '$lib/components/shared-components/settings/SettingInputField.svelte';
|
||||
@@ -10,7 +10,7 @@
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const query = createPreloadedQuery(getAdminConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
|
||||
@@ -9,14 +9,14 @@
|
||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import { createPreloadedQuery, executeMutation } from '$lib/queries/client';
|
||||
import { getConfigQuery, updateConfigMutation } from '$lib/queries/system-config.query';
|
||||
import { getAdminConfigQuery, updateAdminConfigMutation } from '$lib/queries/config-admin.query';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { sendTestEmailAdmin } from '@immich/sdk';
|
||||
import { Button, toastManager } from '@immich/ui';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const query = createPreloadedQuery(getAdminConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
@@ -53,7 +53,7 @@
|
||||
);
|
||||
|
||||
if (!disabled) {
|
||||
await executeMutation(updateConfigMutation, { notifications: configToEdit.notifications });
|
||||
await executeMutation(updateAdminConfigMutation, { notifications: configToEdit.notifications });
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(error, $t('admin.notification_email_test_email_failed'));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import { getAdminConfigQuery } from '$lib/queries/config-admin.query';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
|
||||
import SettingInputField from '$lib/components/shared-components/settings/SettingInputField.svelte';
|
||||
@@ -10,7 +10,7 @@
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const query = createPreloadedQuery(getAdminConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import SettingTextarea from './SettingTextarea.svelte';
|
||||
import FormatMessage from '$lib/elements/FormatMessage.svelte';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import { getAdminConfigQuery } from '$lib/queries/config-admin.query';
|
||||
import EmailTemplatePreviewModal from '$lib/modals/EmailTemplatePreviewModal.svelte';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { type AdminConfigDto, type AdminConfigTemplateEmailsDto, getNotificationTemplateAdmin } from '@immich/sdk';
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
let { config = $bindable() }: Props = $props();
|
||||
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const query = createPreloadedQuery(getAdminConfigQuery);
|
||||
|
||||
let loadingPreview = $state(false);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import { getAdminConfigQuery } from '$lib/queries/config-admin.query';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
|
||||
import SettingTextarea from './SettingTextarea.svelte';
|
||||
@@ -8,7 +8,7 @@
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const query = createPreloadedQuery(getAdminConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import { getAdminConfigQuery } from '$lib/queries/config-admin.query';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
|
||||
import SettingInputField from '$lib/components/shared-components/settings/SettingInputField.svelte';
|
||||
@@ -10,7 +10,7 @@
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const query = createPreloadedQuery(getAdminConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import { getAdminConfigQuery } from '$lib/queries/config-admin.query';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const query = createPreloadedQuery(getAdminConfigQuery);
|
||||
const config = $derived(query.data);
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import { Route } from '$lib/route';
|
||||
import { executeMutation } from '$lib/queries/client';
|
||||
import { createPreloadedQuery, executeMutation } from '$lib/queries/client';
|
||||
import { getStorageQuery } from '$lib/queries/server.query';
|
||||
import { createUserAdminMutation } from '$lib/queries/user-admin.query';
|
||||
import { ByteUnit, convertToBytes } from '$lib/utils/byte-units';
|
||||
import { Field, FormModal, HelperText, Input, PasswordInput, Stack, Switch } from '@immich/ui';
|
||||
import { createQuery } from '@tanstack/svelte-query';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { getUserConfigQuery } from '$lib/queries/config-user.query';
|
||||
|
||||
const query = createQuery(getStorageQuery);
|
||||
const serverInfo = $derived(query.data);
|
||||
const queries = {
|
||||
config: createPreloadedQuery(getUserConfigQuery),
|
||||
storage: createQuery(getStorageQuery),
|
||||
};
|
||||
|
||||
const config = $derived(queries.config.data);
|
||||
const serverInfo = $derived(queries.storage.data);
|
||||
|
||||
let success = $state(false);
|
||||
|
||||
@@ -66,17 +71,17 @@
|
||||
<Input bind:value={email} type="email" />
|
||||
</Field>
|
||||
|
||||
{#if featureFlagsManager.value.email}
|
||||
{#if config.notifications.smtp.enabled}
|
||||
<Field label={$t('admin.send_welcome_email')}>
|
||||
<Switch id="send-welcome-email" bind:checked={notify} class="text-sm" />
|
||||
</Field>
|
||||
{/if}
|
||||
|
||||
<Field label={$t('password')} required={!featureFlagsManager.value.oauth}>
|
||||
<Field label={$t('password')} required={!config.oauth.enabled}>
|
||||
<PasswordInput id="password" bind:value={password} autocomplete="new-password" />
|
||||
</Field>
|
||||
|
||||
<Field label={$t('confirm_password')} required={!featureFlagsManager.value.oauth}>
|
||||
<Field label={$t('confirm_password')} required={!config.oauth.enabled}>
|
||||
<PasswordInput id="confirmPassword" bind:value={passwordConfirm} autocomplete="new-password" />
|
||||
<HelperText color="danger">{passwordMismatchMessage}</HelperText>
|
||||
</Field>
|
||||
|
||||
@@ -14,11 +14,13 @@
|
||||
|
||||
const { params }: PageProps = $props();
|
||||
|
||||
const query = createQuery(getStorageQuery);
|
||||
const serverInfo = $derived(query.data);
|
||||
const queries = {
|
||||
user: createPreloadedQuery(() => getUserAdminQuery(params.id)),
|
||||
storage: createQuery(getStorageQuery),
|
||||
};
|
||||
|
||||
const userQuery = createPreloadedQuery(() => getUserAdminQuery(params.id));
|
||||
const user = $derived(userQuery.data);
|
||||
const user = $derived(queries.user.data);
|
||||
const serverInfo = $derived(queries.storage.data);
|
||||
let { isAdmin, name, email } = $derived(user);
|
||||
let storageLabel = $derived(user.storageLabel || '');
|
||||
const previousQuota = $derived(user.quotaSizeInBytes);
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import AuthPageLayout from '$lib/components/layouts/AuthPageLayout.svelte';
|
||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getPublicConfigQuery } from '$lib/queries/config-public.query';
|
||||
import { getServerConfigQuery } from '$lib/queries/server.query';
|
||||
import { Route } from '$lib/route';
|
||||
import { handleLoginWithPasskey, isPasskeyCancelled, isPasskeySupported } from '$lib/services/passkey.service';
|
||||
import { oauth } from '$lib/utils';
|
||||
@@ -28,10 +30,16 @@
|
||||
let loading = $state(false);
|
||||
let oauthLoading = $state(true);
|
||||
let passkeyLoading = $state(false);
|
||||
const passkeySupported = isPasskeySupported();
|
||||
|
||||
const serverConfig = $derived(serverConfigManager.value);
|
||||
const publicConfig = $derived(data.publicConfig);
|
||||
const queries = {
|
||||
publicConfig: createPreloadedQuery(getPublicConfigQuery),
|
||||
serverConfig: createPreloadedQuery(getServerConfigQuery),
|
||||
};
|
||||
|
||||
const publicConfig = $derived(queries.publicConfig.data);
|
||||
const serverConfig = $derived(queries.serverConfig.data);
|
||||
|
||||
const passkeySupported = isPasskeySupported();
|
||||
const passkeyEnabled = $derived(publicConfig.passkey.enabled && passkeySupported);
|
||||
|
||||
const onSuccess = async (user: LoginResponseDto) => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getPublicConfig } from '@immich/sdk';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
|
||||
import { readPreloadedQuery } from '$lib/queries/client';
|
||||
import { getServerConfigQuery } from '$lib/queries/server.query';
|
||||
import { Route } from '$lib/route';
|
||||
import { getFormatter } from '$lib/utils/i18n';
|
||||
import type { PageLoad } from './$types';
|
||||
@@ -15,19 +15,16 @@ export const load = (async ({ parent, url }) => {
|
||||
redirect(307, continueUrl);
|
||||
}
|
||||
|
||||
if (!serverConfigManager.value.isInitialized) {
|
||||
if (!readPreloadedQuery(getServerConfigQuery()).isInitialized) {
|
||||
// Admin not registered
|
||||
redirect(307, Route.register());
|
||||
}
|
||||
|
||||
const publicConfig = await getPublicConfig();
|
||||
|
||||
const $t = await getFormatter();
|
||||
return {
|
||||
meta: {
|
||||
title: $t('login'),
|
||||
},
|
||||
continueUrl,
|
||||
publicConfig,
|
||||
};
|
||||
}) satisfies PageLoad;
|
||||
|
||||
@@ -11,9 +11,6 @@
|
||||
import OnboardingTheme from './OnboardingTheme.svelte';
|
||||
import OnboardingUserPrivacy from './OnboardingUserPrivacy.svelte';
|
||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
|
||||
import { queryClient } from '$lib/queries/client';
|
||||
import { getConfigQuery } from '$lib/queries/system-config.query';
|
||||
import { Route } from '$lib/route';
|
||||
import { OnboardingRole } from '$lib/types';
|
||||
import { setUserOnboarding, updateAdminOnboarding } from '@immich/sdk';
|
||||
@@ -25,8 +22,12 @@
|
||||
mdiThemeLightDark,
|
||||
mdiTranslate,
|
||||
} from '@mdi/js';
|
||||
import { onMount } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { createPreloadedQuery, queryClient } from '$lib/queries/client';
|
||||
import { getServerConfigQuery } from '$lib/queries/server.query';
|
||||
|
||||
const query = createPreloadedQuery(getServerConfigQuery);
|
||||
const serverConfig = $derived(query.data);
|
||||
|
||||
interface OnboardingStep {
|
||||
name: string;
|
||||
@@ -102,7 +103,7 @@
|
||||
return temporaryIndex === -1 ? 0 : temporaryIndex;
|
||||
});
|
||||
let userRole = $derived(
|
||||
authManager.user.isAdmin && !serverConfigManager.value.isOnboarded ? OnboardingRole.SERVER : OnboardingRole.USER,
|
||||
authManager.user.isAdmin && !serverConfig.isOnboarded ? OnboardingRole.SERVER : OnboardingRole.USER,
|
||||
);
|
||||
|
||||
let onboardingStepCount = $derived(onboardingSteps.filter((step) => shouldRunStep(step.role, userRole)).length);
|
||||
@@ -113,9 +114,7 @@
|
||||
const shouldRunStep = (stepRole: OnboardingRole, userRole: OnboardingRole) => {
|
||||
return (
|
||||
stepRole === OnboardingRole.USER ||
|
||||
(stepRole === OnboardingRole.SERVER &&
|
||||
userRole === OnboardingRole.SERVER &&
|
||||
!serverConfigManager.value.isOnboarded)
|
||||
(stepRole === OnboardingRole.SERVER && userRole === OnboardingRole.SERVER && !serverConfig.isOnboarded)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -131,7 +130,7 @@
|
||||
if (nextStepIndex === -1) {
|
||||
if (authManager.user.isAdmin) {
|
||||
await updateAdminOnboarding({ adminOnboardingUpdateDto: { isOnboarded: true } });
|
||||
await serverConfigManager.loadServerConfig();
|
||||
await queryClient.invalidateQueries({ queryKey: getServerConfigQuery().queryKey });
|
||||
}
|
||||
|
||||
await setUserOnboarding({
|
||||
@@ -153,12 +152,6 @@
|
||||
};
|
||||
|
||||
const OnboardingStep = $derived(onboardingSteps[index].component);
|
||||
|
||||
onMount(async () => {
|
||||
if (userRole === OnboardingRole.SERVER) {
|
||||
await queryClient.query(getConfigQuery());
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<section id="onboarding-page" class="flex min-h-dvh min-w-dvw p-4">
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
|
||||
import { OnboardingRole } from '$lib/types';
|
||||
import { Logo } from '@immich/ui';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { createPreloadedQuery } from '$lib/queries/client';
|
||||
import { getServerConfigQuery } from '$lib/queries/server.query';
|
||||
|
||||
const query = createPreloadedQuery(getServerConfigQuery);
|
||||
const serverConfig = $derived(query.data);
|
||||
|
||||
let userRole = $derived(
|
||||
authManager.user.isAdmin && !serverConfigManager.value.isOnboarded ? OnboardingRole.SERVER : OnboardingRole.USER,
|
||||
authManager.user.isAdmin && !serverConfig.isOnboarded ? OnboardingRole.SERVER : OnboardingRole.USER,
|
||||
);
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<script lang="ts">
|
||||
import SettingSwitch from '$lib/components/shared-components/settings/SettingSwitch.svelte';
|
||||
import { createPreloadedQuery, executeMutation } from '$lib/queries/client';
|
||||
import { getConfigQuery, updateConfigMutation } from '$lib/queries/system-config.query';
|
||||
import { getAdminConfigQuery, updateAdminConfigMutation } from '$lib/queries/config-admin.query';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { onDestroy } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
const query = createPreloadedQuery(getConfigQuery);
|
||||
const query = createPreloadedQuery(getAdminConfigQuery);
|
||||
const configToEdit = $state(cloneDeep(query.data));
|
||||
|
||||
onDestroy(() => {
|
||||
void executeMutation(updateConfigMutation, {
|
||||
void executeMutation(updateAdminConfigMutation, {
|
||||
map: configToEdit.map,
|
||||
newVersionCheck: configToEdit.newVersionCheck,
|
||||
});
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import AuthPageLayout from '$lib/components/layouts/AuthPageLayout.svelte';
|
||||
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
|
||||
import { queryClient } from '$lib/queries/client';
|
||||
import { getServerConfigQuery } from '$lib/queries/server.query';
|
||||
import { Route } from '$lib/route';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { signUpAdmin } from '@immich/sdk';
|
||||
@@ -37,7 +38,7 @@
|
||||
|
||||
try {
|
||||
await signUpAdmin({ signUpDto: { email, password, name } });
|
||||
await serverConfigManager.loadServerConfig();
|
||||
await queryClient.refetchQueries({ queryKey: getServerConfigQuery().queryKey });
|
||||
await goto(Route.login());
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_create_admin_account'));
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
|
||||
import { readPreloadedQuery } from '$lib/queries/client';
|
||||
import { getServerConfigQuery } from '$lib/queries/server.query';
|
||||
import { Route } from '$lib/route';
|
||||
import { getFormatter } from '$lib/utils/i18n';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load = (async ({ parent }) => {
|
||||
await parent();
|
||||
if (serverConfigManager.value.isInitialized) {
|
||||
if (readPreloadedQuery(getServerConfigQuery()).isInitialized) {
|
||||
// Admin has been registered, redirect to login
|
||||
redirect(307, Route.login());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user