From f1db25762878d05a36d497c7d7dc742b3baeeef5 Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Fri, 8 Sep 2023 22:51:46 -0400 Subject: [PATCH] feat(server,web): server config (#4006) * feat: server config * chore: open api * fix: redirect /map to /photos when disabled --- cli/src/api/open-api/api.ts | 112 ++++++++++++++ mobile/openapi/.openapi-generator/FILES | 6 + mobile/openapi/README.md | Bin 19212 -> 19422 bytes mobile/openapi/doc/ServerConfigDto.md | Bin 0 -> 494 bytes mobile/openapi/doc/ServerFeaturesDto.md | Bin 672 -> 697 bytes mobile/openapi/doc/ServerInfoApi.md | Bin 7493 -> 8348 bytes mobile/openapi/doc/SystemConfigDto.md | Bin 964 -> 1028 bytes mobile/openapi/doc/SystemConfigMapDto.md | Bin 0 -> 444 bytes mobile/openapi/lib/api.dart | Bin 6424 -> 6502 bytes mobile/openapi/lib/api/server_info_api.dart | Bin 8834 -> 10218 bytes mobile/openapi/lib/api_client.dart | Bin 19783 -> 19953 bytes .../openapi/lib/model/server_config_dto.dart | Bin 0 -> 3496 bytes .../lib/model/server_features_dto.dart | Bin 5008 -> 5192 bytes .../openapi/lib/model/system_config_dto.dart | Bin 4613 -> 4818 bytes .../lib/model/system_config_map_dto.dart | Bin 0 -> 3070 bytes .../openapi/test/server_config_dto_test.dart | Bin 0 -> 805 bytes .../test/server_features_dto_test.dart | Bin 1403 -> 1492 bytes mobile/openapi/test/server_info_api_test.dart | Bin 1224 -> 1345 bytes .../openapi/test/system_config_dto_test.dart | Bin 1324 -> 1427 bytes .../test/system_config_map_dto_test.dart | Bin 0 -> 673 bytes server/immich-openapi-specs.json | 73 +++++++++- .../src/domain/server-info/server-info.dto.ts | 21 ++- .../server-info/server-info.service.spec.ts | 40 +++-- .../domain/server-info/server-info.service.ts | 14 ++ .../dto/system-config-map.dto.ts | 9 ++ .../system-config/dto/system-config.dto.ts | 6 + .../system-config/system-config.core.ts | 7 +- .../system-config.service.spec.ts | 4 + .../controllers/server-info.controller.ts | 7 + .../infra/entities/system-config.entity.ts | 7 + server/test/e2e/server-info.e2e-spec.ts | 13 ++ web/src/api/open-api/api.ts | 112 ++++++++++++++ .../admin-page/jobs/jobs-panel.svelte | 2 +- .../settings/map-settings/map-settings.svelte | 98 +++++++++++++ .../asset-viewer/detail-panel.svelte | 11 +- .../lib/components/forms/login-form.svelte | 4 +- .../leaflet/tile-layer.svelte | 4 +- .../navigation-bar/navigation-bar.svelte | 2 +- .../side-bar/side-bar.svelte | 10 +- .../user-settings-page/oauth-settings.svelte | 2 +- .../user-settings-list.svelte | 2 +- web/src/lib/constants.ts | 3 - web/src/lib/stores/feature-flags.store.ts | 22 --- web/src/lib/stores/server-config.store.ts | 37 +++++ web/src/routes/(user)/map/+page.svelte | 137 +++++++++--------- web/src/routes/+layout.svelte | 6 +- .../routes/admin/system-settings/+page.svelte | 40 ++--- web/src/routes/auth/login/+page.svelte | 7 +- 48 files changed, 658 insertions(+), 160 deletions(-) create mode 100644 mobile/openapi/doc/ServerConfigDto.md create mode 100644 mobile/openapi/doc/SystemConfigMapDto.md create mode 100644 mobile/openapi/lib/model/server_config_dto.dart create mode 100644 mobile/openapi/lib/model/system_config_map_dto.dart create mode 100644 mobile/openapi/test/server_config_dto_test.dart create mode 100644 mobile/openapi/test/system_config_map_dto_test.dart create mode 100644 server/src/domain/system-config/dto/system-config-map.dto.ts create mode 100644 web/src/lib/components/admin-page/settings/map-settings/map-settings.svelte delete mode 100644 web/src/lib/stores/feature-flags.store.ts create mode 100644 web/src/lib/stores/server-config.store.ts diff --git a/cli/src/api/open-api/api.ts b/cli/src/api/open-api/api.ts index 2b2a31b711..8aa65b66f1 100644 --- a/cli/src/api/open-api/api.ts +++ b/cli/src/api/open-api/api.ts @@ -2343,6 +2343,31 @@ export interface SearchResponseDto { */ 'assets': SearchAssetResponseDto; } +/** + * + * @export + * @interface ServerConfigDto + */ +export interface ServerConfigDto { + /** + * + * @type {string} + * @memberof ServerConfigDto + */ + 'loginPageMessage': string; + /** + * + * @type {string} + * @memberof ServerConfigDto + */ + 'mapTileUrl': string; + /** + * + * @type {string} + * @memberof ServerConfigDto + */ + 'oauthButtonText': string; +} /** * * @export @@ -2367,6 +2392,12 @@ export interface ServerFeaturesDto { * @memberof ServerFeaturesDto */ 'facialRecognition': boolean; + /** + * + * @type {boolean} + * @memberof ServerFeaturesDto + */ + 'map': boolean; /** * * @type {boolean} @@ -2810,6 +2841,12 @@ export interface SystemConfigDto { * @memberof SystemConfigDto */ 'machineLearning': SystemConfigMachineLearningDto; + /** + * + * @type {SystemConfigMapDto} + * @memberof SystemConfigDto + */ + 'map': SystemConfigMapDto; /** * * @type {SystemConfigOAuthDto} @@ -3050,6 +3087,25 @@ export interface SystemConfigMachineLearningDto { */ 'url': string; } +/** + * + * @export + * @interface SystemConfigMapDto + */ +export interface SystemConfigMapDto { + /** + * + * @type {boolean} + * @memberof SystemConfigMapDto + */ + 'enabled': boolean; + /** + * + * @type {string} + * @memberof SystemConfigMapDto + */ + 'tileUrl': string; +} /** * * @export @@ -10825,6 +10881,35 @@ export class SearchApi extends BaseAPI { */ export const ServerInfoApiAxiosParamCreator = function (configuration?: Configuration) { return { + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getServerConfig: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/server-info/config`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * * @param {*} [options] Override http request option. @@ -11027,6 +11112,15 @@ export const ServerInfoApiAxiosParamCreator = function (configuration?: Configur export const ServerInfoApiFp = function(configuration?: Configuration) { const localVarAxiosParamCreator = ServerInfoApiAxiosParamCreator(configuration) return { + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getServerConfig(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getServerConfig(options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @param {*} [options] Override http request option. @@ -11091,6 +11185,14 @@ export const ServerInfoApiFp = function(configuration?: Configuration) { export const ServerInfoApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { const localVarFp = ServerInfoApiFp(configuration) return { + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getServerConfig(options?: AxiosRequestConfig): AxiosPromise { + return localVarFp.getServerConfig(options).then((request) => request(axios, basePath)); + }, /** * * @param {*} [options] Override http request option. @@ -11149,6 +11251,16 @@ export const ServerInfoApiFactory = function (configuration?: Configuration, bas * @extends {BaseAPI} */ export class ServerInfoApi extends BaseAPI { + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof ServerInfoApi + */ + public getServerConfig(options?: AxiosRequestConfig) { + return ServerInfoApiFp(this.configuration).getServerConfig(options).then((request) => request(this.axios, this.basePath)); + } + /** * * @param {*} [options] Override http request option. diff --git a/mobile/openapi/.openapi-generator/FILES b/mobile/openapi/.openapi-generator/FILES index 5cb7fa6515..40490a32de 100644 --- a/mobile/openapi/.openapi-generator/FILES +++ b/mobile/openapi/.openapi-generator/FILES @@ -97,6 +97,7 @@ doc/SearchExploreResponseDto.md doc/SearchFacetCountResponseDto.md doc/SearchFacetResponseDto.md doc/SearchResponseDto.md +doc/ServerConfigDto.md doc/ServerFeaturesDto.md doc/ServerInfoApi.md doc/ServerInfoResponseDto.md @@ -116,6 +117,7 @@ doc/SystemConfigDto.md doc/SystemConfigFFmpegDto.md doc/SystemConfigJobDto.md doc/SystemConfigMachineLearningDto.md +doc/SystemConfigMapDto.md doc/SystemConfigOAuthDto.md doc/SystemConfigPasswordLoginDto.md doc/SystemConfigStorageTemplateDto.md @@ -249,6 +251,7 @@ lib/model/search_explore_response_dto.dart lib/model/search_facet_count_response_dto.dart lib/model/search_facet_response_dto.dart lib/model/search_response_dto.dart +lib/model/server_config_dto.dart lib/model/server_features_dto.dart lib/model/server_info_response_dto.dart lib/model/server_media_types_response_dto.dart @@ -265,6 +268,7 @@ lib/model/system_config_dto.dart lib/model/system_config_f_fmpeg_dto.dart lib/model/system_config_job_dto.dart lib/model/system_config_machine_learning_dto.dart +lib/model/system_config_map_dto.dart lib/model/system_config_o_auth_dto.dart lib/model/system_config_password_login_dto.dart lib/model/system_config_storage_template_dto.dart @@ -382,6 +386,7 @@ test/search_explore_response_dto_test.dart test/search_facet_count_response_dto_test.dart test/search_facet_response_dto_test.dart test/search_response_dto_test.dart +test/server_config_dto_test.dart test/server_features_dto_test.dart test/server_info_api_test.dart test/server_info_response_dto_test.dart @@ -401,6 +406,7 @@ test/system_config_dto_test.dart test/system_config_f_fmpeg_dto_test.dart test/system_config_job_dto_test.dart test/system_config_machine_learning_dto_test.dart +test/system_config_map_dto_test.dart test/system_config_o_auth_dto_test.dart test/system_config_password_login_dto_test.dart test/system_config_storage_template_dto_test.dart diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 52e6801752e519450d4a1b46cf34071284d0560b..096cc971582859aca3ca0702c02195f7e2818c16 100644 GIT binary patch delta 107 zcmeB~#&~Z!;|3#THs}1jw9NF$^(u1QsYPX}MadBULKR6i2y?T5@+&zRh%%Rw{8){Y p{A7LoV6b9{=;jxWUChkBi3O7x-NeB30vCzNy{;Uat6V3s0su^vChh`+Ngmf9mKx7+bfxq$g)#p+{JOty(pi63lln8loQu9Jd`Ilxw4A>jj zq=(iJV=6+VAtAODjO!mHF*F4 delta 16 XcmbQ^c+_gblg+k_{7jp@*n*@0I{F2| diff --git a/mobile/openapi/doc/SystemConfigDto.md b/mobile/openapi/doc/SystemConfigDto.md index 6fc9808528d8d90648e7729f451c5d2f8cc66c71..40d528b4014c4bb3025c6f9a885975c64bb56f0f 100644 GIT binary patch delta 45 zcmX@Y-omlrI3sgzV!^}*B9gv|1uiA|T3WFh!Ii}&skzSid1;yHK+(c*+NJekzmI$8 zy8y@bg>`` zBW3bRc_8Y?XF^G2+v97zFj8(gTHk%iVz;|bGXfLTc`wl0f%p4qS2) pcZZ&J{ZAH?aUHAUR2dcXN)&8WsR`ybsF& delta 17 ZcmaE6G{b1aKEch81=lfc-X*ez1prL^2kHO- diff --git a/mobile/openapi/lib/api/server_info_api.dart b/mobile/openapi/lib/api/server_info_api.dart index 3b7899cc264fde989628cfd36cd903aa35d92bfc..51bcbfa4b010c793c48dad96d03bab8200cfea35 100644 GIT binary patch delta 132 zcmZp2edWL5DkEEReqLH;`eX+od+yYtveY7H2>-8m0#s`AM5f)0(hy;nl6-qRh4j>t lU}Uv{Qd-D*H*wi8$-vdC>u4$@7Av4=_{ZkA`3mDY1pxCLF-rgd delta 12 TcmaFm-{iXCD&uBb_O%KCCq)HI diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index 38ffa7f1bd4f7e9cbec9329b8cea70a61e5f6f31..063e1cb27dc28ee1f80c17d794e7207cc83bd662 100644 GIT binary patch delta 68 zcmX>;i}B-Z#tq!&tj_s)X_=EZT1#@L7L}zIfw}3E6Qz_lvznh}W%5m&ywOT>@_QR@ UX5YjDkoe>U);yap*^~+Z0C9^M2LJ#7 delta 19 bcmex3oALN8#tq!&n`6z7vu>`jEfoL&R_q7n diff --git a/mobile/openapi/lib/model/server_config_dto.dart b/mobile/openapi/lib/model/server_config_dto.dart new file mode 100644 index 0000000000000000000000000000000000000000..75fe4b2bf7ec75558e520cdc71c34a9444e03dc2 GIT binary patch literal 3496 zcmbVOZExE)5dQ98aRGu_!Blzcry-fVc1<$0YvQ4G2Mk6aFfwJalSP-L>KUp3`|e1+ znev)!25d{@-SO_8=Z-g-j3yI!^>IFX{_EB4)%#cTt4p|k^X@8y%Ng9x=5RB+d~^Nh z35t>A%Oz7Lei8lfVnnZERZ7K^OsXUk1wV(XtTbOHyx?nIny`5n>y1?As0J&xY^Re= zWfH|dS3;qB$=3M0VG92ZR~n5geOx`2r8cZIiCE%+A`@I$b$#@(LX=!5g)CPnW^z_0 zKYpL2Ia7Kx!s86c4CIEFY$GE4UW`U*!L)`CTz%o{wJbBSx-=3VMgS9x|2;RADm5@D z`4$$Q*c}M#T*@Pq_cjBv0no$@CKoy?rQJp_msO$$CqJ zctvSo#SP?4=db0GV}#b%wjQVqN}dvgYDY9iX69TAZN&l~fv7Qb2H!b}&fDRh=Upc- z@UsToNcRv}D8?QQ7rr;~slO8TvKOCrz9Ei>WQ$L|g}bqJ>V|E8az2Ex++lHxbP7hk z)v`pVTpeVhWCeV}C5T}l{jk4qbg&W&HTHcR-=W&k67m!~_dAtt%@g**=`_3zr>1N( zupZLFo{H|jGznnk5?rU$AdH4Dtf;t#Ov!a^YGIeKuVg4NroOYl6@pmXlyBY#fp{DU$z%jUZcuWe5Q;!+4lsB(7V z8YkK7pnud(Cpzw1@Tl;p8Be}v3v)m&?d@+(Cc?EPQ;p6$K^Z)}k)}}K>NPUu04A4% z2iu`nN^aSGf$a)1R%jmX8=M?UitDN{w)#BVi#z8T;Y=0@W^w{Mu4Q{9^sO{k>y6np zCJ}nP3xe9R+~Q4WMZ5im9F91#4|qhZx3Qqxb!I3#9oJ4`%lf`Wx=tyGh%J|nkv&b9 z*CxLQS+2E=q^N=>QRmP+Su3`lYY33!Er$n4AW@D2(Rk{Tf>QlX=UZtyUO(q}>Nitv zv56fd|5j$$k?N`K9NGaZ!5IffIU3pA9d!U7uBu%FU_@_%>n0e�B&ykk(FQ#d@$j z0w|_w;S~SkJ8JS)-|>;o{syY3+LjJRjjz-&ZIYy$P`1M4xJ0Exlb?sdb0>vLbt}d@ z3#DlSCSzT@yy>xl7&Iu(Daq5$QC@`bv;svNea0L=>HqNNiIjK>z3MjIEB;5>gJB&a z?7?$>EndOLaQ0912j}Q}>=e)(fox_o48Yc;c7*|M(dk%PgYCM)I zfWg}jvs;?x>qcPj)OLvfjda`bW%^*VF`QN0^)p6y>Op>7ulRR^o!&1-*m?c|{g`<% literal 0 HcmV?d00001 diff --git a/mobile/openapi/lib/model/server_features_dto.dart b/mobile/openapi/lib/model/server_features_dto.dart index 60827add6c45034a9a22fbf9cdf4eea2037812c1..07a725232f7e949f9d7b4f2e1d9e88ad366ba4a6 100644 GIT binary patch delta 114 zcmbQBenMlzH%8{%#DdAc86|=E1&F=*CQ~*e7m%S~YpVd_o3eC)6>VP2YR$+26tPtS t2|r^iW)uK2)MFKF6-qKPi}gUFUK~!$U=t^D%Y#j;=U%}M7LXS%1OQWVCu;xz delta 45 zcmV+|0Mh@+D3B+x_5qU(0`0R017QKPfdqR2vk?X<0kcF0aRIZ`2r~n-ISiWzliw3* DaUTz2 diff --git a/mobile/openapi/lib/model/system_config_dto.dart b/mobile/openapi/lib/model/system_config_dto.dart index da0500ebf8ddf71da93c76334d1ce91042d1e7fa..2fe46a039a5611ac24741836514ba902e0bb0155 100644 GIT binary patch delta 148 zcmZowxum+`F(Y$sV!`6`jEr2qi3Kht`3gYp=6y`9j9fs5f~_r>Z^B{^RyR43^*skr z+*Spo%8$*GQ2@wLk5#Z$D9Oky)&q%Fu>WDw1~RM^f-8$lQgfa2^U^ZYeZhw7r4{Ap cdKKsAX=DM3=puEHhBU4qc90_`Zx9Rx0PA!!#sB~S delta 44 zcmV+{0Mq}{C50rg;{lWU0qnB@19Snijs!3PlhXy=v$_U10kaSY?EPeW3BEgEO&BQemp0|vto7>Tmj$)ZQnHH_5%efRPq z%ahV(3y{Pn?~CW0d&$vgFdD&|&-2-f->0|JPjBYaE4Y65aT>$b3~pz0xS3tOyZ-YG z&B*d~&XgIyOn!Meps!+8OU2VdsvjM2dj2$uhY%e zq>BH&6$;&Jw#L63rtrVvN@H-P``t5HYr|@jiX0P)LU3i>b#Jmt)Lf^PtXF7eN>-=8 z{+wndQ+hDKbOvMra>Hx35efcY4hC7pw1&IAHhle7)`eKzu+5c`@Gt-vAq1be*{WIt zgPQMP(Y0KHuq9?ZL6`1+M3Df(;lY$bCs@LL#q+pl%OKF7RYhBebF2{HUdjr*hh4Zc zO4KXxmUij2{K}O=kb*rPQ!yhIjK|UJ{tM6W@TTPI9L5uHU}mg}sG2zJeEa4;f#`+g z`Ey&Yvk6DQ_@Op9HSpz;!DE$*+Q5n%D48zb%A8|uHbo;nQe4e5t~FD8QVfcqG@~0h< zuvKlBt8IpQ)hQ`C#ilnROL0DwGAFj(LQjAFW~;C$d(>TZ0dg!TvbB+_v81pLa6*PP zNiwEQhnC2-VGGNyOd>4N{dmqUV9hpv5KOaTzTfay)WwsCsE^^u?D?6S0p#mh>v%d* z9eo4cM_(+E0vzmLWha)!z2V!V_fgRCI>T-P6>eN3nI|y1S|9BASt+@rk%dAmSfzP< z2sky6omsK^e^uE+4-vt1RK(fX!qg5*lbVi_usnCVX?Z$;1JEaMdoLsDU{QeU0>UR7 z#rB@(qCGD9C_(x3G7^e)(4bX&lJo=$L!}Kq=Ke1BmZY)c=CPK*8RFju7tmbgDc+{| zXrRY|8__2`;;PyQCh-EnYmDn~9%9Cw=_!obP>Ho{@$&;_5iA_%SVQs`-_uOB{!W{e z{yX%dYtu}4aQsRm&f+B9?Ya}D#3kw^dS-N)c;O7u&(BZ19MFMYToms(<8E3P38|^C zV<+T7o4p!|;b~oqG1}^RG}-^r&=ckH6sh^y+L{PIR1%)-l1Y?&XDXaO5~mcUVa+WQbRC!e>c0O2c(}4_C{t{@D$N*TRl!XngJHN^4wTjC!dD44bKmA6`Hf2!XXF z%M$L^1$Qf78<$iL%k~DkaFJ*EVD_@w?Elc#hC=^>8*V<0dq=+$0RTdG B6(#@x delta 12 TcmX@eb%JxlJ;u%9Os|*#BlZO( diff --git a/mobile/openapi/test/system_config_dto_test.dart b/mobile/openapi/test/system_config_dto_test.dart index 4ea0b98bde8bcbcb9e644d364878d387311f1c4e..8951d16ca8b8a10bf560bc24db38d6f20455b2fb 100644 GIT binary patch delta 39 scmZ3(HJN*ZJu{bYVu4FZzCvzd!Q{7$ip(JT4~yDlITqo~_RMb?0Sbi;I{*Lx delta 12 TcmbQty@qRpJ@e*e%x@V19l->V diff --git a/mobile/openapi/test/system_config_map_dto_test.dart b/mobile/openapi/test/system_config_map_dto_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..d0b753dc0cda688eff7a0384304ed986b1d0b659 GIT binary patch literal 673 zcma)(&riZI6vyxTE1oAHL8hDxghb-NkO)fz4xUQc-bQ2B*0$3S!~gDEfs;%0ux@Yp ze1E(xj-xn+>2{OeKF?OO*Xd@KzqC;lD&4>`O&0UF8^Rj$G3VA#hJ*WI)QqB3 z+9E5oWrdJ9f?CxMbA}QtRK9F&`wGPkZ> zo@wPgSDuNS0!1Ov2DxhulR}|mQmc|Mf8dHe+_CJyt&5^_N9Y)yz4H=B+Mwb_#B>ds ztvlmih)3t+=C}e)Xfr#YPXg0BhdTze~237-D@*2+I4F5Lej$Sp*jq8}!k*(3k} literal 0 HcmV?d00001 diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index 2495001daa..ef6bee00f3 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -3342,6 +3342,27 @@ ] } }, + "/server-info/config": { + "get": { + "operationId": "getServerConfig", + "parameters": [], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ServerConfigDto" + } + } + }, + "description": "" + } + }, + "tags": [ + "Server Info" + ] + } + }, "/server-info/features": { "get": { "operationId": "getServerFeatures", @@ -6618,6 +6639,25 @@ ], "type": "object" }, + "ServerConfigDto": { + "properties": { + "loginPageMessage": { + "type": "string" + }, + "mapTileUrl": { + "type": "string" + }, + "oauthButtonText": { + "type": "string" + } + }, + "required": [ + "oauthButtonText", + "loginPageMessage", + "mapTileUrl" + ], + "type": "object" + }, "ServerFeaturesDto": { "properties": { "clipEncode": { @@ -6629,6 +6669,9 @@ "facialRecognition": { "type": "boolean" }, + "map": { + "type": "boolean" + }, "oauth": { "type": "boolean" }, @@ -6649,15 +6692,16 @@ } }, "required": [ - "configFile", "clipEncode", + "configFile", "facialRecognition", - "sidecar", - "search", - "tagImage", + "map", "oauth", "oauthAutoLaunch", - "passwordLogin" + "passwordLogin", + "sidecar", + "search", + "tagImage" ], "type": "object" }, @@ -6989,6 +7033,9 @@ "machineLearning": { "$ref": "#/components/schemas/SystemConfigMachineLearningDto" }, + "map": { + "$ref": "#/components/schemas/SystemConfigMapDto" + }, "oauth": { "$ref": "#/components/schemas/SystemConfigOAuthDto" }, @@ -7005,6 +7052,7 @@ "required": [ "ffmpeg", "machineLearning", + "map", "oauth", "passwordLogin", "storageTemplate", @@ -7162,6 +7210,21 @@ ], "type": "object" }, + "SystemConfigMapDto": { + "properties": { + "enabled": { + "type": "boolean" + }, + "tileUrl": { + "type": "string" + } + }, + "required": [ + "enabled", + "tileUrl" + ], + "type": "object" + }, "SystemConfigOAuthDto": { "properties": { "autoLaunch": { diff --git a/server/src/domain/server-info/server-info.dto.ts b/server/src/domain/server-info/server-info.dto.ts index b9cdd181f3..105fc1bd2d 100644 --- a/server/src/domain/server-info/server-info.dto.ts +++ b/server/src/domain/server-info/server-info.dto.ts @@ -79,16 +79,21 @@ export class ServerMediaTypesResponseDto { sidecar!: string[]; } -export class ServerFeaturesDto implements FeatureFlags { - configFile!: boolean; - clipEncode!: boolean; - facialRecognition!: boolean; - sidecar!: boolean; - search!: boolean; - tagImage!: boolean; +export class ServerConfigDto { + oauthButtonText!: string; + loginPageMessage!: string; + mapTileUrl!: string; +} - // TODO: use these instead of `POST oauth/config` +export class ServerFeaturesDto implements FeatureFlags { + clipEncode!: boolean; + configFile!: boolean; + facialRecognition!: boolean; + map!: boolean; oauth!: boolean; oauthAutoLaunch!: boolean; passwordLogin!: boolean; + sidecar!: boolean; + search!: boolean; + tagImage!: boolean; } 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 6c8d194641..4363e379b6 100644 --- a/server/src/domain/server-info/server-info.service.spec.ts +++ b/server/src/domain/server-info/server-info.service.spec.ts @@ -143,22 +143,34 @@ describe(ServerInfoService.name, () => { it('should respond the server version', () => { expect(sut.getVersion()).toEqual(serverVersion); }); + }); - describe('getFeatures', () => { - it('should respond the server features', async () => { - await expect(sut.getFeatures()).resolves.toEqual({ - clipEncode: true, - facialRecognition: true, - oauth: false, - oauthAutoLaunch: false, - passwordLogin: true, - search: true, - sidecar: true, - tagImage: true, - configFile: false, - }); - expect(configMock.load).toHaveBeenCalled(); + describe('getFeatures', () => { + it('should respond the server features', async () => { + await expect(sut.getFeatures()).resolves.toEqual({ + clipEncode: true, + facialRecognition: true, + map: true, + oauth: false, + oauthAutoLaunch: false, + passwordLogin: true, + search: true, + sidecar: true, + tagImage: true, + configFile: false, }); + expect(configMock.load).toHaveBeenCalled(); + }); + }); + + describe('getConfig', () => { + it('should respond the server configuration', async () => { + await expect(sut.getConfig()).resolves.toEqual({ + loginPageMessage: '', + oauthButtonText: 'Login with OAuth', + mapTileUrl: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', + }); + 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 655b21603e..86dc4b8d06 100644 --- a/server/src/domain/server-info/server-info.service.ts +++ b/server/src/domain/server-info/server-info.service.ts @@ -5,6 +5,7 @@ import { IStorageRepository, StorageCore, StorageFolder } from '../storage'; import { ISystemConfigRepository, SystemConfigCore } from '../system-config'; import { IUserRepository, UserStatsQueryResponse } from '../user'; import { + ServerConfigDto, ServerFeaturesDto, ServerInfoResponseDto, ServerMediaTypesResponseDto, @@ -55,6 +56,19 @@ export class ServerInfoService { return this.configCore.getFeatures(); } + async getConfig(): Promise { + const config = await this.configCore.getConfig(); + + // TODO move to system config + const loginPageMessage = process.env.PUBLIC_LOGIN_PAGE_MESSAGE || ''; + + return { + loginPageMessage, + mapTileUrl: config.map.tileUrl, + oauthButtonText: config.oauth.buttonText, + }; + } + async getStats(): Promise { const userStats: UserStatsQueryResponse[] = await this.userRepository.getUserStats(); const serverStats = new ServerStatsResponseDto(); diff --git a/server/src/domain/system-config/dto/system-config-map.dto.ts b/server/src/domain/system-config/dto/system-config-map.dto.ts new file mode 100644 index 0000000000..479b1486d5 --- /dev/null +++ b/server/src/domain/system-config/dto/system-config-map.dto.ts @@ -0,0 +1,9 @@ +import { IsBoolean, IsString } from 'class-validator'; + +export class SystemConfigMapDto { + @IsBoolean() + enabled!: boolean; + + @IsString() + tileUrl!: 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 c089da3df3..9ec7de6225 100644 --- a/server/src/domain/system-config/dto/system-config.dto.ts +++ b/server/src/domain/system-config/dto/system-config.dto.ts @@ -5,6 +5,7 @@ import { IsObject, ValidateNested } from 'class-validator'; import { SystemConfigFFmpegDto } from './system-config-ffmpeg.dto'; import { SystemConfigJobDto } from './system-config-job.dto'; import { SystemConfigMachineLearningDto } from './system-config-machine-learning.dto'; +import { SystemConfigMapDto } from './system-config-map.dto'; import { SystemConfigOAuthDto } from './system-config-oauth.dto'; import { SystemConfigPasswordLoginDto } from './system-config-password-login.dto'; import { SystemConfigStorageTemplateDto } from './system-config-storage-template.dto'; @@ -20,6 +21,11 @@ export class SystemConfigDto implements SystemConfig { @IsObject() machineLearning!: SystemConfigMachineLearningDto; + @Type(() => SystemConfigMapDto) + @ValidateNested() + @IsObject() + map!: SystemConfigMapDto; + @Type(() => SystemConfigOAuthDto) @ValidateNested() @IsObject() diff --git a/server/src/domain/system-config/system-config.core.ts b/server/src/domain/system-config/system-config.core.ts index cfe64b0a79..e33a168e8d 100644 --- a/server/src/domain/system-config/system-config.core.ts +++ b/server/src/domain/system-config/system-config.core.ts @@ -55,7 +55,6 @@ export const defaults = Object.freeze({ [QueueName.THUMBNAIL_GENERATION]: { concurrency: 5 }, [QueueName.VIDEO_CONVERSION]: { concurrency: 1 }, }, - machineLearning: { enabled: process.env.IMMICH_MACHINE_LEARNING_ENABLED !== 'false', url: process.env.IMMICH_MACHINE_LEARNING_URL || 'http://immich-machine-learning:3003', @@ -75,6 +74,10 @@ export const defaults = Object.freeze({ maxDistance: 0.6, }, }, + map: { + enabled: true, + tileUrl: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', + }, oauth: { enabled: false, issuerUrl: '', @@ -108,6 +111,7 @@ export enum FeatureFlag { CLIP_ENCODE = 'clipEncode', FACIAL_RECOGNITION = 'facialRecognition', TAG_IMAGE = 'tagImage', + MAP = 'map', SIDECAR = 'sidecar', SEARCH = 'search', OAUTH = 'oauth', @@ -169,6 +173,7 @@ export class SystemConfigCore { [FeatureFlag.CLIP_ENCODE]: mlEnabled && config.machineLearning.clip.enabled, [FeatureFlag.FACIAL_RECOGNITION]: mlEnabled && config.machineLearning.facialRecognition.enabled, [FeatureFlag.TAG_IMAGE]: mlEnabled && config.machineLearning.classification.enabled, + [FeatureFlag.MAP]: config.map.enabled, [FeatureFlag.SIDECAR]: true, [FeatureFlag.SEARCH]: process.env.TYPESENSE_ENABLED !== 'false', 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 7e28d041c1..24d5914b03 100644 --- a/server/src/domain/system-config/system-config.service.spec.ts +++ b/server/src/domain/system-config/system-config.service.spec.ts @@ -73,6 +73,10 @@ const updatedConfig = Object.freeze({ maxDistance: 0.6, }, }, + map: { + enabled: true, + tileUrl: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', + }, oauth: { autoLaunch: true, autoRegister: true, diff --git a/server/src/immich/controllers/server-info.controller.ts b/server/src/immich/controllers/server-info.controller.ts index 3b69c35321..b286329ab0 100644 --- a/server/src/immich/controllers/server-info.controller.ts +++ b/server/src/immich/controllers/server-info.controller.ts @@ -1,4 +1,5 @@ import { + ServerConfigDto, ServerFeaturesDto, ServerInfoResponseDto, ServerInfoService, @@ -42,6 +43,12 @@ export class ServerInfoController { return this.service.getFeatures(); } + @PublicRoute() + @Get('config') + getServerConfig(): Promise { + return this.service.getConfig(); + } + @AdminRoute() @Get('stats') getStats(): Promise { diff --git a/server/src/infra/entities/system-config.entity.ts b/server/src/infra/entities/system-config.entity.ts index 2da905d10a..ebcdcac07d 100644 --- a/server/src/infra/entities/system-config.entity.ts +++ b/server/src/infra/entities/system-config.entity.ts @@ -58,6 +58,9 @@ export enum SystemConfigKey { MACHINE_LEARNING_FACIAL_RECOGNITION_MIN_SCORE = 'machineLearning.facialRecognition.minScore', MACHINE_LEARNING_FACIAL_RECOGNITION_MAX_DISTANCE = 'machineLearning.facialRecognition.maxDistance', + MAP_ENABLED = 'map.enabled', + MAP_TILE_URL = 'map.tileUrl', + OAUTH_ENABLED = 'oauth.enabled', OAUTH_ISSUER_URL = 'oauth.issuerUrl', OAUTH_CLIENT_ID = 'oauth.clientId', @@ -164,6 +167,10 @@ export interface SystemConfig { maxDistance: number; }; }; + map: { + enabled: boolean; + tileUrl: string; + }; oauth: { enabled: boolean; issuerUrl: string; diff --git a/server/test/e2e/server-info.e2e-spec.ts b/server/test/e2e/server-info.e2e-spec.ts index d04415dc69..2d344996d4 100644 --- a/server/test/e2e/server-info.e2e-spec.ts +++ b/server/test/e2e/server-info.e2e-spec.ts @@ -83,6 +83,7 @@ describe(`${ServerInfoController.name} (e2e)`, () => { clipEncode: true, configFile: false, facialRecognition: true, + map: true, oauth: false, oauthAutoLaunch: false, passwordLogin: true, @@ -93,6 +94,18 @@ describe(`${ServerInfoController.name} (e2e)`, () => { }); }); + describe('GET /server-info/config', () => { + it('should respond with the server configuration', async () => { + const { status, body } = await request(server).get('/server-info/config'); + expect(status).toBe(200); + expect(body).toEqual({ + loginPageMessage: '', + oauthButtonText: 'Login with OAuth', + mapTileUrl: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', + }); + }); + }); + describe('GET /server-info/stats', () => { it('should require authentication', async () => { const { status, body } = await request(server).get('/server-info/stats'); diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index 2b2a31b711..8aa65b66f1 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -2343,6 +2343,31 @@ export interface SearchResponseDto { */ 'assets': SearchAssetResponseDto; } +/** + * + * @export + * @interface ServerConfigDto + */ +export interface ServerConfigDto { + /** + * + * @type {string} + * @memberof ServerConfigDto + */ + 'loginPageMessage': string; + /** + * + * @type {string} + * @memberof ServerConfigDto + */ + 'mapTileUrl': string; + /** + * + * @type {string} + * @memberof ServerConfigDto + */ + 'oauthButtonText': string; +} /** * * @export @@ -2367,6 +2392,12 @@ export interface ServerFeaturesDto { * @memberof ServerFeaturesDto */ 'facialRecognition': boolean; + /** + * + * @type {boolean} + * @memberof ServerFeaturesDto + */ + 'map': boolean; /** * * @type {boolean} @@ -2810,6 +2841,12 @@ export interface SystemConfigDto { * @memberof SystemConfigDto */ 'machineLearning': SystemConfigMachineLearningDto; + /** + * + * @type {SystemConfigMapDto} + * @memberof SystemConfigDto + */ + 'map': SystemConfigMapDto; /** * * @type {SystemConfigOAuthDto} @@ -3050,6 +3087,25 @@ export interface SystemConfigMachineLearningDto { */ 'url': string; } +/** + * + * @export + * @interface SystemConfigMapDto + */ +export interface SystemConfigMapDto { + /** + * + * @type {boolean} + * @memberof SystemConfigMapDto + */ + 'enabled': boolean; + /** + * + * @type {string} + * @memberof SystemConfigMapDto + */ + 'tileUrl': string; +} /** * * @export @@ -10825,6 +10881,35 @@ export class SearchApi extends BaseAPI { */ export const ServerInfoApiAxiosParamCreator = function (configuration?: Configuration) { return { + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getServerConfig: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/server-info/config`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * * @param {*} [options] Override http request option. @@ -11027,6 +11112,15 @@ export const ServerInfoApiAxiosParamCreator = function (configuration?: Configur export const ServerInfoApiFp = function(configuration?: Configuration) { const localVarAxiosParamCreator = ServerInfoApiAxiosParamCreator(configuration) return { + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getServerConfig(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getServerConfig(options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @param {*} [options] Override http request option. @@ -11091,6 +11185,14 @@ export const ServerInfoApiFp = function(configuration?: Configuration) { export const ServerInfoApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { const localVarFp = ServerInfoApiFp(configuration) return { + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getServerConfig(options?: AxiosRequestConfig): AxiosPromise { + return localVarFp.getServerConfig(options).then((request) => request(axios, basePath)); + }, /** * * @param {*} [options] Override http request option. @@ -11149,6 +11251,16 @@ export const ServerInfoApiFactory = function (configuration?: Configuration, bas * @extends {BaseAPI} */ export class ServerInfoApi extends BaseAPI { + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof ServerInfoApi + */ + public getServerConfig(options?: AxiosRequestConfig) { + return ServerInfoApiFp(this.configuration).getServerConfig(options).then((request) => request(this.axios, this.basePath)); + } + /** * * @param {*} [options] Override http request option. diff --git a/web/src/lib/components/admin-page/jobs/jobs-panel.svelte b/web/src/lib/components/admin-page/jobs/jobs-panel.svelte index 441210fc2a..38230afd29 100644 --- a/web/src/lib/components/admin-page/jobs/jobs-panel.svelte +++ b/web/src/lib/components/admin-page/jobs/jobs-panel.svelte @@ -4,7 +4,7 @@ NotificationType, } from '$lib/components/shared-components/notification/notification'; import { AppRoute } from '$lib/constants'; - import { featureFlags } from '$lib/stores/feature-flags.store'; + import { featureFlags } from '$lib/stores/server-config.store'; import { handleError } from '$lib/utils/handle-error'; import { AllJobStatusResponseDto, api, JobCommand, JobCommandDto, JobName } from '@api'; import type { ComponentType } from 'svelte'; diff --git a/web/src/lib/components/admin-page/settings/map-settings/map-settings.svelte b/web/src/lib/components/admin-page/settings/map-settings/map-settings.svelte new file mode 100644 index 0000000000..dff3c65a36 --- /dev/null +++ b/web/src/lib/components/admin-page/settings/map-settings/map-settings.svelte @@ -0,0 +1,98 @@ + + +
+ {#await getConfigs() then} +
+
+
+ + +
+ + + + +
+
+
+ {/await} +
diff --git a/web/src/lib/components/asset-viewer/detail-panel.svelte b/web/src/lib/components/asset-viewer/detail-panel.svelte index 53bff125fb..cea013424f 100644 --- a/web/src/lib/components/asset-viewer/detail-panel.svelte +++ b/web/src/lib/components/asset-viewer/detail-panel.svelte @@ -1,18 +1,19 @@ diff --git a/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte b/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte index 2a71f5e829..c34183bcb5 100644 --- a/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte +++ b/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte @@ -16,7 +16,7 @@ import IconButton from '$lib/components/elements/buttons/icon-button.svelte'; import Cog from 'svelte-material-icons/Cog.svelte'; import UserAvatar from '../user-avatar.svelte'; - import { featureFlags } from '$lib/stores/feature-flags.store'; + import { featureFlags } from '$lib/stores/server-config.store'; export let user: UserResponseDto; export let showUploadButton = true; diff --git a/web/src/lib/components/shared-components/side-bar/side-bar.svelte b/web/src/lib/components/shared-components/side-bar/side-bar.svelte index 770bbfa238..c6ac3b13a3 100644 --- a/web/src/lib/components/shared-components/side-bar/side-bar.svelte +++ b/web/src/lib/components/shared-components/side-bar/side-bar.svelte @@ -17,7 +17,7 @@ import SideBarButton from './side-bar-button.svelte'; import { locale } from '$lib/stores/preferences.store'; import SideBarSection from './side-bar-section.svelte'; - import { featureFlags } from '$lib/stores/feature-flags.store'; + import { featureFlags } from '$lib/stores/server-config.store'; const getStats = async (dto: AssetApiGetAssetStatsRequest) => { const { data: stats } = await api.assetApi.getAssetStats(dto); @@ -62,9 +62,11 @@ {/if} - - - + {#if $featureFlags.map} + + + + {/if} import { goto } from '$app/navigation'; - import { featureFlags } from '$lib/stores/feature-flags.store'; + import { featureFlags } from '$lib/stores/server-config.store'; import { oauth, UserResponseDto } from '@api'; import { onMount } from 'svelte'; import { fade } from 'svelte/transition'; diff --git a/web/src/lib/components/user-settings-page/user-settings-list.svelte b/web/src/lib/components/user-settings-page/user-settings-list.svelte index a30539c297..0aa9a6df80 100644 --- a/web/src/lib/components/user-settings-page/user-settings-list.svelte +++ b/web/src/lib/components/user-settings-page/user-settings-list.svelte @@ -1,7 +1,7 @@ - -
- {#if leaflet} - {@const { Map, TileLayer, AssetMarkerCluster, Control } = leaflet} - - +
+ {#if leaflet} + {@const { Map, TileLayer, AssetMarkerCluster, Control } = leaflet} + OpenStreetMap', + maxBounds: [ + [-90, -180], + [90, 180], + ], + minZoom: 2, }} - /> - onViewAssets(detail.assetIds, detail.activeAssetIndex)} - /> - - - - - {/if} -
- + > + OpenStreetMap', + }} + /> + onViewAssets(detail.assetIds, detail.activeAssetIndex)} + /> + + + + + {/if} +
+ - - {#if $showAssetViewer} - 1} - on:next={navigateNext} - on:previous={navigatePrevious} - on:close={() => assetViewingStore.showAssetViewer(false)} + + {#if $showAssetViewer} + 1} + on:next={navigateNext} + on:previous={navigatePrevious} + on:close={() => assetViewingStore.showAssetViewer(false)} + /> + {/if} + + + {#if showSettingsModal} + (showSettingsModal = false)} + on:save={async ({ detail }) => { + const shouldUpdate = !isEqual(omit(detail, 'allowDarkMode'), omit($mapSettings, 'allowDarkMode')); + showSettingsModal = false; + $mapSettings = detail; + + if (shouldUpdate) { + mapMarkers = await loadMapMarkers(); + } + }} /> {/if} - - -{#if showSettingsModal} - (showSettingsModal = false)} - on:save={async ({ detail }) => { - const shouldUpdate = !isEqual(omit(detail, 'allowDarkMode'), omit($mapSettings, 'allowDarkMode')); - showSettingsModal = false; - $mapSettings = detail; - - if (shouldUpdate) { - mapMarkers = await loadMapMarkers(); - } - }} - /> {/if} diff --git a/web/src/routes/+layout.svelte b/web/src/routes/+layout.svelte index 519cdd7868..9ed7bf2828 100644 --- a/web/src/routes/+layout.svelte +++ b/web/src/routes/+layout.svelte @@ -14,7 +14,7 @@ import AppleHeader from '$lib/components/shared-components/apple-header.svelte'; import FaviconHeader from '$lib/components/shared-components/favicon-header.svelte'; import { onMount } from 'svelte'; - import { loadFeatureFlags } from '$lib/stores/feature-flags.store'; + import { loadConfig } from '$lib/stores/server-config.store'; import { handleError } from '$lib/utils/handle-error'; import { dragAndDropFilesStore } from '$lib/stores/drag-and-drop-files.store'; import { api } from '@api'; @@ -37,9 +37,9 @@ onMount(async () => { try { - await loadFeatureFlags(); + await loadConfig(); } catch (error) { - handleError(error, 'Unable to load feature flags'); + handleError(error, 'Unable to connect to server'); } }); diff --git a/web/src/routes/admin/system-settings/+page.svelte b/web/src/routes/admin/system-settings/+page.svelte index e7626148eb..fe8a78d552 100644 --- a/web/src/routes/admin/system-settings/+page.svelte +++ b/web/src/routes/admin/system-settings/+page.svelte @@ -3,6 +3,7 @@ import FFmpegSettings from '$lib/components/admin-page/settings/ffmpeg/ffmpeg-settings.svelte'; import JobSettings from '$lib/components/admin-page/settings/job-settings/job-settings.svelte'; import MachineLearningSettings from '$lib/components/admin-page/settings/machine-learning-settings/machine-learning-settings.svelte'; + import MapSettings from '$lib/components/admin-page/settings/map-settings/map-settings.svelte'; import OAuthSettings from '$lib/components/admin-page/settings/oauth/oauth-settings.svelte'; import PasswordLoginSettings from '$lib/components/admin-page/settings/password-login/password-login-settings.svelte'; import SettingAccordion from '$lib/components/admin-page/settings/setting-accordion.svelte'; @@ -11,7 +12,7 @@ import Button from '$lib/components/elements/buttons/button.svelte'; import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte'; import { downloadManager } from '$lib/stores/download'; - import { featureFlags } from '$lib/stores/feature-flags.store'; + import { featureFlags } from '$lib/stores/server-config.store'; import { downloadBlob } from '$lib/utils/asset-utils'; import { SystemConfigDto, api, copyToClipboard } from '@api'; import Alert from 'svelte-material-icons/Alert.svelte'; @@ -57,20 +58,6 @@ Export as JSON - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + {/await} diff --git a/web/src/routes/auth/login/+page.svelte b/web/src/routes/auth/login/+page.svelte index c3e4e8a418..4b11481f64 100644 --- a/web/src/routes/auth/login/+page.svelte +++ b/web/src/routes/auth/login/+page.svelte @@ -3,18 +3,17 @@ import LoginForm from '$lib/components/forms/login-form.svelte'; import FullscreenContainer from '$lib/components/shared-components/fullscreen-container.svelte'; import { AppRoute } from '$lib/constants'; - import { loginPageMessage } from '$lib/constants'; - import { featureFlags } from '$lib/stores/feature-flags.store'; + import { featureFlags, serverConfig } from '$lib/stores/server-config.store'; import type { PageData } from './$types'; export let data: PageData; {#if $featureFlags.loaded} - +

- {@html loginPageMessage} + {@html $serverConfig.loginPageMessage}