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