From 317adc5c285c2ed92b6e105f799647dcec671ede Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Wed, 3 Jan 2024 21:54:48 -0500 Subject: [PATCH] feat(web,server): external domain setting (#6146) * feat: external domain setting * chore: open api * mobile: handle serverconfig-externalDomain --------- Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> --- cli/src/api/open-api/api.ts | 25 ++++ .../shared_link/ui/shared_link_item.dart | 7 +- .../views/shared_link_edit_page.dart | 7 +- .../server_info/server_config.model.dart | 21 ++-- .../providers/server_info.provider.dart | 13 ++- mobile/openapi/.openapi-generator/FILES | 3 + mobile/openapi/README.md | Bin 24103 -> 24161 bytes mobile/openapi/doc/ServerConfigDto.md | Bin 525 -> 563 bytes mobile/openapi/doc/SystemConfigDto.md | Bin 1524 -> 1597 bytes mobile/openapi/doc/SystemConfigServerDto.md | Bin 0 -> 425 bytes mobile/openapi/lib/api.dart | Bin 7858 -> 7902 bytes mobile/openapi/lib/api_client.dart | Bin 22845 -> 22939 bytes .../openapi/lib/model/server_config_dto.dart | Bin 3782 -> 4102 bytes .../openapi/lib/model/system_config_dto.dart | Bin 6575 -> 6822 bytes .../lib/model/system_config_server_dto.dart | Bin 0 -> 2976 bytes .../openapi/test/server_config_dto_test.dart | Bin 909 -> 1022 bytes .../openapi/test/system_config_dto_test.dart | Bin 2158 -> 2270 bytes .../test/system_config_server_dto_test.dart | Bin 0 -> 599 bytes server/immich-openapi-specs.json | 23 +++- .../src/domain/server-info/server-info.dto.ts | 1 + .../server-info/server-info.service.spec.ts | 1 + .../domain/server-info/server-info.service.ts | 1 + .../dto/system-config-server.dto.ts | 6 + .../system-config/dto/system-config.dto.ts | 6 + .../system-config/system-config.core.ts | 3 + .../system-config.service.spec.ts | 3 + .../infra/entities/system-config.entity.ts | 5 + server/test/e2e/server-info.e2e-spec.ts | 1 + web/src/api/open-api/api.ts | 25 ++++ web/src/api/utils.ts | 4 + .../settings/server/server-settings.svelte | 107 ++++++++++++++++++ .../create-shared-link-modal.svelte | 7 +- web/src/lib/stores/server-config.store.ts | 1 + .../(user)/sharing/sharedlinks/+page.svelte | 5 +- .../routes/admin/system-settings/+page.svelte | 5 + 35 files changed, 259 insertions(+), 21 deletions(-) create mode 100644 mobile/openapi/doc/SystemConfigServerDto.md create mode 100644 mobile/openapi/lib/model/system_config_server_dto.dart create mode 100644 mobile/openapi/test/system_config_server_dto_test.dart create mode 100644 server/src/domain/system-config/dto/system-config-server.dto.ts create mode 100644 web/src/lib/components/admin-page/settings/server/server-settings.svelte diff --git a/cli/src/api/open-api/api.ts b/cli/src/api/open-api/api.ts index 05cac4bcf..3c2980ef8 100644 --- a/cli/src/api/open-api/api.ts +++ b/cli/src/api/open-api/api.ts @@ -3007,6 +3007,12 @@ export interface SearchResponseDto { * @interface ServerConfigDto */ export interface ServerConfigDto { + /** + * + * @type {string} + * @memberof ServerConfigDto + */ + 'externalDomain': string; /** * * @type {boolean} @@ -3590,6 +3596,12 @@ export interface SystemConfigDto { * @memberof SystemConfigDto */ 'reverseGeocoding': SystemConfigReverseGeocodingDto; + /** + * + * @type {SystemConfigServerDto} + * @memberof SystemConfigDto + */ + 'server': SystemConfigServerDto; /** * * @type {SystemConfigStorageTemplateDto} @@ -4014,6 +4026,19 @@ export interface SystemConfigReverseGeocodingDto { */ 'enabled': boolean; } +/** + * + * @export + * @interface SystemConfigServerDto + */ +export interface SystemConfigServerDto { + /** + * + * @type {string} + * @memberof SystemConfigServerDto + */ + 'externalDomain': string; +} /** * * @export diff --git a/mobile/lib/modules/shared_link/ui/shared_link_item.dart b/mobile/lib/modules/shared_link/ui/shared_link_item.dart index f41dec639..b147ea3c5 100644 --- a/mobile/lib/modules/shared_link/ui/shared_link_item.dart +++ b/mobile/lib/modules/shared_link/ui/shared_link_item.dart @@ -9,6 +9,7 @@ import 'package:immich_mobile/modules/search/ui/thumbnail_with_info.dart'; import 'package:immich_mobile/modules/shared_link/models/shared_link.dart'; import 'package:immich_mobile/modules/shared_link/providers/shared_link.provider.dart'; import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/shared/providers/server_info.provider.dart'; import 'package:immich_mobile/shared/ui/confirm_dialog.dart'; import 'package:immich_mobile/shared/ui/immich_toast.dart'; import 'package:immich_mobile/utils/image_url_builder.dart'; @@ -71,7 +72,11 @@ class SharedLinkItem extends ConsumerWidget { final imageSize = math.min(context.width / 4, 100.0); void copyShareLinkToClipboard() { - final serverUrl = getServerUrl(); + final externalDomain = ref.read( + serverInfoProvider.select((s) => s.serverConfig.externalDomain), + ); + final serverUrl = + externalDomain.isNotEmpty ? externalDomain : getServerUrl(); if (serverUrl == null) { ImmichToast.show( context: context, diff --git a/mobile/lib/modules/shared_link/views/shared_link_edit_page.dart b/mobile/lib/modules/shared_link/views/shared_link_edit_page.dart index a2d7bfde2..e96fff56a 100644 --- a/mobile/lib/modules/shared_link/views/shared_link_edit_page.dart +++ b/mobile/lib/modules/shared_link/views/shared_link_edit_page.dart @@ -8,6 +8,7 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/shared_link/models/shared_link.dart'; import 'package:immich_mobile/modules/shared_link/providers/shared_link.provider.dart'; import 'package:immich_mobile/modules/shared_link/services/shared_link.service.dart'; +import 'package:immich_mobile/shared/providers/server_info.provider.dart'; import 'package:immich_mobile/shared/ui/immich_toast.dart'; import 'package:immich_mobile/utils/url_helper.dart'; @@ -353,7 +354,11 @@ class SharedLinkEditPage extends HookConsumerWidget { expiresAt: expiryAfter.value == 0 ? null : calculateExpiry(), ); ref.invalidate(sharedLinksStateProvider); - final serverUrl = getServerUrl(); + final externalDomain = ref.read( + serverInfoProvider.select((s) => s.serverConfig.externalDomain), + ); + final serverUrl = + externalDomain.isNotEmpty ? externalDomain : getServerUrl(); if (newLink != null && serverUrl != null) { newShareLink.value = "$serverUrl/share/${newLink.key}"; copyLinkToClipboard(); diff --git a/mobile/lib/shared/models/server_info/server_config.model.dart b/mobile/lib/shared/models/server_info/server_config.model.dart index cdb99987e..7833301cc 100644 --- a/mobile/lib/shared/models/server_info/server_config.model.dart +++ b/mobile/lib/shared/models/server_info/server_config.model.dart @@ -2,35 +2,40 @@ import 'package:openapi/api.dart'; class ServerConfig { final int trashDays; + final String externalDomain; const ServerConfig({ required this.trashDays, + required this.externalDomain, }); ServerConfig copyWith({ int? trashDays, + String? externalDomain, }) { return ServerConfig( trashDays: trashDays ?? this.trashDays, + externalDomain: externalDomain ?? this.externalDomain, ); } @override - String toString() { - return 'ServerConfig(trashDays: $trashDays)'; - } + String toString() => + 'ServerConfig(trashDays: $trashDays, externalDomain: $externalDomain)'; - ServerConfig.fromDto(ServerConfigDto dto) : trashDays = dto.trashDays; + ServerConfig.fromDto(ServerConfigDto dto) + : trashDays = dto.trashDays, + externalDomain = dto.externalDomain; @override bool operator ==(Object other) { if (identical(this, other)) return true; - return other is ServerConfig && other.trashDays == trashDays; + return other is ServerConfig && + other.trashDays == trashDays && + other.externalDomain == externalDomain; } @override - int get hashCode { - return trashDays.hashCode; - } + int get hashCode => trashDays.hashCode ^ externalDomain.hashCode; } diff --git a/mobile/lib/shared/providers/server_info.provider.dart b/mobile/lib/shared/providers/server_info.provider.dart index b7389824b..0f2d9d1c4 100644 --- a/mobile/lib/shared/providers/server_info.provider.dart +++ b/mobile/lib/shared/providers/server_info.provider.dart @@ -29,6 +29,7 @@ class ServerInfoNotifier extends StateNotifier { ), serverConfig: const ServerConfig( trashDays: 30, + externalDomain: '', ), serverDiskInfo: const ServerDiskInfo( diskAvailable: "0", @@ -74,7 +75,8 @@ class ServerInfoNotifier extends StateNotifier { if (appVersion["major"]! > serverVersion.major) { state = state.copyWith( isVersionMismatch: true, - versionMismatchErrorMessage: "profile_drawer_server_out_of_date_major".tr(), + versionMismatchErrorMessage: + "profile_drawer_server_out_of_date_major".tr(), ); return; } @@ -82,7 +84,8 @@ class ServerInfoNotifier extends StateNotifier { if (appVersion["major"]! < serverVersion.major) { state = state.copyWith( isVersionMismatch: true, - versionMismatchErrorMessage: "profile_drawer_client_out_of_date_major".tr(), + versionMismatchErrorMessage: + "profile_drawer_client_out_of_date_major".tr(), ); return; } @@ -90,7 +93,8 @@ class ServerInfoNotifier extends StateNotifier { if (appVersion["minor"]! > serverVersion.minor) { state = state.copyWith( isVersionMismatch: true, - versionMismatchErrorMessage: "profile_drawer_server_out_of_date_minor".tr(), + versionMismatchErrorMessage: + "profile_drawer_server_out_of_date_minor".tr(), ); return; } @@ -98,7 +102,8 @@ class ServerInfoNotifier extends StateNotifier { if (appVersion["minor"]! < serverVersion.minor) { state = state.copyWith( isVersionMismatch: true, - versionMismatchErrorMessage: "profile_drawer_client_out_of_date_minor".tr(), + versionMismatchErrorMessage: + "profile_drawer_client_out_of_date_minor".tr(), ); return; } diff --git a/mobile/openapi/.openapi-generator/FILES b/mobile/openapi/.openapi-generator/FILES index b8aef8e3d..2eaa4a5bb 100644 --- a/mobile/openapi/.openapi-generator/FILES +++ b/mobile/openapi/.openapi-generator/FILES @@ -149,6 +149,7 @@ doc/SystemConfigNewVersionCheckDto.md doc/SystemConfigOAuthDto.md doc/SystemConfigPasswordLoginDto.md doc/SystemConfigReverseGeocodingDto.md +doc/SystemConfigServerDto.md doc/SystemConfigStorageTemplateDto.md doc/SystemConfigTemplateStorageOptionDto.md doc/SystemConfigThemeDto.md @@ -335,6 +336,7 @@ lib/model/system_config_new_version_check_dto.dart lib/model/system_config_o_auth_dto.dart lib/model/system_config_password_login_dto.dart lib/model/system_config_reverse_geocoding_dto.dart +lib/model/system_config_server_dto.dart lib/model/system_config_storage_template_dto.dart lib/model/system_config_template_storage_option_dto.dart lib/model/system_config_theme_dto.dart @@ -508,6 +510,7 @@ test/system_config_new_version_check_dto_test.dart test/system_config_o_auth_dto_test.dart test/system_config_password_login_dto_test.dart test/system_config_reverse_geocoding_dto_test.dart +test/system_config_server_dto_test.dart test/system_config_storage_template_dto_test.dart test/system_config_template_storage_option_dto_test.dart test/system_config_theme_dto_test.dart diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 9b4e57c9b6dc2539ce37e6cafd92be022daf9d17..068aa8aa2d01e9d701fda6cc5e9f1cc420a47fc3 100644 GIT binary patch delta 37 pcmZ3!hw0vH<`%Q3h@R diff --git a/mobile/openapi/doc/ServerConfigDto.md b/mobile/openapi/doc/ServerConfigDto.md index fd543257be6e14b0cd1ed827f00358d24a1ae664..b2406daf7417e23dcb63db7c6341cc65d2a1e2fa 100644 GIT binary patch delta 29 kcmeBW*~~KG7GG*bNorAEVvb9GZenKMWL`#zjkk;$0jwYj^Z)<= delta 10 RcmdnY(#ta8)+R<{MgSIs1DXH; diff --git a/mobile/openapi/doc/SystemConfigDto.md b/mobile/openapi/doc/SystemConfigDto.md index 403260659a6c141448b532536523f378bbfcbf4a..51bf203ff78e322a0fa6eae173db0c830d98327f 100644 GIT binary patch delta 38 mcmeyuy_aW$01InsQCVuy#0Mg5!C=;8M-~x?*yjJtUl{=tbq;s{ delta 12 TcmdnX^M!kZ0L$h`mM4q=AQuFJ diff --git a/mobile/openapi/doc/SystemConfigServerDto.md b/mobile/openapi/doc/SystemConfigServerDto.md new file mode 100644 index 0000000000000000000000000000000000000000..f749fb587d2c3b8f5de9a16826c02af75827d5b0 GIT binary patch literal 425 zcma)2!Ab)$5WUY=4D6vcknLSh)$KvBrMTWoVM8-rgPTmqj$*-&H(3i-FPckuGw;2b zc@=V`V4|x%TN>*2Gz$4U4oBCwl-?*c!T?o;4{R*TGvP$UXbvx?C+ND)nk3+S;~Ue(@KmJhFm+orIgpOG)Mq9nB_FEPg@KQ}QmZ}I^~k;(mxHKM^KMVWc&3TU!yOjpo^6l`tL zL^hvhVrP^>)u@+|Se)UUpOUJnpgs8v^CmPcwkoK~brdGov0PF^6;zK^uvI9@$Sl@F l72oX0YQ==^jf?DxLa55rH5GL>7qS0g6F?K!nVi8F2LPqEWJUl0 delta 45 zcmV+|0Mh@4AjTcAj{%cM1`U(G0dJE>0?(601Nf6&1gw)O1=6!H1}g%yr3mu|ldca~ Djg1jN diff --git a/mobile/openapi/lib/model/system_config_dto.dart b/mobile/openapi/lib/model/system_config_dto.dart index e7214c201a0f83b5551e89640924dde3a185b0ef..0d64d6bc3bed69e418e911ed3c0c0e0593281939 100644 GIT binary patch delta 172 zcmZ2)yv%gNW+v9uqO#PY$p@K4Hg973%g7Jrx|HNA6oXlt=dv_1K?D_SZDE3Z>>n8+ z8aKb;=wjprE45XDYANQ5V-f`msmChVDwJeo7VAN!4)U$?~ard zsZm}w1Bor_?s)e+&mB3Lj3yI!^JTes`TP88{`t*vK7-45ALk*=7I3v#!l%XT-Q}Mr zXhxQ=H%#03RrK?#5j~2nRGKH5(n%%?eh!ta4BsTY;5#mD*gT6}sk9yRV8xytOj1@h z(fsd9XmposhyP2a@!N83Ft|3u?x~W-GHDaB!3{+wxOVPlaI->4ZjwUDEt=V!$>f)x zk~C-9j7GSf1(ks+xn!k?@b6+YN(*KTTpx_(ySGYaVtdW?H?C(^!R-iOf+&3Ew$jo7 z1IhQW8Z=&maEYcoLa#xSfZ_o(D1gm{iTFPVfMmr??U;}uS{%obU_;BiY?5zu~N^XB_^{|m%J)t{cabiLUR zdE!n_WhSJBEw_*}lfP9Pj=8vat*S^&lBe7lrVk_rQ zowHc!aetrj8l48%_fFcW&3V$ok;L%dRR~OOz_+CVVGVp^Ma2zdTJ7q9E4PHg&Cp;> z@?K%->kwpBpr}YedtscOJEWDGXV_e>FLjlo>`X#}Ot4L@NE7VnTy2O%zcAbjzqv#l zq&37o!3JN^%+wuN2>L)W5@t1emD*^f@|*9V`DJ%of@5 zQ1UwdG$77HxJT*P3H2_0a9{d5y8ktqglJ1hhRyyIW| zKqJ!md!14SkMNAHZF^zu_(H*jp6HlrNi|cbfNK8buwIFuH z6}Fvx*VAFEI4_vqK*0SvLYcra=dC8%iy6w#;Q7pu^$O*3!vPOHgWFPnFnE8xxS~s5 eEe3ZIwzs+uBI9N!rag=RBSi$z diff --git a/mobile/openapi/test/system_config_dto_test.dart b/mobile/openapi/test/system_config_dto_test.dart index 5398f77601a161d283e51abaeeb9e430be1f2a2b..5f41549870eeb1645a88f86fc426f2884c2f77cb 100644 GIT binary patch delta 43 qcmaDSa8Gc{l28BuWI= diff --git a/mobile/openapi/test/system_config_server_dto_test.dart b/mobile/openapi/test/system_config_server_dto_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..4d5dcae2b646500041b7d3d3e7ceedb86c2f7b55 GIT binary patch literal 599 zcmZut%}&BV5We>*#uJbrRZa#%BC*AgC{3guJT03tEgRe2I=e+1!@E0M_|s2e8;KN>|$*RtYTOXA-lSr#YTPtT0D zPHHFAo()y5kUF~UH7k`yD@vmahPi{(;_gOd2T3*v&KY4)%j{hiI5RbBS*wuW#BV*A z|ARQRF7)LB@TPvYz$bxiYaLb(M(3*7A{~(;XM&?qd#I$+!x4N308Gi1mL;felMG?e z&m4~C0pklYO+6fVn||D248PKy_|}oq1>hH7l5Ha6y;%pkq%35*2HXLmCf=G{3%kGY b-C0xGR%cykPv_ui5+$9~$!FuP`%l3Sb8^S0 literal 0 HcmV?d00001 diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index 9d23c8e55..4b2d620ea 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -8593,6 +8593,9 @@ }, "ServerConfigDto": { "properties": { + "externalDomain": { + "type": "string" + }, "isInitialized": { "type": "boolean" }, @@ -8610,7 +8613,8 @@ "trashDays", "oauthButtonText", "loginPageMessage", - "isInitialized" + "isInitialized", + "externalDomain" ], "type": "object" }, @@ -9039,6 +9043,9 @@ "reverseGeocoding": { "$ref": "#/components/schemas/SystemConfigReverseGeocodingDto" }, + "server": { + "$ref": "#/components/schemas/SystemConfigServerDto" + }, "storageTemplate": { "$ref": "#/components/schemas/SystemConfigStorageTemplateDto" }, @@ -9066,7 +9073,8 @@ "thumbnail", "trash", "theme", - "library" + "library", + "server" ], "type": "object" }, @@ -9359,6 +9367,17 @@ ], "type": "object" }, + "SystemConfigServerDto": { + "properties": { + "externalDomain": { + "type": "string" + } + }, + "required": [ + "externalDomain" + ], + "type": "object" + }, "SystemConfigStorageTemplateDto": { "properties": { "enabled": { diff --git a/server/src/domain/server-info/server-info.dto.ts b/server/src/domain/server-info/server-info.dto.ts index e8c68d559..32ac51aa4 100644 --- a/server/src/domain/server-info/server-info.dto.ts +++ b/server/src/domain/server-info/server-info.dto.ts @@ -86,6 +86,7 @@ export class ServerConfigDto { @ApiProperty({ type: 'integer' }) trashDays!: number; isInitialized!: boolean; + externalDomain!: string; } export class ServerFeaturesDto implements FeatureFlags { diff --git a/server/src/domain/server-info/server-info.service.spec.ts b/server/src/domain/server-info/server-info.service.spec.ts index f40c16e82..d093399c7 100644 --- a/server/src/domain/server-info/server-info.service.spec.ts +++ b/server/src/domain/server-info/server-info.service.spec.ts @@ -184,6 +184,7 @@ describe(ServerInfoService.name, () => { loginPageMessage: '', oauthButtonText: 'Login with OAuth', trashDays: 30, + externalDomain: '', }); expect(configMock.load).toHaveBeenCalled(); }); diff --git a/server/src/domain/server-info/server-info.service.ts b/server/src/domain/server-info/server-info.service.ts index 5a1bed567..b13e3c9fa 100644 --- a/server/src/domain/server-info/server-info.service.ts +++ b/server/src/domain/server-info/server-info.service.ts @@ -89,6 +89,7 @@ export class ServerInfoService { trashDays: config.trash.days, oauthButtonText: config.oauth.buttonText, isInitialized, + externalDomain: config.server.externalDomain, }; } diff --git a/server/src/domain/system-config/dto/system-config-server.dto.ts b/server/src/domain/system-config/dto/system-config-server.dto.ts new file mode 100644 index 000000000..0b5cb3550 --- /dev/null +++ b/server/src/domain/system-config/dto/system-config-server.dto.ts @@ -0,0 +1,6 @@ +import { IsString } from 'class-validator'; + +export class SystemConfigServerDto { + @IsString() + externalDomain!: string; +} diff --git a/server/src/domain/system-config/dto/system-config.dto.ts b/server/src/domain/system-config/dto/system-config.dto.ts index 6fbfeced2..122d78ca6 100644 --- a/server/src/domain/system-config/dto/system-config.dto.ts +++ b/server/src/domain/system-config/dto/system-config.dto.ts @@ -11,6 +11,7 @@ import { SystemConfigNewVersionCheckDto } from './system-config-new-version-chec import { SystemConfigOAuthDto } from './system-config-oauth.dto'; import { SystemConfigPasswordLoginDto } from './system-config-password-login.dto'; import { SystemConfigReverseGeocodingDto } from './system-config-reverse-geocoding.dto'; +import { SystemConfigServerDto } from './system-config-server.dto'; import { SystemConfigStorageTemplateDto } from './system-config-storage-template.dto'; import { SystemConfigThemeDto } from './system-config-theme.dto'; import { SystemConfigThumbnailDto } from './system-config-thumbnail.dto'; @@ -86,6 +87,11 @@ export class SystemConfigDto implements SystemConfig { @ValidateNested() @IsObject() library!: SystemConfigLibraryDto; + + @Type(() => SystemConfigServerDto) + @ValidateNested() + @IsObject() + server!: SystemConfigServerDto; } export function mapConfig(config: SystemConfig): SystemConfigDto { diff --git a/server/src/domain/system-config/system-config.core.ts b/server/src/domain/system-config/system-config.core.ts index 5717e0308..8aaab1c9e 100644 --- a/server/src/domain/system-config/system-config.core.ts +++ b/server/src/domain/system-config/system-config.core.ts @@ -127,6 +127,9 @@ export const defaults = Object.freeze({ cronExpression: CronExpression.EVERY_DAY_AT_MIDNIGHT, }, }, + server: { + externalDomain: '', + }, }); export enum FeatureFlag { diff --git a/server/src/domain/system-config/system-config.service.spec.ts b/server/src/domain/system-config/system-config.service.spec.ts index 986247786..1f71a0beb 100644 --- a/server/src/domain/system-config/system-config.service.spec.ts +++ b/server/src/domain/system-config/system-config.service.spec.ts @@ -100,6 +100,9 @@ const updatedConfig = Object.freeze({ passwordLogin: { enabled: true, }, + server: { + externalDomain: '', + }, storageTemplate: { enabled: false, hashVerificationEnabled: true, diff --git a/server/src/infra/entities/system-config.entity.ts b/server/src/infra/entities/system-config.entity.ts index 5c10c027f..9033ec387 100644 --- a/server/src/infra/entities/system-config.entity.ts +++ b/server/src/infra/entities/system-config.entity.ts @@ -84,6 +84,8 @@ export enum SystemConfigKey { PASSWORD_LOGIN_ENABLED = 'passwordLogin.enabled', + SERVER_EXTERNAL_DOMAIN = 'server.externalDomain', + STORAGE_TEMPLATE_ENABLED = 'storageTemplate.enabled', STORAGE_TEMPLATE_HASH_VERIFICATION_ENABLED = 'storageTemplate.hashVerificationEnabled', STORAGE_TEMPLATE = 'storageTemplate.template', @@ -244,4 +246,7 @@ export interface SystemConfig { cronExpression: string; }; }; + server: { + externalDomain: string; + }; } diff --git a/server/test/e2e/server-info.e2e-spec.ts b/server/test/e2e/server-info.e2e-spec.ts index f2839aa50..d2d54d079 100644 --- a/server/test/e2e/server-info.e2e-spec.ts +++ b/server/test/e2e/server-info.e2e-spec.ts @@ -97,6 +97,7 @@ describe(`${ServerInfoController.name} (e2e)`, () => { oauthButtonText: 'Login with OAuth', trashDays: 30, isInitialized: true, + externalDomain: '', }); }); }); diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index 05cac4bcf..3c2980ef8 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -3007,6 +3007,12 @@ export interface SearchResponseDto { * @interface ServerConfigDto */ export interface ServerConfigDto { + /** + * + * @type {string} + * @memberof ServerConfigDto + */ + 'externalDomain': string; /** * * @type {boolean} @@ -3590,6 +3596,12 @@ export interface SystemConfigDto { * @memberof SystemConfigDto */ 'reverseGeocoding': SystemConfigReverseGeocodingDto; + /** + * + * @type {SystemConfigServerDto} + * @memberof SystemConfigDto + */ + 'server': SystemConfigServerDto; /** * * @type {SystemConfigStorageTemplateDto} @@ -4014,6 +4026,19 @@ export interface SystemConfigReverseGeocodingDto { */ 'enabled': boolean; } +/** + * + * @export + * @interface SystemConfigServerDto + */ +export interface SystemConfigServerDto { + /** + * + * @type {string} + * @memberof SystemConfigServerDto + */ + 'externalDomain': string; +} /** * * @export diff --git a/web/src/api/utils.ts b/web/src/api/utils.ts index 8dd0fb4ca..f2f8f50cc 100644 --- a/web/src/api/utils.ts +++ b/web/src/api/utils.ts @@ -19,6 +19,10 @@ export const copyToClipboard = async (secret: string) => { } }; +export const makeSharedLinkUrl = (externalDomain: string, key: string) => { + return `${externalDomain || window.location.origin}/share/${key}`; +}; + export const oauth = { isCallback: (location: Location) => { const search = location.search; diff --git a/web/src/lib/components/admin-page/settings/server/server-settings.svelte b/web/src/lib/components/admin-page/settings/server/server-settings.svelte new file mode 100644 index 000000000..16c1eabfc --- /dev/null +++ b/web/src/lib/components/admin-page/settings/server/server-settings.svelte @@ -0,0 +1,107 @@ + + +
+ {#await getConfigs() then} +
+
+
+ +
+ handleReset(detail)} + on:save={saveSetting} + showResetToDefault={!isEqual(savedConfig, defaultConfig)} + {disabled} + /> +
+
+
+
+ {/await} +
diff --git a/web/src/lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte b/web/src/lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte index cec2686c7..483ed9bd2 100644 --- a/web/src/lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte +++ b/web/src/lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte @@ -5,7 +5,7 @@ import SettingSwitch from '$lib/components/admin-page/settings/setting-switch.svelte'; import Button from '$lib/components/elements/buttons/button.svelte'; import { handleError } from '$lib/utils/handle-error'; - import { api, copyToClipboard, SharedLinkResponseDto, SharedLinkType } from '@api'; + import { api, copyToClipboard, makeSharedLinkUrl, SharedLinkResponseDto, SharedLinkType } from '@api'; import { createEventDispatcher, onMount } from 'svelte'; import Icon from '$lib/components/elements/icon.svelte'; import BaseModal from '../base-modal.svelte'; @@ -13,6 +13,7 @@ import DropdownButton from '../dropdown-button.svelte'; import { notificationController, NotificationType } from '../notification/notification'; import { mdiLink } from '@mdi/js'; + import { serverConfig } from '$lib/stores/server-config.store'; export let albumId: string | undefined = undefined; export let assetIds: string[] = []; @@ -82,7 +83,7 @@ showMetadata, }, }); - sharedLink = `${window.location.origin}/share/${data.key}`; + sharedLink = makeSharedLinkUrl($serverConfig.externalDomain, data.key); } catch (e) { handleError(e, 'Failed to create shared link'); } @@ -182,7 +183,7 @@ {:else}
Individual shared | {editingLink.description}{editingLink.description || ''}
{/if} diff --git a/web/src/lib/stores/server-config.store.ts b/web/src/lib/stores/server-config.store.ts index 8843aaee2..73798631a 100644 --- a/web/src/lib/stores/server-config.store.ts +++ b/web/src/lib/stores/server-config.store.ts @@ -26,6 +26,7 @@ export const serverConfig = writable({ loginPageMessage: '', trashDays: 30, isInitialized: false, + externalDomain: '', }); export const loadConfig = async () => { diff --git a/web/src/routes/(user)/sharing/sharedlinks/+page.svelte b/web/src/routes/(user)/sharing/sharedlinks/+page.svelte index 6fd9a7afa..cb7b0d5fa 100644 --- a/web/src/routes/(user)/sharing/sharedlinks/+page.svelte +++ b/web/src/routes/(user)/sharing/sharedlinks/+page.svelte @@ -1,6 +1,6 @@ diff --git a/web/src/routes/admin/system-settings/+page.svelte b/web/src/routes/admin/system-settings/+page.svelte index 2a6fce5ae..cabe88183 100644 --- a/web/src/routes/admin/system-settings/+page.svelte +++ b/web/src/routes/admin/system-settings/+page.svelte @@ -9,6 +9,7 @@ import SettingAccordion from '$lib/components/admin-page/settings/setting-accordion.svelte'; import StorageTemplateSettings from '$lib/components/admin-page/settings/storage-template/storage-template-settings.svelte'; import ThumbnailSettings from '$lib/components/admin-page/settings/thumbnail/thumbnail-settings.svelte'; + import ServerSettings from '$lib/components/admin-page/settings/server/server-settings.svelte'; import TrashSettings from '$lib/components/admin-page/settings/trash-settings/trash-settings.svelte'; import ThemeSettings from '$lib/components/admin-page/settings/theme/theme-settings.svelte'; import LinkButton from '$lib/components/elements/buttons/link-button.svelte'; @@ -95,6 +96,10 @@ + + + +