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
*/
'map': boolean;
/**
*
* @type {boolean}
* @memberof ServerFeaturesDto
*/
'metrics': boolean;
/**
*
* @type {boolean}
@@ -3566,6 +3572,12 @@ export interface SystemConfigDto {
* @memberof SystemConfigDto
*/
'map': SystemConfigMapDto;
/**
*
* @type {SystemConfigMetricsDto}
* @memberof SystemConfigDto
*/
'metrics': SystemConfigMetricsDto;
/**
*
* @type {SystemConfigNewVersionCheckDto}
@@ -3908,6 +3920,19 @@ export interface SystemConfigMapDto {
*/
'lightStyle': string;
}
/**
*
* @export
* @interface SystemConfigMetricsDto
*/
export interface SystemConfigMetricsDto {
/**
*
* @type {boolean}
* @memberof SystemConfigMetricsDto
*/
'enabled': boolean;
}
/**
*
* @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
* @export

View File

@@ -89,6 +89,7 @@ doc/MapMarkerResponseDto.md
doc/MapTheme.md
doc/MemoryLaneResponseDto.md
doc/MergePersonDto.md
doc/MetricsApi.md
doc/ModelType.md
doc/OAuthApi.md
doc/OAuthAuthorizeResponseDto.md
@@ -145,6 +146,7 @@ doc/SystemConfigLibraryScanDto.md
doc/SystemConfigLoggingDto.md
doc/SystemConfigMachineLearningDto.md
doc/SystemConfigMapDto.md
doc/SystemConfigMetricsDto.md
doc/SystemConfigNewVersionCheckDto.md
doc/SystemConfigOAuthDto.md
doc/SystemConfigPasswordLoginDto.md
@@ -188,6 +190,7 @@ lib/api/authentication_api.dart
lib/api/face_api.dart
lib/api/job_api.dart
lib/api/library_api.dart
lib/api/metrics_api.dart
lib/api/o_auth_api.dart
lib/api/partner_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_machine_learning_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_o_auth_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/memory_lane_response_dto_test.dart
test/merge_person_dto_test.dart
test/metrics_api_test.dart
test/model_type_test.dart
test/o_auth_api_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_machine_learning_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_o_auth_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* | [**scanLibrary**](doc//LibraryApi.md#scanlibrary) | **POST** /library/{id}/scan |
*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* | [**generateOAuthConfig**](doc//OAuthApi.md#generateoauthconfig) | **POST** /oauth/config |
*OAuthApi* | [**linkOAuthAccount**](doc//OAuthApi.md#linkoauthaccount) | **POST** /oauth/link |
@@ -337,6 +338,7 @@ Class | Method | HTTP request | Description
- [SystemConfigLoggingDto](doc//SystemConfigLoggingDto.md)
- [SystemConfigMachineLearningDto](doc//SystemConfigMachineLearningDto.md)
- [SystemConfigMapDto](doc//SystemConfigMapDto.md)
- [SystemConfigMetricsDto](doc//SystemConfigMetricsDto.md)
- [SystemConfigNewVersionCheckDto](doc//SystemConfigNewVersionCheckDto.md)
- [SystemConfigOAuthDto](doc//SystemConfigOAuthDto.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** | |
**facialRecognition** | **bool** | |
**map** | **bool** | |
**metrics** | **bool** | |
**oauth** | **bool** | |
**oauthAutoLaunch** | **bool** | |
**passwordLogin** | **bool** | |

View File

@@ -14,6 +14,7 @@ Name | Type | Description | Notes
**logging** | [**SystemConfigLoggingDto**](SystemConfigLoggingDto.md) | |
**machineLearning** | [**SystemConfigMachineLearningDto**](SystemConfigMachineLearningDto.md) | |
**map** | [**SystemConfigMapDto**](SystemConfigMapDto.md) | |
**metrics** | [**SystemConfigMetricsDto**](SystemConfigMetricsDto.md) | |
**newVersionCheck** | [**SystemConfigNewVersionCheckDto**](SystemConfigNewVersionCheckDto.md) | |
**oauth** | [**SystemConfigOAuthDto**](SystemConfigOAuthDto.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/job_api.dart';
part 'api/library_api.dart';
part 'api/metrics_api.dart';
part 'api/o_auth_api.dart';
part 'api/partner_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_machine_learning_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_o_auth_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);
case 'SystemConfigMapDto':
return SystemConfigMapDto.fromJson(value);
case 'SystemConfigMetricsDto':
return SystemConfigMetricsDto.fromJson(value);
case 'SystemConfigNewVersionCheckDto':
return SystemConfigNewVersionCheckDto.fromJson(value);
case 'SystemConfigOAuthDto':

View File

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

View File

@@ -19,6 +19,7 @@ class SystemConfigDto {
required this.logging,
required this.machineLearning,
required this.map,
required this.metrics,
required this.newVersionCheck,
required this.oauth,
required this.passwordLogin,
@@ -41,6 +42,8 @@ class SystemConfigDto {
SystemConfigMapDto map;
SystemConfigMetricsDto metrics;
SystemConfigNewVersionCheckDto newVersionCheck;
SystemConfigOAuthDto oauth;
@@ -65,6 +68,7 @@ class SystemConfigDto {
other.logging == logging &&
other.machineLearning == machineLearning &&
other.map == map &&
other.metrics == metrics &&
other.newVersionCheck == newVersionCheck &&
other.oauth == oauth &&
other.passwordLogin == passwordLogin &&
@@ -83,6 +87,7 @@ class SystemConfigDto {
(logging.hashCode) +
(machineLearning.hashCode) +
(map.hashCode) +
(metrics.hashCode) +
(newVersionCheck.hashCode) +
(oauth.hashCode) +
(passwordLogin.hashCode) +
@@ -93,7 +98,7 @@ class SystemConfigDto {
(trash.hashCode);
@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() {
final json = <String, dynamic>{};
@@ -103,6 +108,7 @@ class SystemConfigDto {
json[r'logging'] = this.logging;
json[r'machineLearning'] = this.machineLearning;
json[r'map'] = this.map;
json[r'metrics'] = this.metrics;
json[r'newVersionCheck'] = this.newVersionCheck;
json[r'oauth'] = this.oauth;
json[r'passwordLogin'] = this.passwordLogin;
@@ -128,6 +134,7 @@ class SystemConfigDto {
logging: SystemConfigLoggingDto.fromJson(json[r'logging'])!,
machineLearning: SystemConfigMachineLearningDto.fromJson(json[r'machineLearning'])!,
map: SystemConfigMapDto.fromJson(json[r'map'])!,
metrics: SystemConfigMetricsDto.fromJson(json[r'metrics'])!,
newVersionCheck: SystemConfigNewVersionCheckDto.fromJson(json[r'newVersionCheck'])!,
oauth: SystemConfigOAuthDto.fromJson(json[r'oauth'])!,
passwordLogin: SystemConfigPasswordLoginDto.fromJson(json[r'passwordLogin'])!,
@@ -189,6 +196,7 @@ class SystemConfigDto {
'logging',
'machineLearning',
'map',
'metrics',
'newVersionCheck',
'oauth',
'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
});
// bool metrics
test('to test the property `metrics`', () async {
// TODO
});
// bool oauth
test('to test the property `oauth`', () async {
// TODO

View File

@@ -46,6 +46,11 @@ void main() {
// TODO
});
// SystemConfigMetricsDto metrics
test('to test the property `metrics`', () async {
// TODO
});
// SystemConfigNewVersionCheckDto newVersionCheck
test('to test the property `newVersionCheck`', () async {
// 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": {
"post": {
"operationId": "startOAuth",
@@ -8628,6 +8660,9 @@
"map": {
"type": "boolean"
},
"metrics": {
"type": "boolean"
},
"oauth": {
"type": "boolean"
},
@@ -8655,6 +8690,7 @@
"configFile",
"facialRecognition",
"map",
"metrics",
"trash",
"reverseGeocoding",
"oauth",
@@ -9027,6 +9063,9 @@
"map": {
"$ref": "#/components/schemas/SystemConfigMapDto"
},
"metrics": {
"$ref": "#/components/schemas/SystemConfigMetricsDto"
},
"newVersionCheck": {
"$ref": "#/components/schemas/SystemConfigNewVersionCheckDto"
},
@@ -9057,6 +9096,7 @@
"logging",
"machineLearning",
"map",
"metrics",
"newVersionCheck",
"oauth",
"passwordLogin",
@@ -9279,6 +9319,17 @@
],
"type": "object"
},
"SystemConfigMetricsDto": {
"properties": {
"enabled": {
"type": "boolean"
}
},
"required": [
"enabled"
],
"type": "object"
},
"SystemConfigNewVersionCheckDto": {
"properties": {
"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 ONE_HOUR = Duration.fromObject({ hours: 1 });
export const TWENTY_FOUR_HOURS = Duration.fromObject({ hours: 24 });
export interface IVersion {
major: number;

View File

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

View File

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

View File

@@ -81,6 +81,9 @@ export enum JobName {
SIDECAR_DISCOVERY = 'sidecar-discovery',
SIDECAR_SYNC = 'sidecar-sync',
SIDECAR_WRITE = 'sidecar-write',
// metrics
METRICS = 'metrics',
}
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.PERSON_CLEANUP]: QueueName.BACKGROUND_TASK,
[JobName.PERSON_DELETE]: QueueName.BACKGROUND_TASK,
[JobName.METRICS]: QueueName.BACKGROUND_TASK,
// 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 './media.repository';
export * from './metadata.repository';
export * from './metrics.repository';
export * from './move.repository';
export * from './partner.repository';
export * from './person.repository';

View File

@@ -89,7 +89,10 @@ export type JobItem =
| { name: JobName.LIBRARY_REMOVE_OFFLINE; data: IEntityJob }
| { name: JobName.LIBRARY_DELETE; data: IEntityJob }
| { 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 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;
facialRecognition!: boolean;
map!: boolean;
metrics!: boolean;
trash!: boolean;
reverseGeocoding!: boolean;
oauth!: boolean;

View File

@@ -1,5 +1,6 @@
export * from './system-config-ffmpeg.dto';
export * from './system-config-library.dto';
export * from './system-config-metrics.dto';
export * from './system-config-oauth.dto';
export * from './system-config-password-login.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 { Type } from 'class-transformer';
import { IsObject, ValidateNested } from 'class-validator';
import { SystemConfigMetricsDto } from '.';
import { SystemConfigFFmpegDto } from './system-config-ffmpeg.dto';
import { SystemConfigJobDto } from './system-config-job.dto';
import { SystemConfigLibraryDto } from './system-config-library.dto';
@@ -37,6 +38,11 @@ export class SystemConfigDto implements SystemConfig {
@IsObject()
map!: SystemConfigMapDto;
@Type(() => SystemConfigMetricsDto)
@ValidateNested()
@IsObject()
metrics!: SystemConfigMetricsDto;
@Type(() => SystemConfigNewVersionCheckDto)
@ValidateNested()
@IsObject()

View File

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

View File

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

View File

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

View File

@@ -8,6 +8,7 @@ export * from './auth.controller';
export * from './face.controller';
export * from './job.controller';
export * from './library.controller';
export * from './metrics.controller';
export * from './oauth.controller';
export * from './partner.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_DARK_STYLE = 'map.darkStyle',
METRICS_ENABLED = 'metrics.enabled',
REVERSE_GEOCODING_ENABLED = 'reverseGeocoding.enabled',
NEW_VERSION_CHECK_ENABLED = 'newVersionCheck.enabled',
@@ -196,6 +198,9 @@ export interface SystemConfig {
lightStyle: string;
darkStyle: string;
};
metrics: {
enabled: boolean;
};
reverseGeocoding: {
enabled: boolean;
};

View File

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

View File

@@ -13,6 +13,7 @@ export * from './library.repository';
export * from './machine-learning.repository';
export * from './media.repository';
export * from './metadata.repository';
export * from './metrics.repository';
export * from './move.repository';
export * from './partner.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,
MediaService,
MetadataService,
MetricsService,
PersonService,
SmartInfoService,
StorageService,
@@ -27,6 +28,7 @@ export class AppService {
private libraryService: LibraryService,
private mediaService: MediaService,
private metadataService: MetadataService,
private metricsService: MetricsService,
private personService: PersonService,
private smartInfoService: SmartInfoService,
private storageTemplateService: StorageTemplateService,
@@ -60,6 +62,7 @@ export class AppService {
[JobName.VIDEO_CONVERSION]: (data) => this.mediaService.handleVideoConversion(data),
[JobName.QUEUE_METADATA_EXTRACTION]: (data) => this.metadataService.handleQueueMetadataExtraction(data),
[JobName.METADATA_EXTRACTION]: (data) => this.metadataService.handleMetadataExtraction(data),
[JobName.METRICS]: () => this.metricsService.handleSendMetrics(),
[JobName.LINK_LIVE_PHOTOS]: (data) => this.metadataService.handleLivePhotoLinking(data),
[JobName.QUEUE_RECOGNIZE_FACES]: (data) => this.personService.handleQueueRecognizeFaces(data),
[JobName.RECOGNIZE_FACES]: (data) => this.personService.handleRecognizeFaces(data),

View File

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

View File

@@ -3062,6 +3062,12 @@ export interface ServerFeaturesDto {
* @memberof ServerFeaturesDto
*/
'map': boolean;
/**
*
* @type {boolean}
* @memberof ServerFeaturesDto
*/
'metrics': boolean;
/**
*
* @type {boolean}
@@ -3566,6 +3572,12 @@ export interface SystemConfigDto {
* @memberof SystemConfigDto
*/
'map': SystemConfigMapDto;
/**
*
* @type {SystemConfigMetricsDto}
* @memberof SystemConfigDto
*/
'metrics': SystemConfigMetricsDto;
/**
*
* @type {SystemConfigNewVersionCheckDto}
@@ -3908,6 +3920,19 @@ export interface SystemConfigMapDto {
*/
'lightStyle': string;
}
/**
*
* @export
* @interface SystemConfigMetricsDto
*/
export interface SystemConfigMetricsDto {
/**
*
* @type {boolean}
* @memberof SystemConfigMetricsDto
*/
'enabled': boolean;
}
/**
*
* @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
* @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 { asByteUnitString } from '../../utils/byte-units';
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 { featureFlags } from '$lib/stores/server-config.store';
const { serverVersion, connected } = websocketStore;
@@ -70,7 +71,12 @@
<Icon path={mdiDns} size={'24'} />
</div>
<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">
<p>Status</p>

View File

@@ -4,6 +4,7 @@
import JobSettings from '$lib/components/admin-page/settings/job-settings/job-settings.svelte';
import MachineLearningSettings from '$lib/components/admin-page/settings/machine-learning-settings/machine-learning-settings.svelte';
import MapSettings from '$lib/components/admin-page/settings/map-settings/map-settings.svelte';
import 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 PasswordLoginSettings from '$lib/components/admin-page/settings/password-login/password-login-settings.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 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
title="Job Settings"
subtitle="Manage job concurrency"