diff --git a/cli/src/api/open-api/api.ts b/cli/src/api/open-api/api.ts index 03d260a6b1..850aeae7f3 100644 --- a/cli/src/api/open-api/api.ts +++ b/cli/src/api/open-api/api.ts @@ -2087,6 +2087,43 @@ export interface SearchResponseDto { */ 'assets': SearchAssetResponseDto; } +/** + * + * @export + * @interface ServerFeaturesDto + */ +export interface ServerFeaturesDto { + /** + * + * @type {boolean} + * @memberof ServerFeaturesDto + */ + 'machineLearning': boolean; + /** + * + * @type {boolean} + * @memberof ServerFeaturesDto + */ + 'oauth': boolean; + /** + * + * @type {boolean} + * @memberof ServerFeaturesDto + */ + 'oauthAutoLaunch': boolean; + /** + * + * @type {boolean} + * @memberof ServerFeaturesDto + */ + 'passwordLogin': boolean; + /** + * + * @type {boolean} + * @memberof ServerFeaturesDto + */ + 'search': boolean; +} /** * * @export @@ -2208,25 +2245,25 @@ export interface ServerStatsResponseDto { /** * * @export - * @interface ServerVersionReponseDto + * @interface ServerVersionResponseDto */ -export interface ServerVersionReponseDto { +export interface ServerVersionResponseDto { /** * * @type {number} - * @memberof ServerVersionReponseDto + * @memberof ServerVersionResponseDto */ 'major': number; /** * * @type {number} - * @memberof ServerVersionReponseDto + * @memberof ServerVersionResponseDto */ 'minor': number; /** * * @type {number} - * @memberof ServerVersionReponseDto + * @memberof ServerVersionResponseDto */ 'patch': number; } @@ -10156,6 +10193,35 @@ export class SearchApi extends BaseAPI { */ export const ServerInfoApiAxiosParamCreator = function (configuration?: Configuration) { return { + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getServerFeatures: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/server-info/features`; + // 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. @@ -10329,6 +10395,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 getServerFeatures(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getServerFeatures(options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @param {*} [options] Override http request option. @@ -10343,7 +10418,7 @@ export const ServerInfoApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getServerVersion(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + async getServerVersion(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { const localVarAxiosArgs = await localVarAxiosParamCreator.getServerVersion(options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, @@ -10384,6 +10459,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} + */ + getServerFeatures(options?: AxiosRequestConfig): AxiosPromise { + return localVarFp.getServerFeatures(options).then((request) => request(axios, basePath)); + }, /** * * @param {*} [options] Override http request option. @@ -10397,7 +10480,7 @@ export const ServerInfoApiFactory = function (configuration?: Configuration, bas * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getServerVersion(options?: AxiosRequestConfig): AxiosPromise { + getServerVersion(options?: AxiosRequestConfig): AxiosPromise { return localVarFp.getServerVersion(options).then((request) => request(axios, basePath)); }, /** @@ -10434,6 +10517,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 getServerFeatures(options?: AxiosRequestConfig) { + return ServerInfoApiFp(this.configuration).getServerFeatures(options).then((request) => request(this.axios, this.basePath)); + } + /** * * @param {*} [options] Override http request option. diff --git a/cli/src/cli/base-command.ts b/cli/src/cli/base-command.ts index 06247e29c8..c2fb8fee92 100644 --- a/cli/src/cli/base-command.ts +++ b/cli/src/cli/base-command.ts @@ -4,14 +4,14 @@ import { SessionService } from '../services/session.service'; import { LoginError } from '../cores/errors/login-error'; import { exit } from 'node:process'; import os from 'os'; -import { ServerVersionReponseDto, UserResponseDto } from 'src/api/open-api'; +import { ServerVersionResponseDto, UserResponseDto } from 'src/api/open-api'; export abstract class BaseCommand { protected sessionService!: SessionService; protected immichApi!: ImmichApi; protected deviceId!: string; protected user!: UserResponseDto; - protected serverVersion!: ServerVersionReponseDto; + protected serverVersion!: ServerVersionResponseDto; protected configDir; protected authPath; diff --git a/mobile/lib/shared/models/server_info_state.model.dart b/mobile/lib/shared/models/server_info_state.model.dart index 7b1ea9c918..6623ac1666 100644 --- a/mobile/lib/shared/models/server_info_state.model.dart +++ b/mobile/lib/shared/models/server_info_state.model.dart @@ -1,7 +1,7 @@ import 'package:openapi/api.dart'; class ServerInfoState { - final ServerVersionReponseDto serverVersion; + final ServerVersionResponseDto serverVersion; final bool isVersionMismatch; final String versionMismatchErrorMessage; @@ -12,7 +12,7 @@ class ServerInfoState { }); ServerInfoState copyWith({ - ServerVersionReponseDto? serverVersion, + ServerVersionResponseDto? serverVersion, bool? isVersionMismatch, String? versionMismatchErrorMessage, }) { diff --git a/mobile/lib/shared/providers/server_info.provider.dart b/mobile/lib/shared/providers/server_info.provider.dart index 2613b85224..ff0945ead0 100644 --- a/mobile/lib/shared/providers/server_info.provider.dart +++ b/mobile/lib/shared/providers/server_info.provider.dart @@ -10,7 +10,7 @@ class ServerInfoNotifier extends StateNotifier { ServerInfoNotifier(this._serverInfoService) : super( ServerInfoState( - serverVersion: ServerVersionReponseDto( + serverVersion: ServerVersionResponseDto( major: 0, patch_: 0, minor: 0, @@ -23,7 +23,7 @@ class ServerInfoNotifier extends StateNotifier { final ServerInfoService _serverInfoService; getServerVersion() async { - ServerVersionReponseDto? serverVersion = + ServerVersionResponseDto? serverVersion = await _serverInfoService.getServerVersion(); if (serverVersion == null) { diff --git a/mobile/lib/shared/services/server_info.service.dart b/mobile/lib/shared/services/server_info.service.dart index b112fc46ca..bf923dfab0 100644 --- a/mobile/lib/shared/services/server_info.service.dart +++ b/mobile/lib/shared/services/server_info.service.dart @@ -24,7 +24,7 @@ class ServerInfoService { } } - Future getServerVersion() async { + Future getServerVersion() async { try { return await _apiService.serverInfoApi.getServerVersion(); } catch (e) { diff --git a/mobile/openapi/.openapi-generator/FILES b/mobile/openapi/.openapi-generator/FILES index d8e4b1fad8..c51b8a3e02 100644 --- a/mobile/openapi/.openapi-generator/FILES +++ b/mobile/openapi/.openapi-generator/FILES @@ -85,12 +85,13 @@ doc/SearchExploreResponseDto.md doc/SearchFacetCountResponseDto.md doc/SearchFacetResponseDto.md doc/SearchResponseDto.md +doc/ServerFeaturesDto.md doc/ServerInfoApi.md doc/ServerInfoResponseDto.md doc/ServerMediaTypesResponseDto.md doc/ServerPingResponse.md doc/ServerStatsResponseDto.md -doc/ServerVersionReponseDto.md +doc/ServerVersionResponseDto.md doc/SharedLinkApi.md doc/SharedLinkCreateDto.md doc/SharedLinkEditDto.md @@ -223,11 +224,12 @@ 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_features_dto.dart lib/model/server_info_response_dto.dart lib/model/server_media_types_response_dto.dart lib/model/server_ping_response.dart lib/model/server_stats_response_dto.dart -lib/model/server_version_reponse_dto.dart +lib/model/server_version_response_dto.dart lib/model/shared_link_create_dto.dart lib/model/shared_link_edit_dto.dart lib/model/shared_link_response_dto.dart @@ -342,12 +344,13 @@ 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_features_dto_test.dart test/server_info_api_test.dart test/server_info_response_dto_test.dart test/server_media_types_response_dto_test.dart test/server_ping_response_test.dart test/server_stats_response_dto_test.dart -test/server_version_reponse_dto_test.dart +test/server_version_response_dto_test.dart test/shared_link_api_test.dart test/shared_link_create_dto_test.dart test/shared_link_edit_dto_test.dart diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 042d69591f..6c6108a9fe 100644 Binary files a/mobile/openapi/README.md and b/mobile/openapi/README.md differ diff --git a/mobile/openapi/doc/ServerFeaturesDto.md b/mobile/openapi/doc/ServerFeaturesDto.md new file mode 100644 index 0000000000..303b89f188 Binary files /dev/null and b/mobile/openapi/doc/ServerFeaturesDto.md differ diff --git a/mobile/openapi/doc/ServerInfoApi.md b/mobile/openapi/doc/ServerInfoApi.md index 0666c7d414..62758afbe8 100644 Binary files a/mobile/openapi/doc/ServerInfoApi.md and b/mobile/openapi/doc/ServerInfoApi.md differ diff --git a/mobile/openapi/doc/ServerVersionReponseDto.md b/mobile/openapi/doc/ServerVersionResponseDto.md similarity index 91% rename from mobile/openapi/doc/ServerVersionReponseDto.md rename to mobile/openapi/doc/ServerVersionResponseDto.md index 68dfa972cc..b48291f12f 100644 Binary files a/mobile/openapi/doc/ServerVersionReponseDto.md and b/mobile/openapi/doc/ServerVersionResponseDto.md differ diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index e83c7b868b..064a265175 100644 Binary files a/mobile/openapi/lib/api.dart and b/mobile/openapi/lib/api.dart differ diff --git a/mobile/openapi/lib/api/server_info_api.dart b/mobile/openapi/lib/api/server_info_api.dart index fb34e09ce8..3b7899cc26 100644 Binary files a/mobile/openapi/lib/api/server_info_api.dart and b/mobile/openapi/lib/api/server_info_api.dart differ diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index a46580899d..830aab9369 100644 Binary files a/mobile/openapi/lib/api_client.dart and b/mobile/openapi/lib/api_client.dart differ diff --git a/mobile/openapi/lib/model/server_features_dto.dart b/mobile/openapi/lib/model/server_features_dto.dart new file mode 100644 index 0000000000..0c83fa7921 Binary files /dev/null and b/mobile/openapi/lib/model/server_features_dto.dart differ diff --git a/mobile/openapi/lib/model/server_version_reponse_dto.dart b/mobile/openapi/lib/model/server_version_response_dto.dart similarity index 63% rename from mobile/openapi/lib/model/server_version_reponse_dto.dart rename to mobile/openapi/lib/model/server_version_response_dto.dart index a1ec0e68e5..353d65049b 100644 Binary files a/mobile/openapi/lib/model/server_version_reponse_dto.dart and b/mobile/openapi/lib/model/server_version_response_dto.dart differ diff --git a/mobile/openapi/test/server_features_dto_test.dart b/mobile/openapi/test/server_features_dto_test.dart new file mode 100644 index 0000000000..d2ae364c4b Binary files /dev/null and b/mobile/openapi/test/server_features_dto_test.dart differ diff --git a/mobile/openapi/test/server_info_api_test.dart b/mobile/openapi/test/server_info_api_test.dart index 4fee682a25..8ca3e30ef1 100644 Binary files a/mobile/openapi/test/server_info_api_test.dart and b/mobile/openapi/test/server_info_api_test.dart differ diff --git a/mobile/openapi/test/server_version_reponse_dto_test.dart b/mobile/openapi/test/server_version_response_dto_test.dart similarity index 82% rename from mobile/openapi/test/server_version_reponse_dto_test.dart rename to mobile/openapi/test/server_version_response_dto_test.dart index 3095e7a462..add42ccd66 100644 Binary files a/mobile/openapi/test/server_version_reponse_dto_test.dart and b/mobile/openapi/test/server_version_response_dto_test.dart differ diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index 4078e687d6..91f72c1708 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -3248,6 +3248,27 @@ ] } }, + "/server-info/features": { + "get": { + "operationId": "getServerFeatures", + "parameters": [], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ServerFeaturesDto" + } + } + }, + "description": "" + } + }, + "tags": [ + "Server Info" + ] + } + }, "/server-info/media-types": { "get": { "operationId": "getSupportedMediaTypes", @@ -3331,7 +3352,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ServerVersionReponseDto" + "$ref": "#/components/schemas/ServerVersionResponseDto" } } }, @@ -6331,6 +6352,33 @@ ], "type": "object" }, + "ServerFeaturesDto": { + "properties": { + "machineLearning": { + "type": "boolean" + }, + "oauth": { + "type": "boolean" + }, + "oauthAutoLaunch": { + "type": "boolean" + }, + "passwordLogin": { + "type": "boolean" + }, + "search": { + "type": "boolean" + } + }, + "required": [ + "machineLearning", + "search", + "oauth", + "oauthAutoLaunch", + "passwordLogin" + ], + "type": "object" + }, "ServerInfoResponseDto": { "properties": { "diskAvailable": { @@ -6450,7 +6498,7 @@ ], "type": "object" }, - "ServerVersionReponseDto": { + "ServerVersionResponseDto": { "properties": { "major": { "type": "integer" diff --git a/server/src/domain/domain.constant.ts b/server/src/domain/domain.constant.ts index 4c881d7eee..7b60b796aa 100644 --- a/server/src/domain/domain.constant.ts +++ b/server/src/domain/domain.constant.ts @@ -21,6 +21,8 @@ export const SERVER_VERSION = `${serverVersion.major}.${serverVersion.minor}.${s export const APP_MEDIA_LOCATION = process.env.IMMICH_MEDIA_LOCATION || './upload'; +export const SEARCH_ENABLED = process.env.TYPESENSE_ENABLED !== 'false'; + export const MACHINE_LEARNING_URL = process.env.IMMICH_MACHINE_LEARNING_URL || 'http://immich-machine-learning:3003'; export const MACHINE_LEARNING_ENABLED = MACHINE_LEARNING_URL !== 'false'; diff --git a/server/src/domain/server-info/index.ts b/server/src/domain/server-info/index.ts index 72113665a8..74a46a52b8 100644 --- a/server/src/domain/server-info/index.ts +++ b/server/src/domain/server-info/index.ts @@ -1,2 +1,2 @@ -export * from './response-dto'; +export * from './server-info.dto'; export * from './server-info.service'; diff --git a/server/src/domain/server-info/response-dto/index.ts b/server/src/domain/server-info/response-dto/index.ts deleted file mode 100644 index 47cbd2ff8f..0000000000 --- a/server/src/domain/server-info/response-dto/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './server-info-response.dto'; -export * from './server-ping-response.dto'; -export * from './server-stats-response.dto'; -export * from './server-version-response.dto'; -export * from './usage-by-user-response.dto'; diff --git a/server/src/domain/server-info/response-dto/server-info-response.dto.ts b/server/src/domain/server-info/response-dto/server-info-response.dto.ts deleted file mode 100644 index e844da6899..0000000000 --- a/server/src/domain/server-info/response-dto/server-info-response.dto.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; - -export class ServerInfoResponseDto { - diskSize!: string; - diskUse!: string; - diskAvailable!: string; - - @ApiProperty({ type: 'integer', format: 'int64' }) - diskSizeRaw!: number; - - @ApiProperty({ type: 'integer', format: 'int64' }) - diskUseRaw!: number; - - @ApiProperty({ type: 'integer', format: 'int64' }) - diskAvailableRaw!: number; - - @ApiProperty({ type: 'number', format: 'float' }) - diskUsagePercentage!: number; -} diff --git a/server/src/domain/server-info/response-dto/server-ping-response.dto.ts b/server/src/domain/server-info/response-dto/server-ping-response.dto.ts deleted file mode 100644 index 8b41b4af1b..0000000000 --- a/server/src/domain/server-info/response-dto/server-ping-response.dto.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ApiResponseProperty } from '@nestjs/swagger'; - -export class ServerPingResponse { - constructor(res: string) { - this.res = res; - } - - @ApiResponseProperty({ type: String, example: 'pong' }) - res!: string; -} diff --git a/server/src/domain/server-info/response-dto/server-stats-response.dto.ts b/server/src/domain/server-info/response-dto/server-stats-response.dto.ts deleted file mode 100644 index 1459ba452a..0000000000 --- a/server/src/domain/server-info/response-dto/server-stats-response.dto.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { UsageByUserDto } from './usage-by-user-response.dto'; - -export class ServerStatsResponseDto { - @ApiProperty({ type: 'integer' }) - photos = 0; - - @ApiProperty({ type: 'integer' }) - videos = 0; - - @ApiProperty({ type: 'integer', format: 'int64' }) - usage = 0; - - @ApiProperty({ - isArray: true, - type: UsageByUserDto, - title: 'Array of usage for each user', - example: [ - { - photos: 1, - videos: 1, - diskUsageRaw: 1, - }, - ], - }) - usageByUser: UsageByUserDto[] = []; -} - -export class ServerMediaTypesResponseDto { - video!: string[]; - image!: string[]; - sidecar!: string[]; -} diff --git a/server/src/domain/server-info/response-dto/server-version-response.dto.ts b/server/src/domain/server-info/response-dto/server-version-response.dto.ts deleted file mode 100644 index 373fa734f8..0000000000 --- a/server/src/domain/server-info/response-dto/server-version-response.dto.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { IServerVersion } from '@app/domain'; -import { ApiProperty } from '@nestjs/swagger'; - -export class ServerVersionReponseDto implements IServerVersion { - @ApiProperty({ type: 'integer' }) - major!: number; - @ApiProperty({ type: 'integer' }) - minor!: number; - @ApiProperty({ type: 'integer' }) - patch!: number; -} diff --git a/server/src/domain/server-info/response-dto/usage-by-user-response.dto.ts b/server/src/domain/server-info/response-dto/usage-by-user-response.dto.ts deleted file mode 100644 index ac3a829077..0000000000 --- a/server/src/domain/server-info/response-dto/usage-by-user-response.dto.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; - -export class UsageByUserDto { - @ApiProperty({ type: 'string' }) - userId!: string; - @ApiProperty({ type: 'string' }) - userFirstName!: string; - @ApiProperty({ type: 'string' }) - userLastName!: string; - @ApiProperty({ type: 'integer' }) - photos!: number; - @ApiProperty({ type: 'integer' }) - videos!: number; - @ApiProperty({ type: 'integer', format: 'int64' }) - usage!: number; -} diff --git a/server/src/domain/server-info/server-info.dto.ts b/server/src/domain/server-info/server-info.dto.ts new file mode 100644 index 0000000000..ea0699aa6f --- /dev/null +++ b/server/src/domain/server-info/server-info.dto.ts @@ -0,0 +1,89 @@ +import { IServerVersion } from '@app/domain'; +import { ApiProperty, ApiResponseProperty } from '@nestjs/swagger'; + +export class ServerPingResponse { + @ApiResponseProperty({ type: String, example: 'pong' }) + res!: string; +} + +export class ServerInfoResponseDto { + diskSize!: string; + diskUse!: string; + diskAvailable!: string; + + @ApiProperty({ type: 'integer', format: 'int64' }) + diskSizeRaw!: number; + + @ApiProperty({ type: 'integer', format: 'int64' }) + diskUseRaw!: number; + + @ApiProperty({ type: 'integer', format: 'int64' }) + diskAvailableRaw!: number; + + @ApiProperty({ type: 'number', format: 'float' }) + diskUsagePercentage!: number; +} + +export class ServerVersionResponseDto implements IServerVersion { + @ApiProperty({ type: 'integer' }) + major!: number; + @ApiProperty({ type: 'integer' }) + minor!: number; + @ApiProperty({ type: 'integer' }) + patch!: number; +} + +export class UsageByUserDto { + @ApiProperty({ type: 'string' }) + userId!: string; + @ApiProperty({ type: 'string' }) + userFirstName!: string; + @ApiProperty({ type: 'string' }) + userLastName!: string; + @ApiProperty({ type: 'integer' }) + photos!: number; + @ApiProperty({ type: 'integer' }) + videos!: number; + @ApiProperty({ type: 'integer', format: 'int64' }) + usage!: number; +} + +export class ServerStatsResponseDto { + @ApiProperty({ type: 'integer' }) + photos = 0; + + @ApiProperty({ type: 'integer' }) + videos = 0; + + @ApiProperty({ type: 'integer', format: 'int64' }) + usage = 0; + + @ApiProperty({ + isArray: true, + type: UsageByUserDto, + title: 'Array of usage for each user', + example: [ + { + photos: 1, + videos: 1, + diskUsageRaw: 1, + }, + ], + }) + usageByUser: UsageByUserDto[] = []; +} + +export class ServerMediaTypesResponseDto { + video!: string[]; + image!: string[]; + sidecar!: string[]; +} + +export class ServerFeaturesDto { + machineLearning!: boolean; + search!: boolean; + + oauth!: boolean; + oauthAutoLaunch!: boolean; + passwordLogin!: 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 ebb0d800fb..764e1c8891 100644 --- a/server/src/domain/server-info/server-info.service.spec.ts +++ b/server/src/domain/server-info/server-info.service.spec.ts @@ -1,19 +1,22 @@ -import { newStorageRepositoryMock, newUserRepositoryMock } from '@test'; +import { newStorageRepositoryMock, newSystemConfigRepositoryMock, newUserRepositoryMock } from '@test'; import { serverVersion } from '../domain.constant'; +import { ISystemConfigRepository } from '../index'; import { IStorageRepository } from '../storage'; import { IUserRepository } from '../user'; import { ServerInfoService } from './server-info.service'; describe(ServerInfoService.name, () => { let sut: ServerInfoService; + let configMock: jest.Mocked; let storageMock: jest.Mocked; let userMock: jest.Mocked; beforeEach(() => { + configMock = newSystemConfigRepositoryMock(); storageMock = newStorageRepositoryMock(); userMock = newUserRepositoryMock(); - sut = new ServerInfoService(userMock, storageMock); + sut = new ServerInfoService(configMock, userMock, storageMock); }); it('should work', () => { @@ -140,6 +143,19 @@ 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({ + machineLearning: true, + oauth: false, + oauthAutoLaunch: false, + passwordLogin: true, + search: true, + }); + expect(configMock.load).toHaveBeenCalled(); + }); + }); }); describe('getStats', () => { diff --git a/server/src/domain/server-info/server-info.service.ts b/server/src/domain/server-info/server-info.service.ts index 4a7e7f22bb..e628d12bad 100644 --- a/server/src/domain/server-info/server-info.service.ts +++ b/server/src/domain/server-info/server-info.service.ts @@ -1,24 +1,31 @@ import { Inject, Injectable } from '@nestjs/common'; -import { mimeTypes, serverVersion } from '../domain.constant'; +import { MACHINE_LEARNING_ENABLED, mimeTypes, SEARCH_ENABLED, serverVersion } from '../domain.constant'; import { asHumanReadable } from '../domain.util'; import { IStorageRepository, StorageCore, StorageFolder } from '../storage'; +import { ISystemConfigRepository } from '../system-config'; +import { SystemConfigCore } from '../system-config/system-config.core'; import { IUserRepository, UserStatsQueryResponse } from '../user'; import { + ServerFeaturesDto, ServerInfoResponseDto, ServerMediaTypesResponseDto, ServerPingResponse, ServerStatsResponseDto, UsageByUserDto, -} from './response-dto'; +} from './server-info.dto'; @Injectable() export class ServerInfoService { private storageCore = new StorageCore(); + private configCore: SystemConfigCore; constructor( + @Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository, @Inject(IUserRepository) private userRepository: IUserRepository, @Inject(IStorageRepository) private storageRepository: IStorageRepository, - ) {} + ) { + this.configCore = new SystemConfigCore(configRepository); + } async getInfo(): Promise { const libraryBase = this.storageCore.getBaseFolder(StorageFolder.LIBRARY); @@ -38,13 +45,27 @@ export class ServerInfoService { } ping(): ServerPingResponse { - return new ServerPingResponse('pong'); + return { res: 'pong' }; } getVersion() { return serverVersion; } + async getFeatures(): Promise { + const config = await this.configCore.getConfig(); + + return { + machineLearning: MACHINE_LEARNING_ENABLED, + search: SEARCH_ENABLED, + + // TODO: use these instead of `POST oauth/config` + oauth: config.oauth.enabled, + oauthAutoLaunch: config.oauth.autoLaunch, + passwordLogin: config.passwordLogin.enabled, + }; + } + async getStats(): Promise { const userStats: UserStatsQueryResponse[] = await this.userRepository.getUserStats(); const serverStats = new ServerStatsResponseDto(); diff --git a/server/src/immich/controllers/server-info.controller.ts b/server/src/immich/controllers/server-info.controller.ts index 59b6351787..3b69c35321 100644 --- a/server/src/immich/controllers/server-info.controller.ts +++ b/server/src/immich/controllers/server-info.controller.ts @@ -1,10 +1,11 @@ import { + ServerFeaturesDto, ServerInfoResponseDto, ServerInfoService, ServerMediaTypesResponseDto, ServerPingResponse, ServerStatsResponseDto, - ServerVersionReponseDto, + ServerVersionResponseDto, } from '@app/domain'; import { Controller, Get } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; @@ -24,25 +25,31 @@ export class ServerInfoController { } @PublicRoute() - @Get('/ping') + @Get('ping') pingServer(): ServerPingResponse { return this.service.ping(); } @PublicRoute() - @Get('/version') - getServerVersion(): ServerVersionReponseDto { + @Get('version') + getServerVersion(): ServerVersionResponseDto { return this.service.getVersion(); } + @PublicRoute() + @Get('features') + getServerFeatures(): Promise { + return this.service.getFeatures(); + } + @AdminRoute() - @Get('/stats') + @Get('stats') getStats(): Promise { return this.service.getStats(); } @PublicRoute() - @Get('/media-types') + @Get('media-types') getSupportedMediaTypes(): ServerMediaTypesResponseDto { return this.service.getSupportedMediaTypes(); } diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index 03d260a6b1..850aeae7f3 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -2087,6 +2087,43 @@ export interface SearchResponseDto { */ 'assets': SearchAssetResponseDto; } +/** + * + * @export + * @interface ServerFeaturesDto + */ +export interface ServerFeaturesDto { + /** + * + * @type {boolean} + * @memberof ServerFeaturesDto + */ + 'machineLearning': boolean; + /** + * + * @type {boolean} + * @memberof ServerFeaturesDto + */ + 'oauth': boolean; + /** + * + * @type {boolean} + * @memberof ServerFeaturesDto + */ + 'oauthAutoLaunch': boolean; + /** + * + * @type {boolean} + * @memberof ServerFeaturesDto + */ + 'passwordLogin': boolean; + /** + * + * @type {boolean} + * @memberof ServerFeaturesDto + */ + 'search': boolean; +} /** * * @export @@ -2208,25 +2245,25 @@ export interface ServerStatsResponseDto { /** * * @export - * @interface ServerVersionReponseDto + * @interface ServerVersionResponseDto */ -export interface ServerVersionReponseDto { +export interface ServerVersionResponseDto { /** * * @type {number} - * @memberof ServerVersionReponseDto + * @memberof ServerVersionResponseDto */ 'major': number; /** * * @type {number} - * @memberof ServerVersionReponseDto + * @memberof ServerVersionResponseDto */ 'minor': number; /** * * @type {number} - * @memberof ServerVersionReponseDto + * @memberof ServerVersionResponseDto */ 'patch': number; } @@ -10156,6 +10193,35 @@ export class SearchApi extends BaseAPI { */ export const ServerInfoApiAxiosParamCreator = function (configuration?: Configuration) { return { + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getServerFeatures: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/server-info/features`; + // 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. @@ -10329,6 +10395,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 getServerFeatures(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getServerFeatures(options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @param {*} [options] Override http request option. @@ -10343,7 +10418,7 @@ export const ServerInfoApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getServerVersion(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + async getServerVersion(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { const localVarAxiosArgs = await localVarAxiosParamCreator.getServerVersion(options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, @@ -10384,6 +10459,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} + */ + getServerFeatures(options?: AxiosRequestConfig): AxiosPromise { + return localVarFp.getServerFeatures(options).then((request) => request(axios, basePath)); + }, /** * * @param {*} [options] Override http request option. @@ -10397,7 +10480,7 @@ export const ServerInfoApiFactory = function (configuration?: Configuration, bas * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getServerVersion(options?: AxiosRequestConfig): AxiosPromise { + getServerVersion(options?: AxiosRequestConfig): AxiosPromise { return localVarFp.getServerVersion(options).then((request) => request(axios, basePath)); }, /** @@ -10434,6 +10517,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 getServerFeatures(options?: AxiosRequestConfig) { + return ServerInfoApiFp(this.configuration).getServerFeatures(options).then((request) => request(this.axios, this.basePath)); + } + /** * * @param {*} [options] Override http request option. diff --git a/web/src/lib/components/admin-page/jobs/job-tile-button.svelte b/web/src/lib/components/admin-page/jobs/job-tile-button.svelte index b794c387c8..709ed60924 100644 --- a/web/src/lib/components/admin-page/jobs/job-tile-button.svelte +++ b/web/src/lib/components/admin-page/jobs/job-tile-button.svelte @@ -4,17 +4,23 @@ - {#each jobDetailsArray as [jobName, { title, subtitle, allText, missingText, allowForceCommand, icon, component, handleCommand: handleCommandOverride }]} + {#each jobList as [jobName, { title, subtitle, disabled, allText, missingText, allowForceCommand, icon, component, handleCommand: handleCommandOverride }]} {@const { jobCounts, queueStatus } = jobs[jobName]}
- - -
- -
-
-
+ {#if $featureFlags.search} + + +
+ +
+
+
+ {/if} 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 0abf174815..770bbfa238 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,6 +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'; const getStats = async (dto: AssetApiGetAssetStatsRequest) => { const { data: stats } = await api.assetApi.getAssetStats(dto); @@ -56,9 +57,11 @@ - - - + {#if $featureFlags.search} + + + + {/if} diff --git a/web/src/lib/components/shared-components/version-announcement-box.svelte b/web/src/lib/components/shared-components/version-announcement-box.svelte index a59ab0e40b..af98568283 100644 --- a/web/src/lib/components/shared-components/version-announcement-box.svelte +++ b/web/src/lib/components/shared-components/version-announcement-box.svelte @@ -2,16 +2,16 @@ import { getGithubVersion } from '$lib/utils/get-github-version'; import { onMount } from 'svelte'; import FullScreenModal from './full-screen-modal.svelte'; - import type { ServerVersionReponseDto } from '@api'; + import type { ServerVersionResponseDto } from '@api'; import Button from '../elements/buttons/button.svelte'; - export let serverVersion: ServerVersionReponseDto; + export let serverVersion: ServerVersionResponseDto; let showModal = false; let githubVersion: string; $: serverVersionName = semverToName(serverVersion); - function semverToName({ major, minor, patch }: ServerVersionReponseDto) { + function semverToName({ major, minor, patch }: ServerVersionResponseDto) { return `v${major}.${minor}.${patch}`; } diff --git a/web/src/lib/stores/feature-flags.store.ts b/web/src/lib/stores/feature-flags.store.ts new file mode 100644 index 0000000000..119ecd557e --- /dev/null +++ b/web/src/lib/stores/feature-flags.store.ts @@ -0,0 +1,17 @@ +import { api, ServerFeaturesDto } from '@api'; +import { writable } from 'svelte/store'; + +export type FeatureFlags = ServerFeaturesDto; + +export const featureFlags = writable({ + machineLearning: true, + search: true, + oauth: true, + oauthAutoLaunch: true, + passwordLogin: true, +}); + +export const loadFeatureFlags = async () => { + const { data } = await api.serverInfoApi.getServerFeatures(); + featureFlags.update(() => data); +}; diff --git a/web/src/routes/+layout.svelte b/web/src/routes/+layout.svelte index d224461bb2..c57c04f511 100644 --- a/web/src/routes/+layout.svelte +++ b/web/src/routes/+layout.svelte @@ -1,6 +1,5 @@