Compare commits

...

9 Commits

Author SHA1 Message Date
Daniel Dietzler
4b422bd0f7 disable metrics in dev env 2023-12-31 19:50:10 +01:00
Daniel Dietzler
f33a662f48 open api 2023-12-28 19:49:39 +01:00
Daniel Dietzler
0232655da2 revert individual settings for metrics, now only enable/disable 2023-12-28 19:49:32 +01:00
Daniel Dietzler
ac4c57247e add icon indicating when metrics are being shared 2023-12-28 19:29:21 +01:00
Daniel Dietzler
fb01bd956f open api 2023-12-28 19:06:45 +01:00
Daniel Dietzler
902d4d0275 settings for metrics 2023-12-28 19:06:35 +01:00
Daniel Dietzler
db997f9173 queue metrics job every 24 hours 2023-12-23 22:09:51 +01:00
Daniel Dietzler
e9197cde67 collect more metrics, move everything related to metrics repo 2023-12-23 21:50:20 +01:00
Daniel Dietzler
874f707c92 initial sample implementation of metrics 2023-12-23 21:50:18 +01:00
47 changed files with 995 additions and 5 deletions

View File

@@ -3062,6 +3062,12 @@ export interface ServerFeaturesDto {
* @memberof ServerFeaturesDto * @memberof ServerFeaturesDto
*/ */
'map': boolean; 'map': boolean;
/**
*
* @type {boolean}
* @memberof ServerFeaturesDto
*/
'metrics': boolean;
/** /**
* *
* @type {boolean} * @type {boolean}
@@ -3566,6 +3572,12 @@ export interface SystemConfigDto {
* @memberof SystemConfigDto * @memberof SystemConfigDto
*/ */
'map': SystemConfigMapDto; 'map': SystemConfigMapDto;
/**
*
* @type {SystemConfigMetricsDto}
* @memberof SystemConfigDto
*/
'metrics': SystemConfigMetricsDto;
/** /**
* *
* @type {SystemConfigNewVersionCheckDto} * @type {SystemConfigNewVersionCheckDto}
@@ -3908,6 +3920,19 @@ export interface SystemConfigMapDto {
*/ */
'lightStyle': string; 'lightStyle': string;
} }
/**
*
* @export
* @interface SystemConfigMetricsDto
*/
export interface SystemConfigMetricsDto {
/**
*
* @type {boolean}
* @memberof SystemConfigMetricsDto
*/
'enabled': boolean;
}
/** /**
* *
* @export * @export
@@ -12692,6 +12717,109 @@ export class LibraryApi extends BaseAPI {
} }
/**
* MetricsApi - axios parameter creator
* @export
*/
export const MetricsApiAxiosParamCreator = function (configuration?: Configuration) {
return {
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getMetrics: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/metrics`;
// 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;
// authentication cookie required
// authentication api_key required
await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration)
// authentication bearer required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
}
};
/**
* MetricsApi - functional programming interface
* @export
*/
export const MetricsApiFp = function(configuration?: Configuration) {
const localVarAxiosParamCreator = MetricsApiAxiosParamCreator(configuration)
return {
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async getMetrics(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getMetrics(options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
}
};
/**
* MetricsApi - factory interface
* @export
*/
export const MetricsApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
const localVarFp = MetricsApiFp(configuration)
return {
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getMetrics(options?: AxiosRequestConfig): AxiosPromise<object> {
return localVarFp.getMetrics(options).then((request) => request(axios, basePath));
},
};
};
/**
* MetricsApi - object-oriented interface
* @export
* @class MetricsApi
* @extends {BaseAPI}
*/
export class MetricsApi extends BaseAPI {
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof MetricsApi
*/
public getMetrics(options?: AxiosRequestConfig) {
return MetricsApiFp(this.configuration).getMetrics(options).then((request) => request(this.axios, this.basePath));
}
}
/** /**
* OAuthApi - axios parameter creator * OAuthApi - axios parameter creator
* @export * @export

View File

@@ -89,6 +89,7 @@ doc/MapMarkerResponseDto.md
doc/MapTheme.md doc/MapTheme.md
doc/MemoryLaneResponseDto.md doc/MemoryLaneResponseDto.md
doc/MergePersonDto.md doc/MergePersonDto.md
doc/MetricsApi.md
doc/ModelType.md doc/ModelType.md
doc/OAuthApi.md doc/OAuthApi.md
doc/OAuthAuthorizeResponseDto.md doc/OAuthAuthorizeResponseDto.md
@@ -145,6 +146,7 @@ doc/SystemConfigLibraryScanDto.md
doc/SystemConfigLoggingDto.md doc/SystemConfigLoggingDto.md
doc/SystemConfigMachineLearningDto.md doc/SystemConfigMachineLearningDto.md
doc/SystemConfigMapDto.md doc/SystemConfigMapDto.md
doc/SystemConfigMetricsDto.md
doc/SystemConfigNewVersionCheckDto.md doc/SystemConfigNewVersionCheckDto.md
doc/SystemConfigOAuthDto.md doc/SystemConfigOAuthDto.md
doc/SystemConfigPasswordLoginDto.md doc/SystemConfigPasswordLoginDto.md
@@ -188,6 +190,7 @@ lib/api/authentication_api.dart
lib/api/face_api.dart lib/api/face_api.dart
lib/api/job_api.dart lib/api/job_api.dart
lib/api/library_api.dart lib/api/library_api.dart
lib/api/metrics_api.dart
lib/api/o_auth_api.dart lib/api/o_auth_api.dart
lib/api/partner_api.dart lib/api/partner_api.dart
lib/api/person_api.dart lib/api/person_api.dart
@@ -331,6 +334,7 @@ lib/model/system_config_library_scan_dto.dart
lib/model/system_config_logging_dto.dart lib/model/system_config_logging_dto.dart
lib/model/system_config_machine_learning_dto.dart lib/model/system_config_machine_learning_dto.dart
lib/model/system_config_map_dto.dart lib/model/system_config_map_dto.dart
lib/model/system_config_metrics_dto.dart
lib/model/system_config_new_version_check_dto.dart lib/model/system_config_new_version_check_dto.dart
lib/model/system_config_o_auth_dto.dart lib/model/system_config_o_auth_dto.dart
lib/model/system_config_password_login_dto.dart lib/model/system_config_password_login_dto.dart
@@ -448,6 +452,7 @@ test/map_marker_response_dto_test.dart
test/map_theme_test.dart test/map_theme_test.dart
test/memory_lane_response_dto_test.dart test/memory_lane_response_dto_test.dart
test/merge_person_dto_test.dart test/merge_person_dto_test.dart
test/metrics_api_test.dart
test/model_type_test.dart test/model_type_test.dart
test/o_auth_api_test.dart test/o_auth_api_test.dart
test/o_auth_authorize_response_dto_test.dart test/o_auth_authorize_response_dto_test.dart
@@ -504,6 +509,7 @@ test/system_config_library_scan_dto_test.dart
test/system_config_logging_dto_test.dart test/system_config_logging_dto_test.dart
test/system_config_machine_learning_dto_test.dart test/system_config_machine_learning_dto_test.dart
test/system_config_map_dto_test.dart test/system_config_map_dto_test.dart
test/system_config_metrics_dto_test.dart
test/system_config_new_version_check_dto_test.dart test/system_config_new_version_check_dto_test.dart
test/system_config_o_auth_dto_test.dart test/system_config_o_auth_dto_test.dart
test/system_config_password_login_dto_test.dart test/system_config_password_login_dto_test.dart

View File

@@ -145,6 +145,7 @@ Class | Method | HTTP request | Description
*LibraryApi* | [**removeOfflineFiles**](doc//LibraryApi.md#removeofflinefiles) | **POST** /library/{id}/removeOffline | *LibraryApi* | [**removeOfflineFiles**](doc//LibraryApi.md#removeofflinefiles) | **POST** /library/{id}/removeOffline |
*LibraryApi* | [**scanLibrary**](doc//LibraryApi.md#scanlibrary) | **POST** /library/{id}/scan | *LibraryApi* | [**scanLibrary**](doc//LibraryApi.md#scanlibrary) | **POST** /library/{id}/scan |
*LibraryApi* | [**updateLibrary**](doc//LibraryApi.md#updatelibrary) | **PUT** /library/{id} | *LibraryApi* | [**updateLibrary**](doc//LibraryApi.md#updatelibrary) | **PUT** /library/{id} |
*MetricsApi* | [**getMetrics**](doc//MetricsApi.md#getmetrics) | **GET** /metrics |
*OAuthApi* | [**finishOAuth**](doc//OAuthApi.md#finishoauth) | **POST** /oauth/callback | *OAuthApi* | [**finishOAuth**](doc//OAuthApi.md#finishoauth) | **POST** /oauth/callback |
*OAuthApi* | [**generateOAuthConfig**](doc//OAuthApi.md#generateoauthconfig) | **POST** /oauth/config | *OAuthApi* | [**generateOAuthConfig**](doc//OAuthApi.md#generateoauthconfig) | **POST** /oauth/config |
*OAuthApi* | [**linkOAuthAccount**](doc//OAuthApi.md#linkoauthaccount) | **POST** /oauth/link | *OAuthApi* | [**linkOAuthAccount**](doc//OAuthApi.md#linkoauthaccount) | **POST** /oauth/link |
@@ -337,6 +338,7 @@ Class | Method | HTTP request | Description
- [SystemConfigLoggingDto](doc//SystemConfigLoggingDto.md) - [SystemConfigLoggingDto](doc//SystemConfigLoggingDto.md)
- [SystemConfigMachineLearningDto](doc//SystemConfigMachineLearningDto.md) - [SystemConfigMachineLearningDto](doc//SystemConfigMachineLearningDto.md)
- [SystemConfigMapDto](doc//SystemConfigMapDto.md) - [SystemConfigMapDto](doc//SystemConfigMapDto.md)
- [SystemConfigMetricsDto](doc//SystemConfigMetricsDto.md)
- [SystemConfigNewVersionCheckDto](doc//SystemConfigNewVersionCheckDto.md) - [SystemConfigNewVersionCheckDto](doc//SystemConfigNewVersionCheckDto.md)
- [SystemConfigOAuthDto](doc//SystemConfigOAuthDto.md) - [SystemConfigOAuthDto](doc//SystemConfigOAuthDto.md)
- [SystemConfigPasswordLoginDto](doc//SystemConfigPasswordLoginDto.md) - [SystemConfigPasswordLoginDto](doc//SystemConfigPasswordLoginDto.md)

65
mobile/openapi/doc/MetricsApi.md generated Normal file
View File

@@ -0,0 +1,65 @@
# openapi.api.MetricsApi
## Load the API package
```dart
import 'package:openapi/api.dart';
```
All URIs are relative to */api*
Method | HTTP request | Description
------------- | ------------- | -------------
[**getMetrics**](MetricsApi.md#getmetrics) | **GET** /metrics |
# **getMetrics**
> Object getMetrics()
### Example
```dart
import 'package:openapi/api.dart';
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
// TODO Configure API key authorization: api_key
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKeyPrefix = 'Bearer';
// TODO Configure HTTP Bearer authorization: bearer
// Case 1. Use String Token
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
final api_instance = MetricsApi();
try {
final result = api_instance.getMetrics();
print(result);
} catch (e) {
print('Exception when calling MetricsApi->getMetrics: $e\n');
}
```
### Parameters
This endpoint does not need any parameter.
### Return type
[**Object**](Object.md)
### Authorization
[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer)
### HTTP request headers
- **Content-Type**: Not defined
- **Accept**: application/json
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)

View File

@@ -12,6 +12,7 @@ Name | Type | Description | Notes
**configFile** | **bool** | | **configFile** | **bool** | |
**facialRecognition** | **bool** | | **facialRecognition** | **bool** | |
**map** | **bool** | | **map** | **bool** | |
**metrics** | **bool** | |
**oauth** | **bool** | | **oauth** | **bool** | |
**oauthAutoLaunch** | **bool** | | **oauthAutoLaunch** | **bool** | |
**passwordLogin** | **bool** | | **passwordLogin** | **bool** | |

View File

@@ -14,6 +14,7 @@ Name | Type | Description | Notes
**logging** | [**SystemConfigLoggingDto**](SystemConfigLoggingDto.md) | | **logging** | [**SystemConfigLoggingDto**](SystemConfigLoggingDto.md) | |
**machineLearning** | [**SystemConfigMachineLearningDto**](SystemConfigMachineLearningDto.md) | | **machineLearning** | [**SystemConfigMachineLearningDto**](SystemConfigMachineLearningDto.md) | |
**map** | [**SystemConfigMapDto**](SystemConfigMapDto.md) | | **map** | [**SystemConfigMapDto**](SystemConfigMapDto.md) | |
**metrics** | [**SystemConfigMetricsDto**](SystemConfigMetricsDto.md) | |
**newVersionCheck** | [**SystemConfigNewVersionCheckDto**](SystemConfigNewVersionCheckDto.md) | | **newVersionCheck** | [**SystemConfigNewVersionCheckDto**](SystemConfigNewVersionCheckDto.md) | |
**oauth** | [**SystemConfigOAuthDto**](SystemConfigOAuthDto.md) | | **oauth** | [**SystemConfigOAuthDto**](SystemConfigOAuthDto.md) | |
**passwordLogin** | [**SystemConfigPasswordLoginDto**](SystemConfigPasswordLoginDto.md) | | **passwordLogin** | [**SystemConfigPasswordLoginDto**](SystemConfigPasswordLoginDto.md) | |

View File

@@ -0,0 +1,15 @@
# openapi.model.SystemConfigMetricsDto
## Load the model package
```dart
import 'package:openapi/api.dart';
```
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**enabled** | **bool** | |
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -37,6 +37,7 @@ part 'api/authentication_api.dart';
part 'api/face_api.dart'; part 'api/face_api.dart';
part 'api/job_api.dart'; part 'api/job_api.dart';
part 'api/library_api.dart'; part 'api/library_api.dart';
part 'api/metrics_api.dart';
part 'api/o_auth_api.dart'; part 'api/o_auth_api.dart';
part 'api/partner_api.dart'; part 'api/partner_api.dart';
part 'api/person_api.dart'; part 'api/person_api.dart';
@@ -173,6 +174,7 @@ part 'model/system_config_library_scan_dto.dart';
part 'model/system_config_logging_dto.dart'; part 'model/system_config_logging_dto.dart';
part 'model/system_config_machine_learning_dto.dart'; part 'model/system_config_machine_learning_dto.dart';
part 'model/system_config_map_dto.dart'; part 'model/system_config_map_dto.dart';
part 'model/system_config_metrics_dto.dart';
part 'model/system_config_new_version_check_dto.dart'; part 'model/system_config_new_version_check_dto.dart';
part 'model/system_config_o_auth_dto.dart'; part 'model/system_config_o_auth_dto.dart';
part 'model/system_config_password_login_dto.dart'; part 'model/system_config_password_login_dto.dart';

59
mobile/openapi/lib/api/metrics_api.dart generated Normal file
View File

@@ -0,0 +1,59 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class MetricsApi {
MetricsApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient;
final ApiClient apiClient;
/// Performs an HTTP 'GET /metrics' operation and returns the [Response].
Future<Response> getMetricsWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/metrics';
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>[];
return apiClient.invokeAPI(
path,
'GET',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
Future<Object?> getMetrics() async {
final response = await getMetricsWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'Object',) as Object;
}
return null;
}
}

View File

@@ -433,6 +433,8 @@ class ApiClient {
return SystemConfigMachineLearningDto.fromJson(value); return SystemConfigMachineLearningDto.fromJson(value);
case 'SystemConfigMapDto': case 'SystemConfigMapDto':
return SystemConfigMapDto.fromJson(value); return SystemConfigMapDto.fromJson(value);
case 'SystemConfigMetricsDto':
return SystemConfigMetricsDto.fromJson(value);
case 'SystemConfigNewVersionCheckDto': case 'SystemConfigNewVersionCheckDto':
return SystemConfigNewVersionCheckDto.fromJson(value); return SystemConfigNewVersionCheckDto.fromJson(value);
case 'SystemConfigOAuthDto': case 'SystemConfigOAuthDto':

View File

@@ -17,6 +17,7 @@ class ServerFeaturesDto {
required this.configFile, required this.configFile,
required this.facialRecognition, required this.facialRecognition,
required this.map, required this.map,
required this.metrics,
required this.oauth, required this.oauth,
required this.oauthAutoLaunch, required this.oauthAutoLaunch,
required this.passwordLogin, required this.passwordLogin,
@@ -34,6 +35,8 @@ class ServerFeaturesDto {
bool map; bool map;
bool metrics;
bool oauth; bool oauth;
bool oauthAutoLaunch; bool oauthAutoLaunch;
@@ -54,6 +57,7 @@ class ServerFeaturesDto {
other.configFile == configFile && other.configFile == configFile &&
other.facialRecognition == facialRecognition && other.facialRecognition == facialRecognition &&
other.map == map && other.map == map &&
other.metrics == metrics &&
other.oauth == oauth && other.oauth == oauth &&
other.oauthAutoLaunch == oauthAutoLaunch && other.oauthAutoLaunch == oauthAutoLaunch &&
other.passwordLogin == passwordLogin && other.passwordLogin == passwordLogin &&
@@ -69,6 +73,7 @@ class ServerFeaturesDto {
(configFile.hashCode) + (configFile.hashCode) +
(facialRecognition.hashCode) + (facialRecognition.hashCode) +
(map.hashCode) + (map.hashCode) +
(metrics.hashCode) +
(oauth.hashCode) + (oauth.hashCode) +
(oauthAutoLaunch.hashCode) + (oauthAutoLaunch.hashCode) +
(passwordLogin.hashCode) + (passwordLogin.hashCode) +
@@ -78,7 +83,7 @@ class ServerFeaturesDto {
(trash.hashCode); (trash.hashCode);
@override @override
String toString() => 'ServerFeaturesDto[clipEncode=$clipEncode, configFile=$configFile, facialRecognition=$facialRecognition, map=$map, oauth=$oauth, oauthAutoLaunch=$oauthAutoLaunch, passwordLogin=$passwordLogin, reverseGeocoding=$reverseGeocoding, search=$search, sidecar=$sidecar, trash=$trash]'; String toString() => 'ServerFeaturesDto[clipEncode=$clipEncode, configFile=$configFile, facialRecognition=$facialRecognition, map=$map, metrics=$metrics, oauth=$oauth, oauthAutoLaunch=$oauthAutoLaunch, passwordLogin=$passwordLogin, reverseGeocoding=$reverseGeocoding, search=$search, sidecar=$sidecar, trash=$trash]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
@@ -86,6 +91,7 @@ class ServerFeaturesDto {
json[r'configFile'] = this.configFile; json[r'configFile'] = this.configFile;
json[r'facialRecognition'] = this.facialRecognition; json[r'facialRecognition'] = this.facialRecognition;
json[r'map'] = this.map; json[r'map'] = this.map;
json[r'metrics'] = this.metrics;
json[r'oauth'] = this.oauth; json[r'oauth'] = this.oauth;
json[r'oauthAutoLaunch'] = this.oauthAutoLaunch; json[r'oauthAutoLaunch'] = this.oauthAutoLaunch;
json[r'passwordLogin'] = this.passwordLogin; json[r'passwordLogin'] = this.passwordLogin;
@@ -108,6 +114,7 @@ class ServerFeaturesDto {
configFile: mapValueOfType<bool>(json, r'configFile')!, configFile: mapValueOfType<bool>(json, r'configFile')!,
facialRecognition: mapValueOfType<bool>(json, r'facialRecognition')!, facialRecognition: mapValueOfType<bool>(json, r'facialRecognition')!,
map: mapValueOfType<bool>(json, r'map')!, map: mapValueOfType<bool>(json, r'map')!,
metrics: mapValueOfType<bool>(json, r'metrics')!,
oauth: mapValueOfType<bool>(json, r'oauth')!, oauth: mapValueOfType<bool>(json, r'oauth')!,
oauthAutoLaunch: mapValueOfType<bool>(json, r'oauthAutoLaunch')!, oauthAutoLaunch: mapValueOfType<bool>(json, r'oauthAutoLaunch')!,
passwordLogin: mapValueOfType<bool>(json, r'passwordLogin')!, passwordLogin: mapValueOfType<bool>(json, r'passwordLogin')!,
@@ -166,6 +173,7 @@ class ServerFeaturesDto {
'configFile', 'configFile',
'facialRecognition', 'facialRecognition',
'map', 'map',
'metrics',
'oauth', 'oauth',
'oauthAutoLaunch', 'oauthAutoLaunch',
'passwordLogin', 'passwordLogin',

View File

@@ -19,6 +19,7 @@ class SystemConfigDto {
required this.logging, required this.logging,
required this.machineLearning, required this.machineLearning,
required this.map, required this.map,
required this.metrics,
required this.newVersionCheck, required this.newVersionCheck,
required this.oauth, required this.oauth,
required this.passwordLogin, required this.passwordLogin,
@@ -41,6 +42,8 @@ class SystemConfigDto {
SystemConfigMapDto map; SystemConfigMapDto map;
SystemConfigMetricsDto metrics;
SystemConfigNewVersionCheckDto newVersionCheck; SystemConfigNewVersionCheckDto newVersionCheck;
SystemConfigOAuthDto oauth; SystemConfigOAuthDto oauth;
@@ -65,6 +68,7 @@ class SystemConfigDto {
other.logging == logging && other.logging == logging &&
other.machineLearning == machineLearning && other.machineLearning == machineLearning &&
other.map == map && other.map == map &&
other.metrics == metrics &&
other.newVersionCheck == newVersionCheck && other.newVersionCheck == newVersionCheck &&
other.oauth == oauth && other.oauth == oauth &&
other.passwordLogin == passwordLogin && other.passwordLogin == passwordLogin &&
@@ -83,6 +87,7 @@ class SystemConfigDto {
(logging.hashCode) + (logging.hashCode) +
(machineLearning.hashCode) + (machineLearning.hashCode) +
(map.hashCode) + (map.hashCode) +
(metrics.hashCode) +
(newVersionCheck.hashCode) + (newVersionCheck.hashCode) +
(oauth.hashCode) + (oauth.hashCode) +
(passwordLogin.hashCode) + (passwordLogin.hashCode) +
@@ -93,7 +98,7 @@ class SystemConfigDto {
(trash.hashCode); (trash.hashCode);
@override @override
String toString() => 'SystemConfigDto[ffmpeg=$ffmpeg, job=$job, library_=$library_, logging=$logging, machineLearning=$machineLearning, map=$map, newVersionCheck=$newVersionCheck, oauth=$oauth, passwordLogin=$passwordLogin, reverseGeocoding=$reverseGeocoding, storageTemplate=$storageTemplate, theme=$theme, thumbnail=$thumbnail, trash=$trash]'; String toString() => 'SystemConfigDto[ffmpeg=$ffmpeg, job=$job, library_=$library_, logging=$logging, machineLearning=$machineLearning, map=$map, metrics=$metrics, newVersionCheck=$newVersionCheck, oauth=$oauth, passwordLogin=$passwordLogin, reverseGeocoding=$reverseGeocoding, storageTemplate=$storageTemplate, theme=$theme, thumbnail=$thumbnail, trash=$trash]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
@@ -103,6 +108,7 @@ class SystemConfigDto {
json[r'logging'] = this.logging; json[r'logging'] = this.logging;
json[r'machineLearning'] = this.machineLearning; json[r'machineLearning'] = this.machineLearning;
json[r'map'] = this.map; json[r'map'] = this.map;
json[r'metrics'] = this.metrics;
json[r'newVersionCheck'] = this.newVersionCheck; json[r'newVersionCheck'] = this.newVersionCheck;
json[r'oauth'] = this.oauth; json[r'oauth'] = this.oauth;
json[r'passwordLogin'] = this.passwordLogin; json[r'passwordLogin'] = this.passwordLogin;
@@ -128,6 +134,7 @@ class SystemConfigDto {
logging: SystemConfigLoggingDto.fromJson(json[r'logging'])!, logging: SystemConfigLoggingDto.fromJson(json[r'logging'])!,
machineLearning: SystemConfigMachineLearningDto.fromJson(json[r'machineLearning'])!, machineLearning: SystemConfigMachineLearningDto.fromJson(json[r'machineLearning'])!,
map: SystemConfigMapDto.fromJson(json[r'map'])!, map: SystemConfigMapDto.fromJson(json[r'map'])!,
metrics: SystemConfigMetricsDto.fromJson(json[r'metrics'])!,
newVersionCheck: SystemConfigNewVersionCheckDto.fromJson(json[r'newVersionCheck'])!, newVersionCheck: SystemConfigNewVersionCheckDto.fromJson(json[r'newVersionCheck'])!,
oauth: SystemConfigOAuthDto.fromJson(json[r'oauth'])!, oauth: SystemConfigOAuthDto.fromJson(json[r'oauth'])!,
passwordLogin: SystemConfigPasswordLoginDto.fromJson(json[r'passwordLogin'])!, passwordLogin: SystemConfigPasswordLoginDto.fromJson(json[r'passwordLogin'])!,
@@ -189,6 +196,7 @@ class SystemConfigDto {
'logging', 'logging',
'machineLearning', 'machineLearning',
'map', 'map',
'metrics',
'newVersionCheck', 'newVersionCheck',
'oauth', 'oauth',
'passwordLogin', 'passwordLogin',

View File

@@ -0,0 +1,98 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class SystemConfigMetricsDto {
/// Returns a new [SystemConfigMetricsDto] instance.
SystemConfigMetricsDto({
required this.enabled,
});
bool enabled;
@override
bool operator ==(Object other) => identical(this, other) || other is SystemConfigMetricsDto &&
other.enabled == enabled;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(enabled.hashCode);
@override
String toString() => 'SystemConfigMetricsDto[enabled=$enabled]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'enabled'] = this.enabled;
return json;
}
/// Returns a new [SystemConfigMetricsDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static SystemConfigMetricsDto? fromJson(dynamic value) {
if (value is Map) {
final json = value.cast<String, dynamic>();
return SystemConfigMetricsDto(
enabled: mapValueOfType<bool>(json, r'enabled')!,
);
}
return null;
}
static List<SystemConfigMetricsDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <SystemConfigMetricsDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = SystemConfigMetricsDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, SystemConfigMetricsDto> mapFromJson(dynamic json) {
final map = <String, SystemConfigMetricsDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = SystemConfigMetricsDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of SystemConfigMetricsDto-objects as value to a dart map
static Map<String, List<SystemConfigMetricsDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<SystemConfigMetricsDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = SystemConfigMetricsDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'enabled',
};
}

View File

@@ -0,0 +1,26 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
import 'package:openapi/api.dart';
import 'package:test/test.dart';
/// tests for MetricsApi
void main() {
// final instance = MetricsApi();
group('tests for MetricsApi', () {
//Future<Object> getMetrics() async
test('test getMetrics', () async {
// TODO
});
});
}

View File

@@ -36,6 +36,11 @@ void main() {
// TODO // TODO
}); });
// bool metrics
test('to test the property `metrics`', () async {
// TODO
});
// bool oauth // bool oauth
test('to test the property `oauth`', () async { test('to test the property `oauth`', () async {
// TODO // TODO

View File

@@ -46,6 +46,11 @@ void main() {
// TODO // TODO
}); });
// SystemConfigMetricsDto metrics
test('to test the property `metrics`', () async {
// TODO
});
// SystemConfigNewVersionCheckDto newVersionCheck // SystemConfigNewVersionCheckDto newVersionCheck
test('to test the property `newVersionCheck`', () async { test('to test the property `newVersionCheck`', () async {
// TODO // TODO

View File

@@ -0,0 +1,27 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
import 'package:openapi/api.dart';
import 'package:test/test.dart';
// tests for SystemConfigMetricsDto
void main() {
// final instance = SystemConfigMetricsDto();
group('test SystemConfigMetricsDto', () {
// bool enabled
test('to test the property `enabled`', () async {
// TODO
});
});
}

View File

@@ -3716,6 +3716,38 @@
] ]
} }
}, },
"/metrics": {
"get": {
"operationId": "getMetrics",
"parameters": [],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Metrics"
]
}
},
"/oauth/authorize": { "/oauth/authorize": {
"post": { "post": {
"operationId": "startOAuth", "operationId": "startOAuth",
@@ -8628,6 +8660,9 @@
"map": { "map": {
"type": "boolean" "type": "boolean"
}, },
"metrics": {
"type": "boolean"
},
"oauth": { "oauth": {
"type": "boolean" "type": "boolean"
}, },
@@ -8655,6 +8690,7 @@
"configFile", "configFile",
"facialRecognition", "facialRecognition",
"map", "map",
"metrics",
"trash", "trash",
"reverseGeocoding", "reverseGeocoding",
"oauth", "oauth",
@@ -9027,6 +9063,9 @@
"map": { "map": {
"$ref": "#/components/schemas/SystemConfigMapDto" "$ref": "#/components/schemas/SystemConfigMapDto"
}, },
"metrics": {
"$ref": "#/components/schemas/SystemConfigMetricsDto"
},
"newVersionCheck": { "newVersionCheck": {
"$ref": "#/components/schemas/SystemConfigNewVersionCheckDto" "$ref": "#/components/schemas/SystemConfigNewVersionCheckDto"
}, },
@@ -9057,6 +9096,7 @@
"logging", "logging",
"machineLearning", "machineLearning",
"map", "map",
"metrics",
"newVersionCheck", "newVersionCheck",
"oauth", "oauth",
"passwordLogin", "passwordLogin",
@@ -9279,6 +9319,17 @@
], ],
"type": "object" "type": "object"
}, },
"SystemConfigMetricsDto": {
"properties": {
"enabled": {
"type": "boolean"
}
},
"required": [
"enabled"
],
"type": "object"
},
"SystemConfigNewVersionCheckDto": { "SystemConfigNewVersionCheckDto": {
"properties": { "properties": {
"enabled": { "enabled": {

View File

@@ -5,6 +5,7 @@ import pkg from 'src/../../package.json';
export const AUDIT_LOG_MAX_DURATION = Duration.fromObject({ days: 100 }); export const AUDIT_LOG_MAX_DURATION = Duration.fromObject({ days: 100 });
export const ONE_HOUR = Duration.fromObject({ hours: 1 }); export const ONE_HOUR = Duration.fromObject({ hours: 1 });
export const TWENTY_FOUR_HOURS = Duration.fromObject({ hours: 24 });
export interface IVersion { export interface IVersion {
major: number; major: number;

View File

@@ -11,6 +11,7 @@ import { JobService } from './job';
import { LibraryService } from './library'; import { LibraryService } from './library';
import { MediaService } from './media'; import { MediaService } from './media';
import { MetadataService } from './metadata'; import { MetadataService } from './metadata';
import { MetricsService } from './metrics';
import { PartnerService } from './partner'; import { PartnerService } from './partner';
import { PersonService } from './person'; import { PersonService } from './person';
import { SearchService } from './search'; import { SearchService } from './search';
@@ -34,6 +35,7 @@ const providers: Provider[] = [
JobService, JobService,
MediaService, MediaService,
MetadataService, MetadataService,
MetricsService,
LibraryService, LibraryService,
PersonService, PersonService,
PartnerService, PartnerService,

View File

@@ -14,6 +14,7 @@ export * from './job';
export * from './library'; export * from './library';
export * from './media'; export * from './media';
export * from './metadata'; export * from './metadata';
export * from './metrics';
export * from './partner'; export * from './partner';
export * from './person'; export * from './person';
export * from './repositories'; export * from './repositories';

View File

@@ -81,6 +81,9 @@ export enum JobName {
SIDECAR_DISCOVERY = 'sidecar-discovery', SIDECAR_DISCOVERY = 'sidecar-discovery',
SIDECAR_SYNC = 'sidecar-sync', SIDECAR_SYNC = 'sidecar-sync',
SIDECAR_WRITE = 'sidecar-write', SIDECAR_WRITE = 'sidecar-write',
// metrics
METRICS = 'metrics',
} }
export const JOBS_ASSET_PAGINATION_SIZE = 1000; export const JOBS_ASSET_PAGINATION_SIZE = 1000;
@@ -95,6 +98,7 @@ export const JOBS_TO_QUEUE: Record<JobName, QueueName> = {
[JobName.CLEAN_OLD_AUDIT_LOGS]: QueueName.BACKGROUND_TASK, [JobName.CLEAN_OLD_AUDIT_LOGS]: QueueName.BACKGROUND_TASK,
[JobName.PERSON_CLEANUP]: QueueName.BACKGROUND_TASK, [JobName.PERSON_CLEANUP]: QueueName.BACKGROUND_TASK,
[JobName.PERSON_DELETE]: QueueName.BACKGROUND_TASK, [JobName.PERSON_DELETE]: QueueName.BACKGROUND_TASK,
[JobName.METRICS]: QueueName.BACKGROUND_TASK,
// conversion // conversion
[JobName.QUEUE_VIDEO_CONVERSION]: QueueName.VIDEO_CONVERSION, [JobName.QUEUE_VIDEO_CONVERSION]: QueueName.VIDEO_CONVERSION,

View File

@@ -0,0 +1,2 @@
export * from './metrics.dto';
export * from './metrics.service';

View File

@@ -0,0 +1,31 @@
class MetricsServerInfo {
cpuCount!: number;
cpuModel!: string;
memory!: number;
version!: string;
}
class MetricsAssetCount {
image!: number;
video!: number;
total!: number;
}
export interface Metrics {
serverInfo: {
cpuCount: number;
cpuModel: string;
memory: number;
version: string;
};
assetCount: {
image: number;
video: number;
total: number;
};
}
export class MetricsDto implements Metrics {
serverInfo!: MetricsServerInfo;
assetCount!: MetricsAssetCount;
}

View File

@@ -0,0 +1,60 @@
import { Inject, Injectable } from '@nestjs/common';
import { isDev, serverVersion } from '../domain.constant';
import { JobName } from '../job';
import { ISystemConfigRepository } from '../repositories';
import { IJobRepository } from '../repositories/job.repository';
import { IMetricsRepository } from '../repositories/metrics.repository';
import { FeatureFlag, SystemConfigCore } from '../system-config';
import { MetricsDto } from './metrics.dto';
@Injectable()
export class MetricsService {
private configCore: SystemConfigCore;
constructor(
@Inject(IJobRepository) private jobRepository: IJobRepository,
@Inject(IMetricsRepository) private repository: IMetricsRepository,
@Inject(ISystemConfigRepository) systemConfigRepository: ISystemConfigRepository,
) {
this.configCore = SystemConfigCore.create(systemConfigRepository);
}
async handleQueueMetrics() {
if (!(await this.configCore.hasFeature(FeatureFlag.METRICS))) {
return;
}
// TODO
// if (isDev) {
// return;
// }
await this.jobRepository.queue({ name: JobName.METRICS });
}
async handleSendMetrics() {
const metrics = await this.getMetrics();
await this.repository.sendMetrics(metrics);
return true;
}
async getMetrics() {
const metrics = new MetricsDto();
metrics.serverInfo = {
cpuCount: this.repository.getCpuCount(),
cpuModel: this.repository.getCpuModel(),
memory: this.repository.getMemory(),
version: serverVersion.toString(),
};
metrics.assetCount = {
image: await this.repository.getImageCount(),
video: await this.repository.getVideoCount(),
total: await this.repository.getAssetCount(),
};
return metrics;
}
}

View File

@@ -12,6 +12,7 @@ export * from './library.repository';
export * from './machine-learning.repository'; export * from './machine-learning.repository';
export * from './media.repository'; export * from './media.repository';
export * from './metadata.repository'; export * from './metadata.repository';
export * from './metrics.repository';
export * from './move.repository'; export * from './move.repository';
export * from './partner.repository'; export * from './partner.repository';
export * from './person.repository'; export * from './person.repository';

View File

@@ -89,7 +89,10 @@ export type JobItem =
| { name: JobName.LIBRARY_REMOVE_OFFLINE; data: IEntityJob } | { name: JobName.LIBRARY_REMOVE_OFFLINE; data: IEntityJob }
| { name: JobName.LIBRARY_DELETE; data: IEntityJob } | { name: JobName.LIBRARY_DELETE; data: IEntityJob }
| { name: JobName.LIBRARY_QUEUE_SCAN_ALL; data: IBaseJob } | { name: JobName.LIBRARY_QUEUE_SCAN_ALL; data: IBaseJob }
| { name: JobName.LIBRARY_QUEUE_CLEANUP; data: IBaseJob }; | { name: JobName.LIBRARY_QUEUE_CLEANUP; data: IBaseJob }
// Metrics
| { name: JobName.METRICS; data?: IBaseJob };
export type JobHandler<T = any> = (data: T) => boolean | Promise<boolean>; export type JobHandler<T = any> = (data: T) => boolean | Promise<boolean>;
export type JobItemHandler = (item: JobItem) => Promise<void>; export type JobItemHandler = (item: JobItem) => Promise<void>;

View File

@@ -0,0 +1,13 @@
import { MetricsDto } from '../metrics';
export const IMetricsRepository = 'IMetricsRepository';
export interface IMetricsRepository {
getAssetCount(): Promise<number>;
getCpuCount(): number;
getCpuModel(): string;
getMemory(): number;
getImageCount(): Promise<number>;
getVideoCount(): Promise<number>;
sendMetrics(payload: MetricsDto): Promise<void>;
}

View File

@@ -93,6 +93,7 @@ export class ServerFeaturesDto implements FeatureFlags {
configFile!: boolean; configFile!: boolean;
facialRecognition!: boolean; facialRecognition!: boolean;
map!: boolean; map!: boolean;
metrics!: boolean;
trash!: boolean; trash!: boolean;
reverseGeocoding!: boolean; reverseGeocoding!: boolean;
oauth!: boolean; oauth!: boolean;

View File

@@ -1,5 +1,6 @@
export * from './system-config-ffmpeg.dto'; export * from './system-config-ffmpeg.dto';
export * from './system-config-library.dto'; export * from './system-config-library.dto';
export * from './system-config-metrics.dto';
export * from './system-config-oauth.dto'; export * from './system-config-oauth.dto';
export * from './system-config-password-login.dto'; export * from './system-config-password-login.dto';
export * from './system-config-storage-template.dto'; export * from './system-config-storage-template.dto';

View File

@@ -0,0 +1,6 @@
import { IsBoolean } from 'class-validator';
export class SystemConfigMetricsDto {
@IsBoolean()
enabled!: boolean;
}

View File

@@ -1,6 +1,7 @@
import { SystemConfig } from '@app/infra/entities'; import { SystemConfig } from '@app/infra/entities';
import { Type } from 'class-transformer'; import { Type } from 'class-transformer';
import { IsObject, ValidateNested } from 'class-validator'; import { IsObject, ValidateNested } from 'class-validator';
import { SystemConfigMetricsDto } from '.';
import { SystemConfigFFmpegDto } from './system-config-ffmpeg.dto'; import { SystemConfigFFmpegDto } from './system-config-ffmpeg.dto';
import { SystemConfigJobDto } from './system-config-job.dto'; import { SystemConfigJobDto } from './system-config-job.dto';
import { SystemConfigLibraryDto } from './system-config-library.dto'; import { SystemConfigLibraryDto } from './system-config-library.dto';
@@ -37,6 +38,11 @@ export class SystemConfigDto implements SystemConfig {
@IsObject() @IsObject()
map!: SystemConfigMapDto; map!: SystemConfigMapDto;
@Type(() => SystemConfigMetricsDto)
@ValidateNested()
@IsObject()
metrics!: SystemConfigMetricsDto;
@Type(() => SystemConfigNewVersionCheckDto) @Type(() => SystemConfigNewVersionCheckDto)
@ValidateNested() @ValidateNested()
@IsObject() @IsObject()

View File

@@ -82,6 +82,9 @@ export const defaults = Object.freeze<SystemConfig>({
lightStyle: '', lightStyle: '',
darkStyle: '', darkStyle: '',
}, },
metrics: {
enabled: false,
},
reverseGeocoding: { reverseGeocoding: {
enabled: true, enabled: true,
}, },
@@ -132,6 +135,7 @@ export enum FeatureFlag {
CLIP_ENCODE = 'clipEncode', CLIP_ENCODE = 'clipEncode',
FACIAL_RECOGNITION = 'facialRecognition', FACIAL_RECOGNITION = 'facialRecognition',
MAP = 'map', MAP = 'map',
METRICS = 'metrics',
REVERSE_GEOCODING = 'reverseGeocoding', REVERSE_GEOCODING = 'reverseGeocoding',
SIDECAR = 'sidecar', SIDECAR = 'sidecar',
SEARCH = 'search', SEARCH = 'search',
@@ -204,6 +208,7 @@ export class SystemConfigCore {
[FeatureFlag.CLIP_ENCODE]: mlEnabled && config.machineLearning.clip.enabled, [FeatureFlag.CLIP_ENCODE]: mlEnabled && config.machineLearning.clip.enabled,
[FeatureFlag.FACIAL_RECOGNITION]: mlEnabled && config.machineLearning.facialRecognition.enabled, [FeatureFlag.FACIAL_RECOGNITION]: mlEnabled && config.machineLearning.facialRecognition.enabled,
[FeatureFlag.MAP]: config.map.enabled, [FeatureFlag.MAP]: config.map.enabled,
[FeatureFlag.METRICS]: config.metrics.enabled,
[FeatureFlag.REVERSE_GEOCODING]: config.reverseGeocoding.enabled, [FeatureFlag.REVERSE_GEOCODING]: config.reverseGeocoding.enabled,
[FeatureFlag.SIDECAR]: true, [FeatureFlag.SIDECAR]: true,
[FeatureFlag.SEARCH]: true, [FeatureFlag.SEARCH]: true,

View File

@@ -22,6 +22,7 @@ import {
FaceController, FaceController,
JobController, JobController,
LibraryController, LibraryController,
MetricsController,
OAuthController, OAuthController,
PartnerController, PartnerController,
PersonController, PersonController,
@@ -54,6 +55,7 @@ import { ErrorInterceptor, FileUploadInterceptor } from './interceptors';
FaceController, FaceController,
JobController, JobController,
LibraryController, LibraryController,
MetricsController,
OAuthController, OAuthController,
PartnerController, PartnerController,
SearchController, SearchController,

View File

@@ -3,12 +3,14 @@ import {
DatabaseService, DatabaseService,
JobService, JobService,
LibraryService, LibraryService,
MetricsService,
ONE_HOUR, ONE_HOUR,
OpenGraphTags, OpenGraphTags,
ServerInfoService, ServerInfoService,
SharedLinkService, SharedLinkService,
StorageService, StorageService,
SystemConfigService, SystemConfigService,
TWENTY_FOUR_HOURS,
WEB_ROOT_PATH, WEB_ROOT_PATH,
} from '@app/domain'; } from '@app/domain';
import { ImmichLogger } from '@app/infra/logger'; import { ImmichLogger } from '@app/infra/logger';
@@ -46,6 +48,7 @@ export class AppService {
private configService: SystemConfigService, private configService: SystemConfigService,
private jobService: JobService, private jobService: JobService,
private libraryService: LibraryService, private libraryService: LibraryService,
private metricsService: MetricsService,
private serverService: ServerInfoService, private serverService: ServerInfoService,
private sharedLinkService: SharedLinkService, private sharedLinkService: SharedLinkService,
private storageService: StorageService, private storageService: StorageService,
@@ -57,6 +60,11 @@ export class AppService {
await this.serverService.handleVersionCheck(); await this.serverService.handleVersionCheck();
} }
@Interval(TWENTY_FOUR_HOURS.as('milliseconds'))
async onMetricsSend() {
await this.metricsService.handleQueueMetrics();
}
@Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT) @Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT)
async onNightlyJob() { async onNightlyJob() {
await this.jobService.handleNightlyJobs(); await this.jobService.handleNightlyJobs();

View File

@@ -8,6 +8,7 @@ export * from './auth.controller';
export * from './face.controller'; export * from './face.controller';
export * from './job.controller'; export * from './job.controller';
export * from './library.controller'; export * from './library.controller';
export * from './metrics.controller';
export * from './oauth.controller'; export * from './oauth.controller';
export * from './partner.controller'; export * from './partner.controller';
export * from './person.controller'; export * from './person.controller';

View File

@@ -0,0 +1,18 @@
import { Metrics, MetricsService } from '@app/domain';
import { Controller, Get } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { Authenticated } from '../app.guard';
import { UseValidation } from '../app.utils';
@ApiTags('Metrics')
@Controller('metrics')
@Authenticated()
@UseValidation()
export class MetricsController {
constructor(private service: MetricsService) {}
@Get()
getMetrics(): Promise<Partial<Metrics>> {
return this.service.getMetrics();
}
}

View File

@@ -66,6 +66,8 @@ export enum SystemConfigKey {
MAP_LIGHT_STYLE = 'map.lightStyle', MAP_LIGHT_STYLE = 'map.lightStyle',
MAP_DARK_STYLE = 'map.darkStyle', MAP_DARK_STYLE = 'map.darkStyle',
METRICS_ENABLED = 'metrics.enabled',
REVERSE_GEOCODING_ENABLED = 'reverseGeocoding.enabled', REVERSE_GEOCODING_ENABLED = 'reverseGeocoding.enabled',
NEW_VERSION_CHECK_ENABLED = 'newVersionCheck.enabled', NEW_VERSION_CHECK_ENABLED = 'newVersionCheck.enabled',
@@ -196,6 +198,9 @@ export interface SystemConfig {
lightStyle: string; lightStyle: string;
darkStyle: string; darkStyle: string;
}; };
metrics: {
enabled: boolean;
};
reverseGeocoding: { reverseGeocoding: {
enabled: boolean; enabled: boolean;
}; };

View File

@@ -13,6 +13,7 @@ import {
IMachineLearningRepository, IMachineLearningRepository,
IMediaRepository, IMediaRepository,
IMetadataRepository, IMetadataRepository,
IMetricsRepository,
IMoveRepository, IMoveRepository,
IPartnerRepository, IPartnerRepository,
IPersonRepository, IPersonRepository,
@@ -51,6 +52,7 @@ import {
MachineLearningRepository, MachineLearningRepository,
MediaRepository, MediaRepository,
MetadataRepository, MetadataRepository,
MetricsRepository,
MoveRepository, MoveRepository,
PartnerRepository, PartnerRepository,
PersonRepository, PersonRepository,
@@ -78,6 +80,7 @@ const providers: Provider[] = [
{ provide: IKeyRepository, useClass: ApiKeyRepository }, { provide: IKeyRepository, useClass: ApiKeyRepository },
{ provide: IMachineLearningRepository, useClass: MachineLearningRepository }, { provide: IMachineLearningRepository, useClass: MachineLearningRepository },
{ provide: IMetadataRepository, useClass: MetadataRepository }, { provide: IMetadataRepository, useClass: MetadataRepository },
{ provide: IMetricsRepository, useClass: MetricsRepository },
{ provide: IMoveRepository, useClass: MoveRepository }, { provide: IMoveRepository, useClass: MoveRepository },
{ provide: IPartnerRepository, useClass: PartnerRepository }, { provide: IPartnerRepository, useClass: PartnerRepository },
{ provide: IPersonRepository, useClass: PersonRepository }, { provide: IPersonRepository, useClass: PersonRepository },

View File

@@ -13,6 +13,7 @@ export * from './library.repository';
export * from './machine-learning.repository'; export * from './machine-learning.repository';
export * from './media.repository'; export * from './media.repository';
export * from './metadata.repository'; export * from './metadata.repository';
export * from './metrics.repository';
export * from './move.repository'; export * from './move.repository';
export * from './partner.repository'; export * from './partner.repository';
export * from './person.repository'; export * from './person.repository';

View File

@@ -0,0 +1,40 @@
import { MetricsDto } from '@app/domain/metrics';
import { IMetricsRepository } from '@app/domain/repositories/metrics.repository';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import axios from 'axios';
import os from 'os';
import { Repository } from 'typeorm';
import { AssetEntity, AssetType } from '../entities';
@Injectable()
export class MetricsRepository implements IMetricsRepository {
constructor(@InjectRepository(AssetEntity) private assetRepository: Repository<AssetEntity>) {}
async sendMetrics(payload: MetricsDto): Promise<void> {
await axios.post('IMMICH-DATA-DOMAIN', payload);
}
getAssetCount() {
return this.assetRepository.count();
}
getCpuCount() {
return os.cpus().length;
}
getCpuModel() {
return os.cpus()[0].model;
}
getMemory() {
return os.totalmem();
}
getImageCount() {
return this.assetRepository.count({ where: { isVisible: true, type: AssetType.IMAGE } });
}
getVideoCount() {
return this.assetRepository.count({ where: { isVisible: true, type: AssetType.VIDEO } });
}
}

View File

@@ -8,6 +8,7 @@ import {
LibraryService, LibraryService,
MediaService, MediaService,
MetadataService, MetadataService,
MetricsService,
PersonService, PersonService,
SmartInfoService, SmartInfoService,
StorageService, StorageService,
@@ -27,6 +28,7 @@ export class AppService {
private libraryService: LibraryService, private libraryService: LibraryService,
private mediaService: MediaService, private mediaService: MediaService,
private metadataService: MetadataService, private metadataService: MetadataService,
private metricsService: MetricsService,
private personService: PersonService, private personService: PersonService,
private smartInfoService: SmartInfoService, private smartInfoService: SmartInfoService,
private storageTemplateService: StorageTemplateService, private storageTemplateService: StorageTemplateService,
@@ -60,6 +62,7 @@ export class AppService {
[JobName.VIDEO_CONVERSION]: (data) => this.mediaService.handleVideoConversion(data), [JobName.VIDEO_CONVERSION]: (data) => this.mediaService.handleVideoConversion(data),
[JobName.QUEUE_METADATA_EXTRACTION]: (data) => this.metadataService.handleQueueMetadataExtraction(data), [JobName.QUEUE_METADATA_EXTRACTION]: (data) => this.metadataService.handleQueueMetadataExtraction(data),
[JobName.METADATA_EXTRACTION]: (data) => this.metadataService.handleMetadataExtraction(data), [JobName.METADATA_EXTRACTION]: (data) => this.metadataService.handleMetadataExtraction(data),
[JobName.METRICS]: () => this.metricsService.handleSendMetrics(),
[JobName.LINK_LIVE_PHOTOS]: (data) => this.metadataService.handleLivePhotoLinking(data), [JobName.LINK_LIVE_PHOTOS]: (data) => this.metadataService.handleLivePhotoLinking(data),
[JobName.QUEUE_RECOGNIZE_FACES]: (data) => this.personService.handleQueueRecognizeFaces(data), [JobName.QUEUE_RECOGNIZE_FACES]: (data) => this.personService.handleQueueRecognizeFaces(data),
[JobName.RECOGNIZE_FACES]: (data) => this.personService.handleRecognizeFaces(data), [JobName.RECOGNIZE_FACES]: (data) => this.personService.handleRecognizeFaces(data),

View File

@@ -22,6 +22,7 @@ import {
AuditApi, AuditApi,
ActivityApi, ActivityApi,
FaceApi, FaceApi,
MetricsApi,
} from './open-api'; } from './open-api';
import { BASE_PATH } from './open-api/base'; import { BASE_PATH } from './open-api/base';
import { DUMMY_BASE_URL, toPathString } from './open-api/common'; import { DUMMY_BASE_URL, toPathString } from './open-api/common';
@@ -37,6 +38,7 @@ class ImmichApi {
public faceApi: FaceApi; public faceApi: FaceApi;
public jobApi: JobApi; public jobApi: JobApi;
public keyApi: APIKeyApi; public keyApi: APIKeyApi;
public metricsApi: MetricsApi;
public oauthApi: OAuthApi; public oauthApi: OAuthApi;
public partnerApi: PartnerApi; public partnerApi: PartnerApi;
public searchApi: SearchApi; public searchApi: SearchApi;
@@ -65,6 +67,7 @@ class ImmichApi {
this.faceApi = new FaceApi(this.config); this.faceApi = new FaceApi(this.config);
this.jobApi = new JobApi(this.config); this.jobApi = new JobApi(this.config);
this.keyApi = new APIKeyApi(this.config); this.keyApi = new APIKeyApi(this.config);
this.metricsApi = new MetricsApi(this.config);
this.oauthApi = new OAuthApi(this.config); this.oauthApi = new OAuthApi(this.config);
this.partnerApi = new PartnerApi(this.config); this.partnerApi = new PartnerApi(this.config);
this.searchApi = new SearchApi(this.config); this.searchApi = new SearchApi(this.config);

View File

@@ -3062,6 +3062,12 @@ export interface ServerFeaturesDto {
* @memberof ServerFeaturesDto * @memberof ServerFeaturesDto
*/ */
'map': boolean; 'map': boolean;
/**
*
* @type {boolean}
* @memberof ServerFeaturesDto
*/
'metrics': boolean;
/** /**
* *
* @type {boolean} * @type {boolean}
@@ -3566,6 +3572,12 @@ export interface SystemConfigDto {
* @memberof SystemConfigDto * @memberof SystemConfigDto
*/ */
'map': SystemConfigMapDto; 'map': SystemConfigMapDto;
/**
*
* @type {SystemConfigMetricsDto}
* @memberof SystemConfigDto
*/
'metrics': SystemConfigMetricsDto;
/** /**
* *
* @type {SystemConfigNewVersionCheckDto} * @type {SystemConfigNewVersionCheckDto}
@@ -3908,6 +3920,19 @@ export interface SystemConfigMapDto {
*/ */
'lightStyle': string; 'lightStyle': string;
} }
/**
*
* @export
* @interface SystemConfigMetricsDto
*/
export interface SystemConfigMetricsDto {
/**
*
* @type {boolean}
* @memberof SystemConfigMetricsDto
*/
'enabled': boolean;
}
/** /**
* *
* @export * @export
@@ -12692,6 +12717,109 @@ export class LibraryApi extends BaseAPI {
} }
/**
* MetricsApi - axios parameter creator
* @export
*/
export const MetricsApiAxiosParamCreator = function (configuration?: Configuration) {
return {
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getMetrics: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/metrics`;
// 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;
// authentication cookie required
// authentication api_key required
await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration)
// authentication bearer required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
}
};
/**
* MetricsApi - functional programming interface
* @export
*/
export const MetricsApiFp = function(configuration?: Configuration) {
const localVarAxiosParamCreator = MetricsApiAxiosParamCreator(configuration)
return {
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async getMetrics(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getMetrics(options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
}
};
/**
* MetricsApi - factory interface
* @export
*/
export const MetricsApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
const localVarFp = MetricsApiFp(configuration)
return {
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getMetrics(options?: AxiosRequestConfig): AxiosPromise<object> {
return localVarFp.getMetrics(options).then((request) => request(axios, basePath));
},
};
};
/**
* MetricsApi - object-oriented interface
* @export
* @class MetricsApi
* @extends {BaseAPI}
*/
export class MetricsApi extends BaseAPI {
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof MetricsApi
*/
public getMetrics(options?: AxiosRequestConfig) {
return MetricsApiFp(this.configuration).getMetrics(options).then((request) => request(this.axios, this.basePath));
}
}
/** /**
* OAuthApi - axios parameter creator * OAuthApi - axios parameter creator
* @export * @export

View File

@@ -0,0 +1,118 @@
<script lang="ts">
import {
notificationController,
NotificationType,
} from '$lib/components/shared-components/notification/notification';
import { handleError } from '$lib/utils/handle-error';
import { api, SystemConfigMetricsDto } from '@api';
import { isEqual } from 'lodash-es';
import { fade } from 'svelte/transition';
import SettingButtonsRow from '../setting-buttons-row.svelte';
import SettingSwitch from '../setting-switch.svelte';
import type { ResetOptions } from '$lib/utils/dipatch';
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
export let config: SystemConfigMetricsDto; // this is the config that is being edited
export let disabled = false;
let savedConfig: SystemConfigMetricsDto;
let defaultConfig: SystemConfigMetricsDto;
const handleReset = (detail: ResetOptions) => {
if (detail.default) {
resetToDefault();
} else {
reset();
}
};
async function refreshConfig() {
[savedConfig, defaultConfig] = await Promise.all([
api.systemConfigApi.getConfig().then((res) => res.data.metrics),
api.systemConfigApi.getConfigDefaults().then((res) => res.data.metrics),
]);
}
async function saveSetting() {
try {
const { data: current } = await api.systemConfigApi.getConfig();
const { data: updated } = await api.systemConfigApi.updateConfig({
systemConfigDto: {
...current,
metrics: {
enabled: config.enabled,
},
},
});
config = { ...updated.metrics };
savedConfig = { ...updated.metrics };
notificationController.show({ message: 'Settings saved', type: NotificationType.Info });
} catch (error) {
handleError(error, 'Unable to save settings');
}
}
async function reset() {
const { data: resetConfig } = await api.systemConfigApi.getConfig();
config = { ...resetConfig.metrics };
savedConfig = { ...resetConfig.metrics };
notificationController.show({
message: 'Reset settings to the recent saved settings',
type: NotificationType.Info,
});
}
async function resetToDefault() {
const { data: configs } = await api.systemConfigApi.getConfigDefaults();
config = { ...configs.metrics };
defaultConfig = { ...configs.metrics };
notificationController.show({
message: 'Reset map settings to default',
type: NotificationType.Info,
});
}
function getSharedMetrics() {
return api.metricsApi.getMetrics().then((response) => response.data);
}
</script>
<div class="mt-2">
{#await refreshConfig() then}
<div in:fade={{ duration: 500 }}>
<form autocomplete="off" on:submit|preventDefault>
<div class="ml-4 mt-4 flex flex-col gap-4">
<SettingSwitch
title="ENABLED"
{disabled}
subtitle="Enable sharing of anonymous usage data"
bind:checked={config.enabled}
/>
{#if config.enabled}
{#await getSharedMetrics()}
<LoadingSpinner />
{:then metrics}
<div class="mt-2 rounded-lg bg-gray-200 p-4 text-xs dark:bg-gray-700 dark:text-immich-dark-fg">
<pre><code>{JSON.stringify(metrics, null, 2)}</code></pre>
</div>
{/await}
{/if}
<SettingButtonsRow
on:reset={({ detail }) => handleReset(detail)}
on:save={saveSetting}
showResetToDefault={!isEqual(savedConfig, defaultConfig)}
{disabled}
/>
</div>
</form>
</div>
{/await}
</div>

View File

@@ -7,8 +7,9 @@
import Icon from '$lib/components/elements/icon.svelte'; import Icon from '$lib/components/elements/icon.svelte';
import { asByteUnitString } from '../../utils/byte-units'; import { asByteUnitString } from '../../utils/byte-units';
import LoadingSpinner from './loading-spinner.svelte'; import LoadingSpinner from './loading-spinner.svelte';
import { mdiCloud, mdiDns } from '@mdi/js'; import { mdiCloud, mdiDns, mdiEyeOutline } from '@mdi/js';
import { serverInfoStore } from '$lib/stores/server-info.store'; import { serverInfoStore } from '$lib/stores/server-info.store';
import { featureFlags } from '$lib/stores/server-config.store';
const { serverVersion, connected } = websocketStore; const { serverVersion, connected } = websocketStore;
@@ -70,7 +71,12 @@
<Icon path={mdiDns} size={'24'} /> <Icon path={mdiDns} size={'24'} />
</div> </div>
<div class="hidden text-xs group-hover:sm:block md:block"> <div class="hidden text-xs group-hover:sm:block md:block">
<p class="text-sm font-medium text-immich-primary dark:text-immich-dark-primary">Server</p> <div class="text-sm font-medium text-immich-primary dark:text-immich-dark-primary flex justify-between">
<p>Server</p>
{#if $featureFlags.metrics}
<Icon path={mdiEyeOutline} title="This instance is currently sharing metrics with Immich." />
{/if}
</div>
<div class="mt-2 flex justify-between justify-items-center"> <div class="mt-2 flex justify-between justify-items-center">
<p>Status</p> <p>Status</p>

View File

@@ -4,6 +4,7 @@
import JobSettings from '$lib/components/admin-page/settings/job-settings/job-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 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 MapSettings from '$lib/components/admin-page/settings/map-settings/map-settings.svelte';
import MetricsSettings from '$lib/components/admin-page/settings/metrics-settings/metrics-settings.svelte';
import OAuthSettings from '$lib/components/admin-page/settings/oauth/oauth-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 PasswordLoginSettings from '$lib/components/admin-page/settings/password-login/password-login-settings.svelte';
import SettingAccordion from '$lib/components/admin-page/settings/setting-accordion.svelte'; import SettingAccordion from '$lib/components/admin-page/settings/setting-accordion.svelte';
@@ -63,6 +64,13 @@
<section id="setting-content" class="flex place-content-center sm:mx-4"> <section id="setting-content" class="flex place-content-center sm:mx-4">
<section class="w-full pb-28 sm:w-5/6 md:w-[850px]"> <section class="w-full pb-28 sm:w-5/6 md:w-[850px]">
<SettingAccordion
title="Anonymous Usage Data Settings"
subtitle="Manage if you want to share anonymous usage data with Immich"
>
<MetricsSettings disabled={$featureFlags.configFile} config={configs.metrics} />
</SettingAccordion>
<SettingAccordion <SettingAccordion
title="Job Settings" title="Job Settings"
subtitle="Manage job concurrency" subtitle="Manage job concurrency"