mirror of
https://github.com/immich-app/immich.git
synced 2025-06-03 05:34:32 -04:00
feat(web,server)!: runtime log level (#5672)
* feat: change log level at runtime * chore: open api * chore: prefer env over runtime * chore: remove default env value
This commit is contained in:
parent
f2270ad757
commit
9768931275
45
cli/src/api/open-api/api.ts
generated
45
cli/src/api/open-api/api.ts
generated
@ -2175,6 +2175,24 @@ export const LibraryType = {
|
|||||||
export type LibraryType = typeof LibraryType[keyof typeof LibraryType];
|
export type LibraryType = typeof LibraryType[keyof typeof LibraryType];
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const LogLevel = {
|
||||||
|
Verbose: 'verbose',
|
||||||
|
Debug: 'debug',
|
||||||
|
Log: 'log',
|
||||||
|
Warn: 'warn',
|
||||||
|
Error: 'error',
|
||||||
|
Fatal: 'fatal'
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type LogLevel = typeof LogLevel[keyof typeof LogLevel];
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
@ -3577,6 +3595,12 @@ export interface SystemConfigDto {
|
|||||||
* @memberof SystemConfigDto
|
* @memberof SystemConfigDto
|
||||||
*/
|
*/
|
||||||
'library': SystemConfigLibraryDto;
|
'library': SystemConfigLibraryDto;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {SystemConfigLoggingDto}
|
||||||
|
* @memberof SystemConfigDto
|
||||||
|
*/
|
||||||
|
'logging': SystemConfigLoggingDto;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {SystemConfigMachineLearningDto}
|
* @type {SystemConfigMachineLearningDto}
|
||||||
@ -3860,6 +3884,27 @@ export interface SystemConfigLibraryScanDto {
|
|||||||
*/
|
*/
|
||||||
'enabled': boolean;
|
'enabled': boolean;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface SystemConfigLoggingDto
|
||||||
|
*/
|
||||||
|
export interface SystemConfigLoggingDto {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
* @memberof SystemConfigLoggingDto
|
||||||
|
*/
|
||||||
|
'enabled': boolean;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {LogLevel}
|
||||||
|
* @memberof SystemConfigLoggingDto
|
||||||
|
*/
|
||||||
|
'level': LogLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
|
6
mobile/openapi/.openapi-generator/FILES
generated
6
mobile/openapi/.openapi-generator/FILES
generated
@ -82,6 +82,7 @@ doc/LibraryApi.md
|
|||||||
doc/LibraryResponseDto.md
|
doc/LibraryResponseDto.md
|
||||||
doc/LibraryStatsResponseDto.md
|
doc/LibraryStatsResponseDto.md
|
||||||
doc/LibraryType.md
|
doc/LibraryType.md
|
||||||
|
doc/LogLevel.md
|
||||||
doc/LoginCredentialDto.md
|
doc/LoginCredentialDto.md
|
||||||
doc/LoginResponseDto.md
|
doc/LoginResponseDto.md
|
||||||
doc/LogoutResponseDto.md
|
doc/LogoutResponseDto.md
|
||||||
@ -142,6 +143,7 @@ doc/SystemConfigFFmpegDto.md
|
|||||||
doc/SystemConfigJobDto.md
|
doc/SystemConfigJobDto.md
|
||||||
doc/SystemConfigLibraryDto.md
|
doc/SystemConfigLibraryDto.md
|
||||||
doc/SystemConfigLibraryScanDto.md
|
doc/SystemConfigLibraryScanDto.md
|
||||||
|
doc/SystemConfigLoggingDto.md
|
||||||
doc/SystemConfigMachineLearningDto.md
|
doc/SystemConfigMachineLearningDto.md
|
||||||
doc/SystemConfigMapDto.md
|
doc/SystemConfigMapDto.md
|
||||||
doc/SystemConfigNewVersionCheckDto.md
|
doc/SystemConfigNewVersionCheckDto.md
|
||||||
@ -274,6 +276,7 @@ lib/model/job_status_dto.dart
|
|||||||
lib/model/library_response_dto.dart
|
lib/model/library_response_dto.dart
|
||||||
lib/model/library_stats_response_dto.dart
|
lib/model/library_stats_response_dto.dart
|
||||||
lib/model/library_type.dart
|
lib/model/library_type.dart
|
||||||
|
lib/model/log_level.dart
|
||||||
lib/model/login_credential_dto.dart
|
lib/model/login_credential_dto.dart
|
||||||
lib/model/login_response_dto.dart
|
lib/model/login_response_dto.dart
|
||||||
lib/model/logout_response_dto.dart
|
lib/model/logout_response_dto.dart
|
||||||
@ -327,6 +330,7 @@ lib/model/system_config_f_fmpeg_dto.dart
|
|||||||
lib/model/system_config_job_dto.dart
|
lib/model/system_config_job_dto.dart
|
||||||
lib/model/system_config_library_dto.dart
|
lib/model/system_config_library_dto.dart
|
||||||
lib/model/system_config_library_scan_dto.dart
|
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_machine_learning_dto.dart
|
||||||
lib/model/system_config_map_dto.dart
|
lib/model/system_config_map_dto.dart
|
||||||
lib/model/system_config_new_version_check_dto.dart
|
lib/model/system_config_new_version_check_dto.dart
|
||||||
@ -439,6 +443,7 @@ test/library_api_test.dart
|
|||||||
test/library_response_dto_test.dart
|
test/library_response_dto_test.dart
|
||||||
test/library_stats_response_dto_test.dart
|
test/library_stats_response_dto_test.dart
|
||||||
test/library_type_test.dart
|
test/library_type_test.dart
|
||||||
|
test/log_level_test.dart
|
||||||
test/login_credential_dto_test.dart
|
test/login_credential_dto_test.dart
|
||||||
test/login_response_dto_test.dart
|
test/login_response_dto_test.dart
|
||||||
test/logout_response_dto_test.dart
|
test/logout_response_dto_test.dart
|
||||||
@ -499,6 +504,7 @@ test/system_config_f_fmpeg_dto_test.dart
|
|||||||
test/system_config_job_dto_test.dart
|
test/system_config_job_dto_test.dart
|
||||||
test/system_config_library_dto_test.dart
|
test/system_config_library_dto_test.dart
|
||||||
test/system_config_library_scan_dto_test.dart
|
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_machine_learning_dto_test.dart
|
||||||
test/system_config_map_dto_test.dart
|
test/system_config_map_dto_test.dart
|
||||||
test/system_config_new_version_check_dto_test.dart
|
test/system_config_new_version_check_dto_test.dart
|
||||||
|
2
mobile/openapi/README.md
generated
2
mobile/openapi/README.md
generated
@ -281,6 +281,7 @@ Class | Method | HTTP request | Description
|
|||||||
- [LibraryResponseDto](doc//LibraryResponseDto.md)
|
- [LibraryResponseDto](doc//LibraryResponseDto.md)
|
||||||
- [LibraryStatsResponseDto](doc//LibraryStatsResponseDto.md)
|
- [LibraryStatsResponseDto](doc//LibraryStatsResponseDto.md)
|
||||||
- [LibraryType](doc//LibraryType.md)
|
- [LibraryType](doc//LibraryType.md)
|
||||||
|
- [LogLevel](doc//LogLevel.md)
|
||||||
- [LoginCredentialDto](doc//LoginCredentialDto.md)
|
- [LoginCredentialDto](doc//LoginCredentialDto.md)
|
||||||
- [LoginResponseDto](doc//LoginResponseDto.md)
|
- [LoginResponseDto](doc//LoginResponseDto.md)
|
||||||
- [LogoutResponseDto](doc//LogoutResponseDto.md)
|
- [LogoutResponseDto](doc//LogoutResponseDto.md)
|
||||||
@ -334,6 +335,7 @@ Class | Method | HTTP request | Description
|
|||||||
- [SystemConfigJobDto](doc//SystemConfigJobDto.md)
|
- [SystemConfigJobDto](doc//SystemConfigJobDto.md)
|
||||||
- [SystemConfigLibraryDto](doc//SystemConfigLibraryDto.md)
|
- [SystemConfigLibraryDto](doc//SystemConfigLibraryDto.md)
|
||||||
- [SystemConfigLibraryScanDto](doc//SystemConfigLibraryScanDto.md)
|
- [SystemConfigLibraryScanDto](doc//SystemConfigLibraryScanDto.md)
|
||||||
|
- [SystemConfigLoggingDto](doc//SystemConfigLoggingDto.md)
|
||||||
- [SystemConfigMachineLearningDto](doc//SystemConfigMachineLearningDto.md)
|
- [SystemConfigMachineLearningDto](doc//SystemConfigMachineLearningDto.md)
|
||||||
- [SystemConfigMapDto](doc//SystemConfigMapDto.md)
|
- [SystemConfigMapDto](doc//SystemConfigMapDto.md)
|
||||||
- [SystemConfigNewVersionCheckDto](doc//SystemConfigNewVersionCheckDto.md)
|
- [SystemConfigNewVersionCheckDto](doc//SystemConfigNewVersionCheckDto.md)
|
||||||
|
14
mobile/openapi/doc/LogLevel.md
generated
Normal file
14
mobile/openapi/doc/LogLevel.md
generated
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# openapi.model.LogLevel
|
||||||
|
|
||||||
|
## Load the model package
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
Name | Type | Description | Notes
|
||||||
|
------------ | ------------- | ------------- | -------------
|
||||||
|
|
||||||
|
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||||
|
|
||||||
|
|
1
mobile/openapi/doc/SystemConfigDto.md
generated
1
mobile/openapi/doc/SystemConfigDto.md
generated
@ -11,6 +11,7 @@ Name | Type | Description | Notes
|
|||||||
**ffmpeg** | [**SystemConfigFFmpegDto**](SystemConfigFFmpegDto.md) | |
|
**ffmpeg** | [**SystemConfigFFmpegDto**](SystemConfigFFmpegDto.md) | |
|
||||||
**job** | [**SystemConfigJobDto**](SystemConfigJobDto.md) | |
|
**job** | [**SystemConfigJobDto**](SystemConfigJobDto.md) | |
|
||||||
**library_** | [**SystemConfigLibraryDto**](SystemConfigLibraryDto.md) | |
|
**library_** | [**SystemConfigLibraryDto**](SystemConfigLibraryDto.md) | |
|
||||||
|
**logging** | [**SystemConfigLoggingDto**](SystemConfigLoggingDto.md) | |
|
||||||
**machineLearning** | [**SystemConfigMachineLearningDto**](SystemConfigMachineLearningDto.md) | |
|
**machineLearning** | [**SystemConfigMachineLearningDto**](SystemConfigMachineLearningDto.md) | |
|
||||||
**map** | [**SystemConfigMapDto**](SystemConfigMapDto.md) | |
|
**map** | [**SystemConfigMapDto**](SystemConfigMapDto.md) | |
|
||||||
**newVersionCheck** | [**SystemConfigNewVersionCheckDto**](SystemConfigNewVersionCheckDto.md) | |
|
**newVersionCheck** | [**SystemConfigNewVersionCheckDto**](SystemConfigNewVersionCheckDto.md) | |
|
||||||
|
16
mobile/openapi/doc/SystemConfigLoggingDto.md
generated
Normal file
16
mobile/openapi/doc/SystemConfigLoggingDto.md
generated
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# openapi.model.SystemConfigLoggingDto
|
||||||
|
|
||||||
|
## Load the model package
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
Name | Type | Description | Notes
|
||||||
|
------------ | ------------- | ------------- | -------------
|
||||||
|
**enabled** | **bool** | |
|
||||||
|
**level** | [**LogLevel**](LogLevel.md) | |
|
||||||
|
|
||||||
|
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||||
|
|
||||||
|
|
2
mobile/openapi/lib/api.dart
generated
2
mobile/openapi/lib/api.dart
generated
@ -117,6 +117,7 @@ part 'model/job_status_dto.dart';
|
|||||||
part 'model/library_response_dto.dart';
|
part 'model/library_response_dto.dart';
|
||||||
part 'model/library_stats_response_dto.dart';
|
part 'model/library_stats_response_dto.dart';
|
||||||
part 'model/library_type.dart';
|
part 'model/library_type.dart';
|
||||||
|
part 'model/log_level.dart';
|
||||||
part 'model/login_credential_dto.dart';
|
part 'model/login_credential_dto.dart';
|
||||||
part 'model/login_response_dto.dart';
|
part 'model/login_response_dto.dart';
|
||||||
part 'model/logout_response_dto.dart';
|
part 'model/logout_response_dto.dart';
|
||||||
@ -170,6 +171,7 @@ part 'model/system_config_f_fmpeg_dto.dart';
|
|||||||
part 'model/system_config_job_dto.dart';
|
part 'model/system_config_job_dto.dart';
|
||||||
part 'model/system_config_library_dto.dart';
|
part 'model/system_config_library_dto.dart';
|
||||||
part 'model/system_config_library_scan_dto.dart';
|
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_machine_learning_dto.dart';
|
||||||
part 'model/system_config_map_dto.dart';
|
part 'model/system_config_map_dto.dart';
|
||||||
part 'model/system_config_new_version_check_dto.dart';
|
part 'model/system_config_new_version_check_dto.dart';
|
||||||
|
4
mobile/openapi/lib/api_client.dart
generated
4
mobile/openapi/lib/api_client.dart
generated
@ -321,6 +321,8 @@ class ApiClient {
|
|||||||
return LibraryStatsResponseDto.fromJson(value);
|
return LibraryStatsResponseDto.fromJson(value);
|
||||||
case 'LibraryType':
|
case 'LibraryType':
|
||||||
return LibraryTypeTypeTransformer().decode(value);
|
return LibraryTypeTypeTransformer().decode(value);
|
||||||
|
case 'LogLevel':
|
||||||
|
return LogLevelTypeTransformer().decode(value);
|
||||||
case 'LoginCredentialDto':
|
case 'LoginCredentialDto':
|
||||||
return LoginCredentialDto.fromJson(value);
|
return LoginCredentialDto.fromJson(value);
|
||||||
case 'LoginResponseDto':
|
case 'LoginResponseDto':
|
||||||
@ -427,6 +429,8 @@ class ApiClient {
|
|||||||
return SystemConfigLibraryDto.fromJson(value);
|
return SystemConfigLibraryDto.fromJson(value);
|
||||||
case 'SystemConfigLibraryScanDto':
|
case 'SystemConfigLibraryScanDto':
|
||||||
return SystemConfigLibraryScanDto.fromJson(value);
|
return SystemConfigLibraryScanDto.fromJson(value);
|
||||||
|
case 'SystemConfigLoggingDto':
|
||||||
|
return SystemConfigLoggingDto.fromJson(value);
|
||||||
case 'SystemConfigMachineLearningDto':
|
case 'SystemConfigMachineLearningDto':
|
||||||
return SystemConfigMachineLearningDto.fromJson(value);
|
return SystemConfigMachineLearningDto.fromJson(value);
|
||||||
case 'SystemConfigMapDto':
|
case 'SystemConfigMapDto':
|
||||||
|
3
mobile/openapi/lib/api_helper.dart
generated
3
mobile/openapi/lib/api_helper.dart
generated
@ -88,6 +88,9 @@ String parameterToString(dynamic value) {
|
|||||||
if (value is LibraryType) {
|
if (value is LibraryType) {
|
||||||
return LibraryTypeTypeTransformer().encode(value).toString();
|
return LibraryTypeTypeTransformer().encode(value).toString();
|
||||||
}
|
}
|
||||||
|
if (value is LogLevel) {
|
||||||
|
return LogLevelTypeTransformer().encode(value).toString();
|
||||||
|
}
|
||||||
if (value is MapTheme) {
|
if (value is MapTheme) {
|
||||||
return MapThemeTypeTransformer().encode(value).toString();
|
return MapThemeTypeTransformer().encode(value).toString();
|
||||||
}
|
}
|
||||||
|
97
mobile/openapi/lib/model/log_level.dart
generated
Normal file
97
mobile/openapi/lib/model/log_level.dart
generated
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
//
|
||||||
|
// 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 LogLevel {
|
||||||
|
/// Instantiate a new enum with the provided [value].
|
||||||
|
const LogLevel._(this.value);
|
||||||
|
|
||||||
|
/// The underlying value of this enum member.
|
||||||
|
final String value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => value;
|
||||||
|
|
||||||
|
String toJson() => value;
|
||||||
|
|
||||||
|
static const verbose = LogLevel._(r'verbose');
|
||||||
|
static const debug = LogLevel._(r'debug');
|
||||||
|
static const log = LogLevel._(r'log');
|
||||||
|
static const warn = LogLevel._(r'warn');
|
||||||
|
static const error = LogLevel._(r'error');
|
||||||
|
static const fatal = LogLevel._(r'fatal');
|
||||||
|
|
||||||
|
/// List of all possible values in this [enum][LogLevel].
|
||||||
|
static const values = <LogLevel>[
|
||||||
|
verbose,
|
||||||
|
debug,
|
||||||
|
log,
|
||||||
|
warn,
|
||||||
|
error,
|
||||||
|
fatal,
|
||||||
|
];
|
||||||
|
|
||||||
|
static LogLevel? fromJson(dynamic value) => LogLevelTypeTransformer().decode(value);
|
||||||
|
|
||||||
|
static List<LogLevel>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <LogLevel>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = LogLevel.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transformation class that can [encode] an instance of [LogLevel] to String,
|
||||||
|
/// and [decode] dynamic data back to [LogLevel].
|
||||||
|
class LogLevelTypeTransformer {
|
||||||
|
factory LogLevelTypeTransformer() => _instance ??= const LogLevelTypeTransformer._();
|
||||||
|
|
||||||
|
const LogLevelTypeTransformer._();
|
||||||
|
|
||||||
|
String encode(LogLevel data) => data.value;
|
||||||
|
|
||||||
|
/// Decodes a [dynamic value][data] to a LogLevel.
|
||||||
|
///
|
||||||
|
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
|
||||||
|
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
|
||||||
|
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
|
||||||
|
///
|
||||||
|
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
|
||||||
|
/// and users are still using an old app with the old code.
|
||||||
|
LogLevel? decode(dynamic data, {bool allowNull = true}) {
|
||||||
|
if (data != null) {
|
||||||
|
switch (data) {
|
||||||
|
case r'verbose': return LogLevel.verbose;
|
||||||
|
case r'debug': return LogLevel.debug;
|
||||||
|
case r'log': return LogLevel.log;
|
||||||
|
case r'warn': return LogLevel.warn;
|
||||||
|
case r'error': return LogLevel.error;
|
||||||
|
case r'fatal': return LogLevel.fatal;
|
||||||
|
default:
|
||||||
|
if (!allowNull) {
|
||||||
|
throw ArgumentError('Unknown enum value to decode: $data');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Singleton [LogLevelTypeTransformer] instance.
|
||||||
|
static LogLevelTypeTransformer? _instance;
|
||||||
|
}
|
||||||
|
|
10
mobile/openapi/lib/model/system_config_dto.dart
generated
10
mobile/openapi/lib/model/system_config_dto.dart
generated
@ -16,6 +16,7 @@ class SystemConfigDto {
|
|||||||
required this.ffmpeg,
|
required this.ffmpeg,
|
||||||
required this.job,
|
required this.job,
|
||||||
required this.library_,
|
required this.library_,
|
||||||
|
required this.logging,
|
||||||
required this.machineLearning,
|
required this.machineLearning,
|
||||||
required this.map,
|
required this.map,
|
||||||
required this.newVersionCheck,
|
required this.newVersionCheck,
|
||||||
@ -34,6 +35,8 @@ class SystemConfigDto {
|
|||||||
|
|
||||||
SystemConfigLibraryDto library_;
|
SystemConfigLibraryDto library_;
|
||||||
|
|
||||||
|
SystemConfigLoggingDto logging;
|
||||||
|
|
||||||
SystemConfigMachineLearningDto machineLearning;
|
SystemConfigMachineLearningDto machineLearning;
|
||||||
|
|
||||||
SystemConfigMapDto map;
|
SystemConfigMapDto map;
|
||||||
@ -59,6 +62,7 @@ class SystemConfigDto {
|
|||||||
other.ffmpeg == ffmpeg &&
|
other.ffmpeg == ffmpeg &&
|
||||||
other.job == job &&
|
other.job == job &&
|
||||||
other.library_ == library_ &&
|
other.library_ == library_ &&
|
||||||
|
other.logging == logging &&
|
||||||
other.machineLearning == machineLearning &&
|
other.machineLearning == machineLearning &&
|
||||||
other.map == map &&
|
other.map == map &&
|
||||||
other.newVersionCheck == newVersionCheck &&
|
other.newVersionCheck == newVersionCheck &&
|
||||||
@ -76,6 +80,7 @@ class SystemConfigDto {
|
|||||||
(ffmpeg.hashCode) +
|
(ffmpeg.hashCode) +
|
||||||
(job.hashCode) +
|
(job.hashCode) +
|
||||||
(library_.hashCode) +
|
(library_.hashCode) +
|
||||||
|
(logging.hashCode) +
|
||||||
(machineLearning.hashCode) +
|
(machineLearning.hashCode) +
|
||||||
(map.hashCode) +
|
(map.hashCode) +
|
||||||
(newVersionCheck.hashCode) +
|
(newVersionCheck.hashCode) +
|
||||||
@ -88,13 +93,14 @@ class SystemConfigDto {
|
|||||||
(trash.hashCode);
|
(trash.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'SystemConfigDto[ffmpeg=$ffmpeg, job=$job, library_=$library_, 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, 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>{};
|
||||||
json[r'ffmpeg'] = this.ffmpeg;
|
json[r'ffmpeg'] = this.ffmpeg;
|
||||||
json[r'job'] = this.job;
|
json[r'job'] = this.job;
|
||||||
json[r'library'] = this.library_;
|
json[r'library'] = this.library_;
|
||||||
|
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'newVersionCheck'] = this.newVersionCheck;
|
json[r'newVersionCheck'] = this.newVersionCheck;
|
||||||
@ -119,6 +125,7 @@ class SystemConfigDto {
|
|||||||
ffmpeg: SystemConfigFFmpegDto.fromJson(json[r'ffmpeg'])!,
|
ffmpeg: SystemConfigFFmpegDto.fromJson(json[r'ffmpeg'])!,
|
||||||
job: SystemConfigJobDto.fromJson(json[r'job'])!,
|
job: SystemConfigJobDto.fromJson(json[r'job'])!,
|
||||||
library_: SystemConfigLibraryDto.fromJson(json[r'library'])!,
|
library_: SystemConfigLibraryDto.fromJson(json[r'library'])!,
|
||||||
|
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'])!,
|
||||||
newVersionCheck: SystemConfigNewVersionCheckDto.fromJson(json[r'newVersionCheck'])!,
|
newVersionCheck: SystemConfigNewVersionCheckDto.fromJson(json[r'newVersionCheck'])!,
|
||||||
@ -179,6 +186,7 @@ class SystemConfigDto {
|
|||||||
'ffmpeg',
|
'ffmpeg',
|
||||||
'job',
|
'job',
|
||||||
'library',
|
'library',
|
||||||
|
'logging',
|
||||||
'machineLearning',
|
'machineLearning',
|
||||||
'map',
|
'map',
|
||||||
'newVersionCheck',
|
'newVersionCheck',
|
||||||
|
106
mobile/openapi/lib/model/system_config_logging_dto.dart
generated
Normal file
106
mobile/openapi/lib/model/system_config_logging_dto.dart
generated
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
//
|
||||||
|
// 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 SystemConfigLoggingDto {
|
||||||
|
/// Returns a new [SystemConfigLoggingDto] instance.
|
||||||
|
SystemConfigLoggingDto({
|
||||||
|
required this.enabled,
|
||||||
|
required this.level,
|
||||||
|
});
|
||||||
|
|
||||||
|
bool enabled;
|
||||||
|
|
||||||
|
LogLevel level;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is SystemConfigLoggingDto &&
|
||||||
|
other.enabled == enabled &&
|
||||||
|
other.level == level;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(enabled.hashCode) +
|
||||||
|
(level.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'SystemConfigLoggingDto[enabled=$enabled, level=$level]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final json = <String, dynamic>{};
|
||||||
|
json[r'enabled'] = this.enabled;
|
||||||
|
json[r'level'] = this.level;
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [SystemConfigLoggingDto] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static SystemConfigLoggingDto? fromJson(dynamic value) {
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
return SystemConfigLoggingDto(
|
||||||
|
enabled: mapValueOfType<bool>(json, r'enabled')!,
|
||||||
|
level: LogLevel.fromJson(json[r'level'])!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<SystemConfigLoggingDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <SystemConfigLoggingDto>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = SystemConfigLoggingDto.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, SystemConfigLoggingDto> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, SystemConfigLoggingDto>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = SystemConfigLoggingDto.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of SystemConfigLoggingDto-objects as value to a dart map
|
||||||
|
static Map<String, List<SystemConfigLoggingDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<SystemConfigLoggingDto>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
json = json.cast<String, dynamic>();
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
map[entry.key] = SystemConfigLoggingDto.listFromJson(entry.value, growable: growable,);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
'enabled',
|
||||||
|
'level',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
21
mobile/openapi/test/log_level_test.dart
generated
Normal file
21
mobile/openapi/test/log_level_test.dart
generated
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
//
|
||||||
|
// 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 LogLevel
|
||||||
|
void main() {
|
||||||
|
|
||||||
|
group('test LogLevel', () {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
5
mobile/openapi/test/system_config_dto_test.dart
generated
5
mobile/openapi/test/system_config_dto_test.dart
generated
@ -31,6 +31,11 @@ void main() {
|
|||||||
// TODO
|
// TODO
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// SystemConfigLoggingDto logging
|
||||||
|
test('to test the property `logging`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
// SystemConfigMachineLearningDto machineLearning
|
// SystemConfigMachineLearningDto machineLearning
|
||||||
test('to test the property `machineLearning`', () async {
|
test('to test the property `machineLearning`', () async {
|
||||||
// TODO
|
// TODO
|
||||||
|
32
mobile/openapi/test/system_config_logging_dto_test.dart
generated
Normal file
32
mobile/openapi/test/system_config_logging_dto_test.dart
generated
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
//
|
||||||
|
// 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 SystemConfigLoggingDto
|
||||||
|
void main() {
|
||||||
|
// final instance = SystemConfigLoggingDto();
|
||||||
|
|
||||||
|
group('test SystemConfigLoggingDto', () {
|
||||||
|
// bool enabled
|
||||||
|
test('to test the property `enabled`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
// LogLevel level
|
||||||
|
test('to test the property `level`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
@ -7949,6 +7949,17 @@
|
|||||||
],
|
],
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"LogLevel": {
|
||||||
|
"enum": [
|
||||||
|
"verbose",
|
||||||
|
"debug",
|
||||||
|
"log",
|
||||||
|
"warn",
|
||||||
|
"error",
|
||||||
|
"fatal"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"LoginCredentialDto": {
|
"LoginCredentialDto": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"email": {
|
"email": {
|
||||||
@ -9039,6 +9050,9 @@
|
|||||||
"library": {
|
"library": {
|
||||||
"$ref": "#/components/schemas/SystemConfigLibraryDto"
|
"$ref": "#/components/schemas/SystemConfigLibraryDto"
|
||||||
},
|
},
|
||||||
|
"logging": {
|
||||||
|
"$ref": "#/components/schemas/SystemConfigLoggingDto"
|
||||||
|
},
|
||||||
"machineLearning": {
|
"machineLearning": {
|
||||||
"$ref": "#/components/schemas/SystemConfigMachineLearningDto"
|
"$ref": "#/components/schemas/SystemConfigMachineLearningDto"
|
||||||
},
|
},
|
||||||
@ -9072,6 +9086,7 @@
|
|||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"ffmpeg",
|
"ffmpeg",
|
||||||
|
"logging",
|
||||||
"machineLearning",
|
"machineLearning",
|
||||||
"map",
|
"map",
|
||||||
"newVersionCheck",
|
"newVersionCheck",
|
||||||
@ -9243,6 +9258,21 @@
|
|||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"SystemConfigLoggingDto": {
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"level": {
|
||||||
|
"$ref": "#/components/schemas/LogLevel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"level",
|
||||||
|
"enabled"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"SystemConfigMachineLearningDto": {
|
"SystemConfigMachineLearningDto": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"classification": {
|
"classification": {
|
||||||
|
@ -153,9 +153,6 @@
|
|||||||
"statements": 90
|
"statements": 90
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"setupFilesAfterEnv": [
|
|
||||||
"<rootDir>/test/setup.ts"
|
|
||||||
],
|
|
||||||
"testEnvironment": "node",
|
"testEnvironment": "node",
|
||||||
"moduleNameMapper": {
|
"moduleNameMapper": {
|
||||||
"^@test(|/.*)$": "<rootDir>/test/$1",
|
"^@test(|/.*)$": "<rootDir>/test/$1",
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { AssetEntity, LibraryType } from '@app/infra/entities';
|
import { AssetEntity, LibraryType } from '@app/infra/entities';
|
||||||
import { BadRequestException, Inject, Logger } from '@nestjs/common';
|
import { ImmichLogger } from '@app/infra/logger';
|
||||||
|
import { BadRequestException, Inject } from '@nestjs/common';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { DateTime, Duration } from 'luxon';
|
import { DateTime, Duration } from 'luxon';
|
||||||
import { extname } from 'path';
|
import { extname } from 'path';
|
||||||
@ -75,7 +76,7 @@ export interface UploadFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class AssetService {
|
export class AssetService {
|
||||||
private logger = new Logger(AssetService.name);
|
private logger = new ImmichLogger(AssetService.name);
|
||||||
private access: AccessCore;
|
private access: AccessCore;
|
||||||
private configCore: SystemConfigCore;
|
private configCore: SystemConfigCore;
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { AssetPathType, DatabaseAction, PersonPathType, UserPathType } from '@app/infra/entities';
|
import { AssetPathType, DatabaseAction, PersonPathType, UserPathType } from '@app/infra/entities';
|
||||||
import { BadRequestException, Inject, Injectable, Logger } from '@nestjs/common';
|
import { ImmichLogger } from '@app/infra/logger';
|
||||||
|
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import { resolve } from 'node:path';
|
import { resolve } from 'node:path';
|
||||||
import { AccessCore, Permission } from '../access';
|
import { AccessCore, Permission } from '../access';
|
||||||
@ -29,7 +30,7 @@ import {
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuditService {
|
export class AuditService {
|
||||||
private access: AccessCore;
|
private access: AccessCore;
|
||||||
private logger = new Logger(AuditService.name);
|
private logger = new ImmichLogger(AuditService.name);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(IAccessRepository) accessRepository: IAccessRepository,
|
@Inject(IAccessRepository) accessRepository: IAccessRepository,
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { SystemConfig, UserEntity } from '@app/infra/entities';
|
import { SystemConfig, UserEntity } from '@app/infra/entities';
|
||||||
|
import { ImmichLogger } from '@app/infra/logger';
|
||||||
import {
|
import {
|
||||||
BadRequestException,
|
BadRequestException,
|
||||||
Inject,
|
Inject,
|
||||||
Injectable,
|
Injectable,
|
||||||
InternalServerErrorException,
|
InternalServerErrorException,
|
||||||
Logger,
|
|
||||||
UnauthorizedException,
|
UnauthorizedException,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import cookieParser from 'cookie';
|
import cookieParser from 'cookie';
|
||||||
@ -68,7 +68,7 @@ interface OAuthProfile extends UserinfoResponse {
|
|||||||
export class AuthService {
|
export class AuthService {
|
||||||
private access: AccessCore;
|
private access: AccessCore;
|
||||||
private configCore: SystemConfigCore;
|
private configCore: SystemConfigCore;
|
||||||
private logger = new Logger(AuthService.name);
|
private logger = new ImmichLogger(AuthService.name);
|
||||||
private userCore: UserCore;
|
private userCore: UserCore;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// TODO: remove nestjs references from domain
|
// TODO: remove nestjs references from domain
|
||||||
import { LogLevel } from '@nestjs/common';
|
import { LogLevel } from '@app/infra/entities';
|
||||||
import { ConfigModuleOptions } from '@nestjs/config';
|
import { ConfigModuleOptions } from '@nestjs/config';
|
||||||
import Joi from 'joi';
|
import Joi from 'joi';
|
||||||
|
|
||||||
@ -18,19 +18,11 @@ export const immichAppConfig: ConfigModuleOptions = {
|
|||||||
DB_PASSWORD: WHEN_DB_URL_SET,
|
DB_PASSWORD: WHEN_DB_URL_SET,
|
||||||
DB_DATABASE_NAME: WHEN_DB_URL_SET,
|
DB_DATABASE_NAME: WHEN_DB_URL_SET,
|
||||||
DB_URL: Joi.string().optional(),
|
DB_URL: Joi.string().optional(),
|
||||||
LOG_LEVEL: Joi.string().optional().valid('simple', 'verbose', 'debug', 'log', 'warn', 'error').default('log'),
|
LOG_LEVEL: Joi.string()
|
||||||
|
.optional()
|
||||||
|
.valid(...Object.values(LogLevel)),
|
||||||
MACHINE_LEARNING_PORT: Joi.number().optional(),
|
MACHINE_LEARNING_PORT: Joi.number().optional(),
|
||||||
MICROSERVICES_PORT: Joi.number().optional(),
|
MICROSERVICES_PORT: Joi.number().optional(),
|
||||||
SERVER_PORT: Joi.number().optional(),
|
SERVER_PORT: Joi.number().optional(),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getLogLevels() {
|
|
||||||
const LOG_LEVELS: LogLevel[] = ['verbose', 'debug', 'log', 'warn', 'error'];
|
|
||||||
let logLevel = process.env.LOG_LEVEL || 'log';
|
|
||||||
if (logLevel === 'simple') {
|
|
||||||
logLevel = 'log';
|
|
||||||
}
|
|
||||||
const logLevelIndex = LOG_LEVELS.indexOf(logLevel as LogLevel);
|
|
||||||
return logLevelIndex === -1 ? [] : LOG_LEVELS.slice(logLevelIndex);
|
|
||||||
}
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { ImmichLogger } from '@app/infra/logger';
|
||||||
import { DynamicModule, Global, Module, ModuleMetadata, Provider } from '@nestjs/common';
|
import { DynamicModule, Global, Module, ModuleMetadata, Provider } from '@nestjs/common';
|
||||||
import { ActivityService } from './activity';
|
import { ActivityService } from './activity';
|
||||||
import { AlbumService } from './album';
|
import { AlbumService } from './album';
|
||||||
@ -43,6 +44,7 @@ const providers: Provider[] = [
|
|||||||
SystemConfigService,
|
SystemConfigService,
|
||||||
TagService,
|
TagService,
|
||||||
UserService,
|
UserService,
|
||||||
|
ImmichLogger,
|
||||||
{
|
{
|
||||||
provide: INITIAL_SYSTEM_CONFIG,
|
provide: INITIAL_SYSTEM_CONFIG,
|
||||||
inject: [SystemConfigService],
|
inject: [SystemConfigService],
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { AssetType } from '@app/infra/entities';
|
import { AssetType } from '@app/infra/entities';
|
||||||
import { BadRequestException, Inject, Injectable, Logger } from '@nestjs/common';
|
import { ImmichLogger } from '@app/infra/logger';
|
||||||
|
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
|
||||||
import { mapAsset } from '../asset';
|
import { mapAsset } from '../asset';
|
||||||
import {
|
import {
|
||||||
ClientEvent,
|
ClientEvent,
|
||||||
@ -18,7 +19,7 @@ import { AllJobStatusResponseDto, JobCommandDto, JobStatusDto } from './job.dto'
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class JobService {
|
export class JobService {
|
||||||
private logger = new Logger(JobService.name);
|
private logger = new ImmichLogger(JobService.name);
|
||||||
private configCore: SystemConfigCore;
|
private configCore: SystemConfigCore;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { AssetType, LibraryType } from '@app/infra/entities';
|
import { AssetType, LibraryType } from '@app/infra/entities';
|
||||||
import { BadRequestException, Inject, Injectable, Logger } from '@nestjs/common';
|
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
|
||||||
import { R_OK } from 'node:constants';
|
import { R_OK } from 'node:constants';
|
||||||
import { Stats } from 'node:fs';
|
import { Stats } from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
@ -10,6 +10,7 @@ import { mimeTypes } from '../domain.constant';
|
|||||||
import { usePagination, validateCronExpression } from '../domain.util';
|
import { usePagination, validateCronExpression } from '../domain.util';
|
||||||
import { IBaseJob, IEntityJob, ILibraryFileJob, ILibraryRefreshJob, JOBS_ASSET_PAGINATION_SIZE, JobName } from '../job';
|
import { IBaseJob, IEntityJob, ILibraryFileJob, ILibraryRefreshJob, JOBS_ASSET_PAGINATION_SIZE, JobName } from '../job';
|
||||||
|
|
||||||
|
import { ImmichLogger } from '@app/infra/logger';
|
||||||
import {
|
import {
|
||||||
IAccessRepository,
|
IAccessRepository,
|
||||||
IAssetRepository,
|
IAssetRepository,
|
||||||
@ -33,7 +34,7 @@ import {
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LibraryService {
|
export class LibraryService {
|
||||||
readonly logger = new Logger(LibraryService.name);
|
readonly logger = new ImmichLogger(LibraryService.name);
|
||||||
private access: AccessCore;
|
private access: AccessCore;
|
||||||
private configCore: SystemConfigCore;
|
private configCore: SystemConfigCore;
|
||||||
|
|
||||||
|
@ -7,7 +7,8 @@ import {
|
|||||||
TranscodePolicy,
|
TranscodePolicy,
|
||||||
VideoCodec,
|
VideoCodec,
|
||||||
} from '@app/infra/entities';
|
} from '@app/infra/entities';
|
||||||
import { Inject, Injectable, Logger, UnsupportedMediaTypeException } from '@nestjs/common';
|
import { ImmichLogger } from '@app/infra/logger';
|
||||||
|
import { Inject, Injectable, UnsupportedMediaTypeException } from '@nestjs/common';
|
||||||
import { usePagination } from '../domain.util';
|
import { usePagination } from '../domain.util';
|
||||||
import { IBaseJob, IEntityJob, JOBS_ASSET_PAGINATION_SIZE, JobName, QueueName } from '../job';
|
import { IBaseJob, IEntityJob, JOBS_ASSET_PAGINATION_SIZE, JobName, QueueName } from '../job';
|
||||||
import {
|
import {
|
||||||
@ -39,7 +40,7 @@ import {
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MediaService {
|
export class MediaService {
|
||||||
private logger = new Logger(MediaService.name);
|
private logger = new ImmichLogger(MediaService.name);
|
||||||
private configCore: SystemConfigCore;
|
private configCore: SystemConfigCore;
|
||||||
private storageCore: StorageCore;
|
private storageCore: StorageCore;
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { AssetEntity, AssetType, ExifEntity } from '@app/infra/entities';
|
import { AssetEntity, AssetType, ExifEntity } from '@app/infra/entities';
|
||||||
import { Inject, Injectable, Logger } from '@nestjs/common';
|
import { ImmichLogger } from '@app/infra/logger';
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { ExifDateTime, Tags } from 'exiftool-vendored';
|
import { ExifDateTime, Tags } from 'exiftool-vendored';
|
||||||
import { firstDateTime } from 'exiftool-vendored/dist/FirstDateTime';
|
import { firstDateTime } from 'exiftool-vendored/dist/FirstDateTime';
|
||||||
import { constants } from 'fs/promises';
|
import { constants } from 'fs/promises';
|
||||||
@ -91,7 +92,7 @@ const validate = <T>(value: T): NonNullable<T> | null => {
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MetadataService {
|
export class MetadataService {
|
||||||
private logger = new Logger(MetadataService.name);
|
private logger = new ImmichLogger(MetadataService.name);
|
||||||
private storageCore: StorageCore;
|
private storageCore: StorageCore;
|
||||||
private configCore: SystemConfigCore;
|
private configCore: SystemConfigCore;
|
||||||
private subscription: Subscription | null = null;
|
private subscription: Subscription | null = null;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { PersonEntity } from '@app/infra/entities';
|
import { PersonEntity } from '@app/infra/entities';
|
||||||
import { PersonPathType } from '@app/infra/entities/move.entity';
|
import { PersonPathType } from '@app/infra/entities/move.entity';
|
||||||
import { BadRequestException, Inject, Injectable, Logger, NotFoundException } from '@nestjs/common';
|
import { ImmichLogger } from '@app/infra/logger';
|
||||||
|
import { BadRequestException, Inject, Injectable, NotFoundException } from '@nestjs/common';
|
||||||
import { AccessCore, Permission } from '../access';
|
import { AccessCore, Permission } from '../access';
|
||||||
import { AssetResponseDto, BulkIdErrorReason, BulkIdResponseDto, mapAsset } from '../asset';
|
import { AssetResponseDto, BulkIdErrorReason, BulkIdResponseDto, mapAsset } from '../asset';
|
||||||
import { AuthDto } from '../auth';
|
import { AuthDto } from '../auth';
|
||||||
@ -45,7 +46,7 @@ export class PersonService {
|
|||||||
private access: AccessCore;
|
private access: AccessCore;
|
||||||
private configCore: SystemConfigCore;
|
private configCore: SystemConfigCore;
|
||||||
private storageCore: StorageCore;
|
private storageCore: StorageCore;
|
||||||
readonly logger = new Logger(PersonService.name);
|
readonly logger = new ImmichLogger(PersonService.name);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(IAccessRepository) accessRepository: IAccessRepository,
|
@Inject(IAccessRepository) accessRepository: IAccessRepository,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { AssetEntity } from '@app/infra/entities';
|
import { AssetEntity } from '@app/infra/entities';
|
||||||
import { Inject, Injectable, Logger } from '@nestjs/common';
|
import { ImmichLogger } from '@app/infra/logger';
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { AssetResponseDto, mapAsset } from '../asset';
|
import { AssetResponseDto, mapAsset } from '../asset';
|
||||||
import { AuthDto } from '../auth';
|
import { AuthDto } from '../auth';
|
||||||
import { PersonResponseDto } from '../person';
|
import { PersonResponseDto } from '../person';
|
||||||
@ -18,7 +19,7 @@ import { SearchResponseDto } from './response-dto';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SearchService {
|
export class SearchService {
|
||||||
private logger = new Logger(SearchService.name);
|
private logger = new ImmichLogger(SearchService.name);
|
||||||
private configCore: SystemConfigCore;
|
private configCore: SystemConfigCore;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Inject, Injectable, Logger } from '@nestjs/common';
|
import { ImmichLogger } from '@app/infra/logger';
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import { ServerVersion, isDev, mimeTypes, serverVersion } from '../domain.constant';
|
import { ServerVersion, isDev, mimeTypes, serverVersion } from '../domain.constant';
|
||||||
import { asHumanReadable } from '../domain.util';
|
import { asHumanReadable } from '../domain.util';
|
||||||
@ -25,7 +26,7 @@ import {
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ServerInfoService {
|
export class ServerInfoService {
|
||||||
private logger = new Logger(ServerInfoService.name);
|
private logger = new ImmichLogger(ServerInfoService.name);
|
||||||
private configCore: SystemConfigCore;
|
private configCore: SystemConfigCore;
|
||||||
private releaseVersion = serverVersion;
|
private releaseVersion = serverVersion;
|
||||||
private releaseVersionCheckedAt: DateTime | null = null;
|
private releaseVersionCheckedAt: DateTime | null = null;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Inject, Injectable, Logger } from '@nestjs/common';
|
import { ImmichLogger } from '@app/infra/logger';
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { setTimeout } from 'timers/promises';
|
import { setTimeout } from 'timers/promises';
|
||||||
import { usePagination } from '../domain.util';
|
import { usePagination } from '../domain.util';
|
||||||
import { IBaseJob, IEntityJob, JOBS_ASSET_PAGINATION_SIZE, JobName, QueueName } from '../job';
|
import { IBaseJob, IEntityJob, JOBS_ASSET_PAGINATION_SIZE, JobName, QueueName } from '../job';
|
||||||
@ -15,7 +16,7 @@ import { SystemConfigCore } from '../system-config';
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class SmartInfoService {
|
export class SmartInfoService {
|
||||||
private configCore: SystemConfigCore;
|
private configCore: SystemConfigCore;
|
||||||
private logger = new Logger(SmartInfoService.name);
|
private logger = new ImmichLogger(SmartInfoService.name);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(IAssetRepository) private assetRepository: IAssetRepository,
|
@Inject(IAssetRepository) private assetRepository: IAssetRepository,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { AssetEntity, AssetPathType, AssetType, SystemConfig } from '@app/infra/entities';
|
import { AssetEntity, AssetPathType, AssetType, SystemConfig } from '@app/infra/entities';
|
||||||
import { Inject, Injectable, Logger } from '@nestjs/common';
|
import { ImmichLogger } from '@app/infra/logger';
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import handlebar from 'handlebars';
|
import handlebar from 'handlebars';
|
||||||
import * as luxon from 'luxon';
|
import * as luxon from 'luxon';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
@ -42,7 +43,7 @@ interface RenderMetadata {
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class StorageTemplateService {
|
export class StorageTemplateService {
|
||||||
private logger = new Logger(StorageTemplateService.name);
|
private logger = new ImmichLogger(StorageTemplateService.name);
|
||||||
private configCore: SystemConfigCore;
|
private configCore: SystemConfigCore;
|
||||||
private storageCore: StorageCore;
|
private storageCore: StorageCore;
|
||||||
private template: {
|
private template: {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { AssetEntity, AssetPathType, PathType, PersonEntity, PersonPathType } from '@app/infra/entities';
|
import { AssetEntity, AssetPathType, PathType, PersonEntity, PersonPathType } from '@app/infra/entities';
|
||||||
import { Logger } from '@nestjs/common';
|
import { ImmichLogger } from '@app/infra/logger';
|
||||||
import { dirname, join, resolve } from 'node:path';
|
import { dirname, join, resolve } from 'node:path';
|
||||||
import { APP_MEDIA_LOCATION } from '../domain.constant';
|
import { APP_MEDIA_LOCATION } from '../domain.constant';
|
||||||
import { IAssetRepository, IMoveRepository, IPersonRepository, IStorageRepository } from '../repositories';
|
import { IAssetRepository, IMoveRepository, IPersonRepository, IStorageRepository } from '../repositories';
|
||||||
@ -24,7 +24,7 @@ type GeneratedAssetPath = AssetPathType.JPEG_THUMBNAIL | AssetPathType.WEBP_THUM
|
|||||||
let instance: StorageCore | null;
|
let instance: StorageCore | null;
|
||||||
|
|
||||||
export class StorageCore {
|
export class StorageCore {
|
||||||
private logger = new Logger(StorageCore.name);
|
private logger = new ImmichLogger(StorageCore.name);
|
||||||
|
|
||||||
private constructor(
|
private constructor(
|
||||||
private assetRepository: IAssetRepository,
|
private assetRepository: IAssetRepository,
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import { Inject, Injectable, Logger } from '@nestjs/common';
|
import { ImmichLogger } from '@app/infra/logger';
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { IDeleteFilesJob } from '../job';
|
import { IDeleteFilesJob } from '../job';
|
||||||
import { IStorageRepository } from '../repositories';
|
import { IStorageRepository } from '../repositories';
|
||||||
import { StorageCore, StorageFolder } from './storage.core';
|
import { StorageCore, StorageFolder } from './storage.core';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class StorageService {
|
export class StorageService {
|
||||||
private logger = new Logger(StorageService.name);
|
private logger = new ImmichLogger(StorageService.name);
|
||||||
|
|
||||||
constructor(@Inject(IStorageRepository) private storageRepository: IStorageRepository) {}
|
constructor(@Inject(IStorageRepository) private storageRepository: IStorageRepository) {}
|
||||||
|
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
import { LogLevel } from '@app/infra/entities';
|
||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsBoolean, IsEnum } from 'class-validator';
|
||||||
|
|
||||||
|
export class SystemConfigLoggingDto {
|
||||||
|
@IsBoolean()
|
||||||
|
enabled!: boolean;
|
||||||
|
|
||||||
|
@ApiProperty({ enum: LogLevel, enumName: 'LogLevel' })
|
||||||
|
@IsEnum(LogLevel)
|
||||||
|
level!: LogLevel;
|
||||||
|
}
|
@ -4,6 +4,7 @@ import { IsObject, ValidateNested } from 'class-validator';
|
|||||||
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';
|
||||||
|
import { SystemConfigLoggingDto } from './system-config-logging.dto';
|
||||||
import { SystemConfigMachineLearningDto } from './system-config-machine-learning.dto';
|
import { SystemConfigMachineLearningDto } from './system-config-machine-learning.dto';
|
||||||
import { SystemConfigMapDto } from './system-config-map.dto';
|
import { SystemConfigMapDto } from './system-config-map.dto';
|
||||||
import { SystemConfigNewVersionCheckDto } from './system-config-new-version-check.dto';
|
import { SystemConfigNewVersionCheckDto } from './system-config-new-version-check.dto';
|
||||||
@ -21,6 +22,11 @@ export class SystemConfigDto implements SystemConfig {
|
|||||||
@IsObject()
|
@IsObject()
|
||||||
ffmpeg!: SystemConfigFFmpegDto;
|
ffmpeg!: SystemConfigFFmpegDto;
|
||||||
|
|
||||||
|
@Type(() => SystemConfigLoggingDto)
|
||||||
|
@ValidateNested()
|
||||||
|
@IsObject()
|
||||||
|
logging!: SystemConfigLoggingDto;
|
||||||
|
|
||||||
@Type(() => SystemConfigMachineLearningDto)
|
@Type(() => SystemConfigMachineLearningDto)
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@IsObject()
|
@IsObject()
|
||||||
|
@ -2,6 +2,7 @@ import {
|
|||||||
AudioCodec,
|
AudioCodec,
|
||||||
Colorspace,
|
Colorspace,
|
||||||
CQMode,
|
CQMode,
|
||||||
|
LogLevel,
|
||||||
SystemConfig,
|
SystemConfig,
|
||||||
SystemConfigEntity,
|
SystemConfigEntity,
|
||||||
SystemConfigKey,
|
SystemConfigKey,
|
||||||
@ -11,7 +12,8 @@ import {
|
|||||||
TranscodePolicy,
|
TranscodePolicy,
|
||||||
VideoCodec,
|
VideoCodec,
|
||||||
} from '@app/infra/entities';
|
} from '@app/infra/entities';
|
||||||
import { BadRequestException, ForbiddenException, Injectable, Logger } from '@nestjs/common';
|
import { ImmichLogger } from '@app/infra/logger';
|
||||||
|
import { BadRequestException, ForbiddenException, Injectable } from '@nestjs/common';
|
||||||
import { CronExpression } from '@nestjs/schedule';
|
import { CronExpression } from '@nestjs/schedule';
|
||||||
import { plainToInstance } from 'class-transformer';
|
import { plainToInstance } from 'class-transformer';
|
||||||
import { validate } from 'class-validator';
|
import { validate } from 'class-validator';
|
||||||
@ -21,7 +23,7 @@ import { QueueName } from '../job/job.constants';
|
|||||||
import { ISystemConfigRepository } from '../repositories';
|
import { ISystemConfigRepository } from '../repositories';
|
||||||
import { SystemConfigDto } from './dto';
|
import { SystemConfigDto } from './dto';
|
||||||
|
|
||||||
export type SystemConfigValidator = (config: SystemConfig) => void | Promise<void>;
|
export type SystemConfigValidator = (config: SystemConfig, newConfig: SystemConfig) => void | Promise<void>;
|
||||||
|
|
||||||
export const defaults = Object.freeze<SystemConfig>({
|
export const defaults = Object.freeze<SystemConfig>({
|
||||||
ffmpeg: {
|
ffmpeg: {
|
||||||
@ -57,6 +59,10 @@ export const defaults = Object.freeze<SystemConfig>({
|
|||||||
[QueueName.THUMBNAIL_GENERATION]: { concurrency: 5 },
|
[QueueName.THUMBNAIL_GENERATION]: { concurrency: 5 },
|
||||||
[QueueName.VIDEO_CONVERSION]: { concurrency: 1 },
|
[QueueName.VIDEO_CONVERSION]: { concurrency: 1 },
|
||||||
},
|
},
|
||||||
|
logging: {
|
||||||
|
enabled: true,
|
||||||
|
level: LogLevel.LOG,
|
||||||
|
},
|
||||||
machineLearning: {
|
machineLearning: {
|
||||||
enabled: process.env.IMMICH_MACHINE_LEARNING_ENABLED !== 'false',
|
enabled: process.env.IMMICH_MACHINE_LEARNING_ENABLED !== 'false',
|
||||||
url: process.env.IMMICH_MACHINE_LEARNING_URL || 'http://immich-machine-learning:3003',
|
url: process.env.IMMICH_MACHINE_LEARNING_URL || 'http://immich-machine-learning:3003',
|
||||||
@ -149,7 +155,7 @@ let instance: SystemConfigCore | null;
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SystemConfigCore {
|
export class SystemConfigCore {
|
||||||
private logger = new Logger(SystemConfigCore.name);
|
private logger = new ImmichLogger(SystemConfigCore.name);
|
||||||
private validators: SystemConfigValidator[] = [];
|
private validators: SystemConfigValidator[] = [];
|
||||||
private configCache: SystemConfigEntity<SystemConfigValue>[] | null = null;
|
private configCache: SystemConfigEntity<SystemConfigValue>[] | null = null;
|
||||||
|
|
||||||
@ -253,14 +259,16 @@ export class SystemConfigCore {
|
|||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateConfig(config: SystemConfig): Promise<SystemConfig> {
|
public async updateConfig(newConfig: SystemConfig): Promise<SystemConfig> {
|
||||||
if (await this.hasFeature(FeatureFlag.CONFIG_FILE)) {
|
if (await this.hasFeature(FeatureFlag.CONFIG_FILE)) {
|
||||||
throw new BadRequestException('Cannot update configuration while IMMICH_CONFIG_FILE is in use');
|
throw new BadRequestException('Cannot update configuration while IMMICH_CONFIG_FILE is in use');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const oldConfig = await this.getConfig();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (const validator of this.validators) {
|
for (const validator of this.validators) {
|
||||||
await validator(config);
|
await validator(newConfig, oldConfig);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logger.warn(`Unable to save system config due to a validation error: ${e}`);
|
this.logger.warn(`Unable to save system config due to a validation error: ${e}`);
|
||||||
@ -272,9 +280,9 @@ export class SystemConfigCore {
|
|||||||
|
|
||||||
for (const key of Object.values(SystemConfigKey)) {
|
for (const key of Object.values(SystemConfigKey)) {
|
||||||
// get via dot notation
|
// get via dot notation
|
||||||
const item = { key, value: _.get(config, key) as SystemConfigValue };
|
const item = { key, value: _.get(newConfig, key) as SystemConfigValue };
|
||||||
const defaultValue = _.get(defaults, key);
|
const defaultValue = _.get(defaults, key);
|
||||||
const isMissing = !_.has(config, key);
|
const isMissing = !_.has(newConfig, key);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isMissing ||
|
isMissing ||
|
||||||
@ -298,11 +306,11 @@ export class SystemConfigCore {
|
|||||||
await this.repository.deleteKeys(deletes.map((item) => item.key));
|
await this.repository.deleteKeys(deletes.map((item) => item.key));
|
||||||
}
|
}
|
||||||
|
|
||||||
const newConfig = await this.getConfig();
|
const config = await this.getConfig();
|
||||||
|
|
||||||
this.config$.next(newConfig);
|
this.config$.next(config);
|
||||||
|
|
||||||
return newConfig;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async refreshConfig() {
|
public async refreshConfig() {
|
||||||
|
@ -2,6 +2,7 @@ import {
|
|||||||
AudioCodec,
|
AudioCodec,
|
||||||
Colorspace,
|
Colorspace,
|
||||||
CQMode,
|
CQMode,
|
||||||
|
LogLevel,
|
||||||
SystemConfig,
|
SystemConfig,
|
||||||
SystemConfigEntity,
|
SystemConfigEntity,
|
||||||
SystemConfigKey,
|
SystemConfigKey,
|
||||||
@ -57,6 +58,10 @@ const updatedConfig = Object.freeze<SystemConfig>({
|
|||||||
accel: TranscodeHWAccel.DISABLED,
|
accel: TranscodeHWAccel.DISABLED,
|
||||||
tonemap: ToneMapping.HABLE,
|
tonemap: ToneMapping.HABLE,
|
||||||
},
|
},
|
||||||
|
logging: {
|
||||||
|
enabled: true,
|
||||||
|
level: LogLevel.LOG,
|
||||||
|
},
|
||||||
machineLearning: {
|
machineLearning: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
url: 'http://immich-machine-learning:3003',
|
url: 'http://immich-machine-learning:3003',
|
||||||
@ -159,7 +164,7 @@ describe(SystemConfigService.name, () => {
|
|||||||
const validator: SystemConfigValidator = jest.fn();
|
const validator: SystemConfigValidator = jest.fn();
|
||||||
sut.addValidator(validator);
|
sut.addValidator(validator);
|
||||||
await sut.updateConfig(defaults);
|
await sut.updateConfig(defaults);
|
||||||
expect(validator).toHaveBeenCalledWith(defaults);
|
expect(validator).toHaveBeenCalledWith(defaults, defaults);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -279,7 +284,7 @@ describe(SystemConfigService.name, () => {
|
|||||||
|
|
||||||
await expect(sut.updateConfig(updatedConfig)).rejects.toBeInstanceOf(BadRequestException);
|
await expect(sut.updateConfig(updatedConfig)).rejects.toBeInstanceOf(BadRequestException);
|
||||||
|
|
||||||
expect(validator).toHaveBeenCalledWith(updatedConfig);
|
expect(validator).toHaveBeenCalledWith(updatedConfig, defaults);
|
||||||
expect(configMock.saveAll).not.toHaveBeenCalled();
|
expect(configMock.saveAll).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
import { Inject, Injectable, Logger } from '@nestjs/common';
|
import { LogLevel, SystemConfig } from '@app/infra/entities';
|
||||||
|
import { ImmichLogger } from '@app/infra/logger';
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { instanceToPlain } from 'class-transformer';
|
||||||
|
import _ from 'lodash';
|
||||||
import {
|
import {
|
||||||
ClientEvent,
|
ClientEvent,
|
||||||
ICommunicationRepository,
|
ICommunicationRepository,
|
||||||
@ -22,7 +26,7 @@ import { SystemConfigCore, SystemConfigValidator } from './system-config.core';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SystemConfigService {
|
export class SystemConfigService {
|
||||||
private logger = new Logger(SystemConfigService.name);
|
private logger = new ImmichLogger(SystemConfigService.name);
|
||||||
private core: SystemConfigCore;
|
private core: SystemConfigCore;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -32,6 +36,13 @@ export class SystemConfigService {
|
|||||||
) {
|
) {
|
||||||
this.core = SystemConfigCore.create(repository);
|
this.core = SystemConfigCore.create(repository);
|
||||||
this.communicationRepository.on(ServerEvent.CONFIG_UPDATE, () => this.handleConfigUpdate());
|
this.communicationRepository.on(ServerEvent.CONFIG_UPDATE, () => this.handleConfigUpdate());
|
||||||
|
this.core.config$.subscribe((config) => this.setLogLevel(config));
|
||||||
|
this.core.addValidator((newConfig, oldConfig) => this.validateConfig(newConfig, oldConfig));
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
const config = await this.core.getConfig();
|
||||||
|
await this.setLogLevel(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
get config$() {
|
get config$() {
|
||||||
@ -106,4 +117,22 @@ export class SystemConfigService {
|
|||||||
private async handleConfigUpdate() {
|
private async handleConfigUpdate() {
|
||||||
await this.core.refreshConfig();
|
await this.core.refreshConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async setLogLevel({ logging }: SystemConfig) {
|
||||||
|
const envLevel = this.getEnvLogLevel();
|
||||||
|
const configLevel = logging.enabled ? logging.level : false;
|
||||||
|
const level = envLevel ? envLevel : configLevel;
|
||||||
|
ImmichLogger.setLogLevel(level);
|
||||||
|
this.logger.log(`LogLevel=${level} ${envLevel ? '(set via LOG_LEVEL)' : '(set via system config)'}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getEnvLogLevel() {
|
||||||
|
return process.env.LOG_LEVEL as LogLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async validateConfig(newConfig: SystemConfig, oldConfig: SystemConfig) {
|
||||||
|
if (!_.isEqual(instanceToPlain(newConfig.logging), oldConfig.logging) && this.getEnvLogLevel()) {
|
||||||
|
throw new Error('Logging cannot be changed while the environment variable LOG_LEVEL is set.');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { UserEntity } from '@app/infra/entities';
|
import { UserEntity } from '@app/infra/entities';
|
||||||
import { BadRequestException, ForbiddenException, Inject, Injectable, Logger, NotFoundException } from '@nestjs/common';
|
import { ImmichLogger } from '@app/infra/logger';
|
||||||
|
import { BadRequestException, ForbiddenException, Inject, Injectable, NotFoundException } from '@nestjs/common';
|
||||||
import { randomBytes } from 'crypto';
|
import { randomBytes } from 'crypto';
|
||||||
import { AuthDto } from '../auth';
|
import { AuthDto } from '../auth';
|
||||||
import { ImmichFileResponse } from '../domain.util';
|
import { ImmichFileResponse } from '../domain.util';
|
||||||
@ -21,7 +22,7 @@ import { UserCore } from './user.core';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserService {
|
export class UserService {
|
||||||
private logger = new Logger(UserService.name);
|
private logger = new ImmichLogger(UserService.name);
|
||||||
private userCore: UserCore;
|
private userCore: UserCore;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -15,7 +15,8 @@ import {
|
|||||||
UploadFile,
|
UploadFile,
|
||||||
} from '@app/domain';
|
} from '@app/domain';
|
||||||
import { ASSET_CHECKSUM_CONSTRAINT, AssetEntity, AssetType, LibraryType } from '@app/infra/entities';
|
import { ASSET_CHECKSUM_CONSTRAINT, AssetEntity, AssetType, LibraryType } from '@app/infra/entities';
|
||||||
import { Inject, Injectable, InternalServerErrorException, Logger, NotFoundException } from '@nestjs/common';
|
import { ImmichLogger } from '@app/infra/logger';
|
||||||
|
import { Inject, Injectable, InternalServerErrorException, NotFoundException } from '@nestjs/common';
|
||||||
import { QueryFailedError } from 'typeorm';
|
import { QueryFailedError } from 'typeorm';
|
||||||
import { IAssetRepository } from './asset-repository';
|
import { IAssetRepository } from './asset-repository';
|
||||||
import { AssetCore } from './asset.core';
|
import { AssetCore } from './asset.core';
|
||||||
@ -38,7 +39,7 @@ import { CuratedObjectsResponseDto } from './response-dto/curated-objects-respon
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AssetService {
|
export class AssetService {
|
||||||
readonly logger = new Logger(AssetService.name);
|
readonly logger = new ImmichLogger(AssetService.name);
|
||||||
private assetCore: AssetCore;
|
private assetCore: AssetCore;
|
||||||
private access: AccessCore;
|
private access: AccessCore;
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { AuthDto, AuthService, IMMICH_API_KEY_NAME, LoginDetails } from '@app/domain';
|
import { AuthDto, AuthService, IMMICH_API_KEY_NAME, LoginDetails } from '@app/domain';
|
||||||
|
import { ImmichLogger } from '@app/infra/logger';
|
||||||
import {
|
import {
|
||||||
CanActivate,
|
CanActivate,
|
||||||
ExecutionContext,
|
ExecutionContext,
|
||||||
Injectable,
|
Injectable,
|
||||||
Logger,
|
|
||||||
SetMetadata,
|
SetMetadata,
|
||||||
applyDecorators,
|
applyDecorators,
|
||||||
createParamDecorator,
|
createParamDecorator,
|
||||||
@ -77,7 +77,7 @@ export interface AuthRequest extends Request {
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AppGuard implements CanActivate {
|
export class AppGuard implements CanActivate {
|
||||||
private logger = new Logger(AppGuard.name);
|
private logger = new ImmichLogger(AppGuard.name);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private reflector: Reflector,
|
private reflector: Reflector,
|
||||||
|
@ -6,8 +6,10 @@ import {
|
|||||||
ServerInfoService,
|
ServerInfoService,
|
||||||
SharedLinkService,
|
SharedLinkService,
|
||||||
StorageService,
|
StorageService,
|
||||||
|
SystemConfigService,
|
||||||
} from '@app/domain';
|
} from '@app/domain';
|
||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { ImmichLogger } from '@app/infra/logger';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
import { Cron, CronExpression, Interval } from '@nestjs/schedule';
|
import { Cron, CronExpression, Interval } from '@nestjs/schedule';
|
||||||
import { NextFunction, Request, Response } from 'express';
|
import { NextFunction, Request, Response } from 'express';
|
||||||
import { readFileSync } from 'fs';
|
import { readFileSync } from 'fs';
|
||||||
@ -34,10 +36,11 @@ const render = (index: string, meta: OpenGraphTags) => {
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AppService {
|
export class AppService {
|
||||||
private logger = new Logger(AppService.name);
|
private logger = new ImmichLogger(AppService.name);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
|
private configService: SystemConfigService,
|
||||||
private jobService: JobService,
|
private jobService: JobService,
|
||||||
private serverService: ServerInfoService,
|
private serverService: ServerInfoService,
|
||||||
private sharedLinkService: SharedLinkService,
|
private sharedLinkService: SharedLinkService,
|
||||||
@ -55,6 +58,7 @@ export class AppService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
|
await this.configService.init();
|
||||||
this.storageService.init();
|
this.storageService.init();
|
||||||
await this.serverService.handleVersionCheck();
|
await this.serverService.handleVersionCheck();
|
||||||
this.logger.log(`Feature Flags: ${JSON.stringify(await this.serverService.getFeatures(), null, 2)}`);
|
this.logger.log(`Feature Flags: ${JSON.stringify(await this.serverService.getFeatures(), null, 2)}`);
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
|
import { ImmichLogger } from '@app/infra/logger';
|
||||||
import {
|
import {
|
||||||
CallHandler,
|
CallHandler,
|
||||||
ExecutionContext,
|
ExecutionContext,
|
||||||
HttpException,
|
HttpException,
|
||||||
Injectable,
|
Injectable,
|
||||||
InternalServerErrorException,
|
InternalServerErrorException,
|
||||||
Logger,
|
|
||||||
NestInterceptor,
|
NestInterceptor,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { Observable, catchError, throwError } from 'rxjs';
|
import { Observable, catchError, throwError } from 'rxjs';
|
||||||
@ -13,7 +13,7 @@ import { routeToErrorMessage } from '../app.utils';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ErrorInterceptor implements NestInterceptor {
|
export class ErrorInterceptor implements NestInterceptor {
|
||||||
private logger = new Logger(ErrorInterceptor.name);
|
private logger = new ImmichLogger(ErrorInterceptor.name);
|
||||||
|
|
||||||
async intercept(context: ExecutionContext, next: CallHandler<any>): Promise<Observable<any>> {
|
async intercept(context: ExecutionContext, next: CallHandler<any>): Promise<Observable<any>> {
|
||||||
return next.handle().pipe(
|
return next.handle().pipe(
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { ImmichFileResponse, isConnectionAborted } from '@app/domain';
|
import { ImmichFileResponse, isConnectionAborted } from '@app/domain';
|
||||||
import { CallHandler, ExecutionContext, Logger, NestInterceptor } from '@nestjs/common';
|
import { ImmichLogger } from '@app/infra/logger';
|
||||||
|
import { CallHandler, ExecutionContext, NestInterceptor } from '@nestjs/common';
|
||||||
import { Response } from 'express';
|
import { Response } from 'express';
|
||||||
import { access, constants } from 'fs/promises';
|
import { access, constants } from 'fs/promises';
|
||||||
import { isAbsolute } from 'path';
|
import { isAbsolute } from 'path';
|
||||||
@ -10,7 +11,7 @@ type SendFile = Parameters<Response['sendFile']>;
|
|||||||
type SendFileOptions = SendFile[1];
|
type SendFileOptions = SendFile[1];
|
||||||
|
|
||||||
export class FileServeInterceptor implements NestInterceptor {
|
export class FileServeInterceptor implements NestInterceptor {
|
||||||
private logger = new Logger(FileServeInterceptor.name);
|
private logger = new ImmichLogger(FileServeInterceptor.name);
|
||||||
|
|
||||||
intercept(context: ExecutionContext, next: CallHandler<any>): Observable<any> {
|
intercept(context: ExecutionContext, next: CallHandler<any>): Observable<any> {
|
||||||
const http = context.switchToHttp();
|
const http = context.switchToHttp();
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { AssetService, UploadFieldName, UploadFile } from '@app/domain';
|
import { AssetService, UploadFieldName, UploadFile } from '@app/domain';
|
||||||
import { CallHandler, ExecutionContext, Injectable, Logger, NestInterceptor } from '@nestjs/common';
|
import { ImmichLogger } from '@app/infra/logger';
|
||||||
|
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
|
||||||
import { PATH_METADATA } from '@nestjs/common/constants';
|
import { PATH_METADATA } from '@nestjs/common/constants';
|
||||||
import { Reflector } from '@nestjs/core';
|
import { Reflector } from '@nestjs/core';
|
||||||
import { transformException } from '@nestjs/platform-express/multer/multer/multer.utils';
|
import { transformException } from '@nestjs/platform-express/multer/multer/multer.utils';
|
||||||
@ -52,7 +53,7 @@ const asRequest = (req: AuthRequest, file: Express.Multer.File) => {
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FileUploadInterceptor implements NestInterceptor {
|
export class FileUploadInterceptor implements NestInterceptor {
|
||||||
private logger = new Logger(FileUploadInterceptor.name);
|
private logger = new ImmichLogger(FileUploadInterceptor.name);
|
||||||
|
|
||||||
private handlers: {
|
private handlers: {
|
||||||
userProfile: RequestHandler;
|
userProfile: RequestHandler;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { envName, getLogLevels, isDev, serverVersion } from '@app/domain';
|
import { envName, isDev, serverVersion } from '@app/domain';
|
||||||
import { WebSocketAdapter, enablePrefilter } from '@app/infra';
|
import { WebSocketAdapter, enablePrefilter } from '@app/infra';
|
||||||
import { Logger } from '@nestjs/common';
|
import { ImmichLogger } from '@app/infra/logger';
|
||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
import { NestExpressApplication } from '@nestjs/platform-express';
|
import { NestExpressApplication } from '@nestjs/platform-express';
|
||||||
import { json } from 'body-parser';
|
import { json } from 'body-parser';
|
||||||
@ -9,12 +9,13 @@ import { AppModule } from './app.module';
|
|||||||
import { AppService } from './app.service';
|
import { AppService } from './app.service';
|
||||||
import { useSwagger } from './app.utils';
|
import { useSwagger } from './app.utils';
|
||||||
|
|
||||||
const logger = new Logger('ImmichServer');
|
const logger = new ImmichLogger('ImmichServer');
|
||||||
const port = Number(process.env.SERVER_PORT) || 3001;
|
const port = Number(process.env.SERVER_PORT) || 3001;
|
||||||
|
|
||||||
export async function bootstrap() {
|
export async function bootstrap() {
|
||||||
const app = await NestFactory.create<NestExpressApplication>(AppModule, { logger: getLogLevels() });
|
const app = await NestFactory.create<NestExpressApplication>(AppModule, { bufferLogs: true });
|
||||||
|
|
||||||
|
app.useLogger(app.get(ImmichLogger));
|
||||||
app.set('trust proxy', ['loopback', 'linklocal', 'uniquelocal']);
|
app.set('trust proxy', ['loopback', 'linklocal', 'uniquelocal']);
|
||||||
app.set('etag', 'strong');
|
app.set('etag', 'strong');
|
||||||
app.use(cookieParser());
|
app.use(cookieParser());
|
||||||
|
@ -45,6 +45,12 @@ export enum SystemConfigKey {
|
|||||||
JOB_LIBRARY_CONCURRENCY = 'job.library.concurrency',
|
JOB_LIBRARY_CONCURRENCY = 'job.library.concurrency',
|
||||||
JOB_MIGRATION_CONCURRENCY = 'job.migration.concurrency',
|
JOB_MIGRATION_CONCURRENCY = 'job.migration.concurrency',
|
||||||
|
|
||||||
|
LIBRARY_SCAN_ENABLED = 'library.scan.enabled',
|
||||||
|
LIBRARY_SCAN_CRON_EXPRESSION = 'library.scan.cronExpression',
|
||||||
|
|
||||||
|
LOGGING_ENABLED = 'logging.enabled',
|
||||||
|
LOGGING_LEVEL = 'logging.level',
|
||||||
|
|
||||||
MACHINE_LEARNING_ENABLED = 'machineLearning.enabled',
|
MACHINE_LEARNING_ENABLED = 'machineLearning.enabled',
|
||||||
MACHINE_LEARNING_URL = 'machineLearning.url',
|
MACHINE_LEARNING_URL = 'machineLearning.url',
|
||||||
|
|
||||||
@ -94,9 +100,6 @@ export enum SystemConfigKey {
|
|||||||
TRASH_DAYS = 'trash.days',
|
TRASH_DAYS = 'trash.days',
|
||||||
|
|
||||||
THEME_CUSTOM_CSS = 'theme.customCss',
|
THEME_CUSTOM_CSS = 'theme.customCss',
|
||||||
|
|
||||||
LIBRARY_SCAN_ENABLED = 'library.scan.enabled',
|
|
||||||
LIBRARY_SCAN_CRON_EXPRESSION = 'library.scan.cronExpression',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum TranscodePolicy {
|
export enum TranscodePolicy {
|
||||||
@ -144,6 +147,15 @@ export enum Colorspace {
|
|||||||
P3 = 'p3',
|
P3 = 'p3',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum LogLevel {
|
||||||
|
VERBOSE = 'verbose',
|
||||||
|
DEBUG = 'debug',
|
||||||
|
LOG = 'log',
|
||||||
|
WARN = 'warn',
|
||||||
|
ERROR = 'error',
|
||||||
|
FATAL = 'fatal',
|
||||||
|
}
|
||||||
|
|
||||||
export interface SystemConfig {
|
export interface SystemConfig {
|
||||||
ffmpeg: {
|
ffmpeg: {
|
||||||
crf: number;
|
crf: number;
|
||||||
@ -165,6 +177,10 @@ export interface SystemConfig {
|
|||||||
tonemap: ToneMapping;
|
tonemap: ToneMapping;
|
||||||
};
|
};
|
||||||
job: Record<QueueName, { concurrency: number }>;
|
job: Record<QueueName, { concurrency: number }>;
|
||||||
|
logging: {
|
||||||
|
enabled: boolean;
|
||||||
|
level: LogLevel;
|
||||||
|
};
|
||||||
machineLearning: {
|
machineLearning: {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
url: string;
|
url: string;
|
||||||
|
21
server/src/infra/logger.ts
Normal file
21
server/src/infra/logger.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { ConsoleLogger } from '@nestjs/common';
|
||||||
|
import { isLogLevelEnabled } from '@nestjs/common/services/utils/is-log-level-enabled.util';
|
||||||
|
import { LogLevel } from './entities';
|
||||||
|
|
||||||
|
const LOG_LEVELS = [LogLevel.VERBOSE, LogLevel.DEBUG, LogLevel.LOG, LogLevel.WARN, LogLevel.ERROR, LogLevel.FATAL];
|
||||||
|
|
||||||
|
export class ImmichLogger extends ConsoleLogger {
|
||||||
|
private static logLevels: LogLevel[] = [];
|
||||||
|
|
||||||
|
constructor(context: string) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
isLevelEnabled(level: LogLevel) {
|
||||||
|
return isLogLevelEnabled(level, ImmichLogger.logLevels);
|
||||||
|
}
|
||||||
|
|
||||||
|
static setLogLevel(level: LogLevel | false): void {
|
||||||
|
ImmichLogger.logLevels = level === false ? [] : LOG_LEVELS.slice(LOG_LEVELS.indexOf(level));
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,7 @@ import {
|
|||||||
OnServerEventCallback,
|
OnServerEventCallback,
|
||||||
ServerEvent,
|
ServerEvent,
|
||||||
} from '@app/domain';
|
} from '@app/domain';
|
||||||
import { Logger } from '@nestjs/common';
|
import { ImmichLogger } from '@app/infra/logger';
|
||||||
import {
|
import {
|
||||||
OnGatewayConnection,
|
OnGatewayConnection,
|
||||||
OnGatewayDisconnect,
|
OnGatewayDisconnect,
|
||||||
@ -20,7 +20,7 @@ import { Server, Socket } from 'socket.io';
|
|||||||
export class CommunicationRepository
|
export class CommunicationRepository
|
||||||
implements OnGatewayConnection, OnGatewayDisconnect, OnGatewayInit, ICommunicationRepository
|
implements OnGatewayConnection, OnGatewayDisconnect, OnGatewayInit, ICommunicationRepository
|
||||||
{
|
{
|
||||||
private logger = new Logger(CommunicationRepository.name);
|
private logger = new ImmichLogger(CommunicationRepository.name);
|
||||||
private onConnectCallbacks: OnConnectCallback[] = [];
|
private onConnectCallbacks: OnConnectCallback[] = [];
|
||||||
private onServerEventCallbacks: Record<ServerEvent, OnServerEventCallback[]> = {
|
private onServerEventCallbacks: Record<ServerEvent, OnServerEventCallback[]> = {
|
||||||
[ServerEvent.CONFIG_UPDATE]: [],
|
[ServerEvent.CONFIG_UPDATE]: [],
|
||||||
|
@ -6,7 +6,7 @@ import {
|
|||||||
IStorageRepository,
|
IStorageRepository,
|
||||||
mimeTypes,
|
mimeTypes,
|
||||||
} from '@app/domain';
|
} from '@app/domain';
|
||||||
import { Logger } from '@nestjs/common';
|
import { ImmichLogger } from '@app/infra/logger';
|
||||||
import archiver from 'archiver';
|
import archiver from 'archiver';
|
||||||
import { constants, createReadStream, existsSync, mkdirSync } from 'fs';
|
import { constants, createReadStream, existsSync, mkdirSync } from 'fs';
|
||||||
import fs, { readdir, writeFile } from 'fs/promises';
|
import fs, { readdir, writeFile } from 'fs/promises';
|
||||||
@ -18,7 +18,7 @@ import path from 'path';
|
|||||||
const moveFile = promisify<string, string, mv.Options>(mv);
|
const moveFile = promisify<string, string, mv.Options>(mv);
|
||||||
|
|
||||||
export class FilesystemProvider implements IStorageRepository {
|
export class FilesystemProvider implements IStorageRepository {
|
||||||
private logger = new Logger(FilesystemProvider.name);
|
private logger = new ImmichLogger(FilesystemProvider.name);
|
||||||
|
|
||||||
createZipStream(): ImmichZipStream {
|
createZipStream(): ImmichZipStream {
|
||||||
const archive = archiver('zip', { store: true });
|
const archive = archiver('zip', { store: true });
|
||||||
|
@ -8,8 +8,9 @@ import {
|
|||||||
QueueName,
|
QueueName,
|
||||||
QueueStatus,
|
QueueStatus,
|
||||||
} from '@app/domain';
|
} from '@app/domain';
|
||||||
|
import { ImmichLogger } from '@app/infra/logger';
|
||||||
import { getQueueToken } from '@nestjs/bullmq';
|
import { getQueueToken } from '@nestjs/bullmq';
|
||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { ModuleRef } from '@nestjs/core';
|
import { ModuleRef } from '@nestjs/core';
|
||||||
import { SchedulerRegistry } from '@nestjs/schedule';
|
import { SchedulerRegistry } from '@nestjs/schedule';
|
||||||
import { Job, JobsOptions, Processor, Queue, Worker, WorkerOptions } from 'bullmq';
|
import { Job, JobsOptions, Processor, Queue, Worker, WorkerOptions } from 'bullmq';
|
||||||
@ -19,7 +20,7 @@ import { bullConfig } from '../infra.config';
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class JobRepository implements IJobRepository {
|
export class JobRepository implements IJobRepository {
|
||||||
private workers: Partial<Record<QueueName, Worker>> = {};
|
private workers: Partial<Record<QueueName, Worker>> = {};
|
||||||
private logger = new Logger(JobRepository.name);
|
private logger = new ImmichLogger(JobRepository.name);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private moduleRef: ModuleRef,
|
private moduleRef: ModuleRef,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { CropOptions, IMediaRepository, ResizeOptions, TranscodeOptions, VideoInfo } from '@app/domain';
|
import { CropOptions, IMediaRepository, ResizeOptions, TranscodeOptions, VideoInfo } from '@app/domain';
|
||||||
import { Colorspace } from '@app/infra/entities';
|
import { Colorspace } from '@app/infra/entities';
|
||||||
import { Logger } from '@nestjs/common';
|
import { ImmichLogger } from '@app/infra/logger';
|
||||||
import ffmpeg, { FfprobeData } from 'fluent-ffmpeg';
|
import ffmpeg, { FfprobeData } from 'fluent-ffmpeg';
|
||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import sharp from 'sharp';
|
import sharp from 'sharp';
|
||||||
@ -11,7 +11,7 @@ const probe = promisify<string, FfprobeData>(ffmpeg.ffprobe);
|
|||||||
sharp.concurrency(0);
|
sharp.concurrency(0);
|
||||||
|
|
||||||
export class MediaRepository implements IMediaRepository {
|
export class MediaRepository implements IMediaRepository {
|
||||||
private logger = new Logger(MediaRepository.name);
|
private logger = new ImmichLogger(MediaRepository.name);
|
||||||
|
|
||||||
crop(input: string | Buffer, options: CropOptions): Promise<Buffer> {
|
crop(input: string | Buffer, options: CropOptions): Promise<Buffer> {
|
||||||
return sharp(input, { failOn: 'none' })
|
return sharp(input, { failOn: 'none' })
|
||||||
|
@ -7,7 +7,8 @@ import {
|
|||||||
} from '@app/domain';
|
} from '@app/domain';
|
||||||
import { DatabaseLock, RequireLock } from '@app/infra';
|
import { DatabaseLock, RequireLock } from '@app/infra';
|
||||||
import { GeodataAdmin1Entity, GeodataAdmin2Entity, GeodataPlacesEntity, SystemMetadataKey } from '@app/infra/entities';
|
import { GeodataAdmin1Entity, GeodataAdmin2Entity, GeodataPlacesEntity, SystemMetadataKey } from '@app/infra/entities';
|
||||||
import { Inject, Logger } from '@nestjs/common';
|
import { ImmichLogger } from '@app/infra/logger';
|
||||||
|
import { Inject } from '@nestjs/common';
|
||||||
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||||
import { DefaultReadTaskOptions, exiftool, Tags } from 'exiftool-vendored';
|
import { DefaultReadTaskOptions, exiftool, Tags } from 'exiftool-vendored';
|
||||||
import { createReadStream, existsSync } from 'fs';
|
import { createReadStream, existsSync } from 'fs';
|
||||||
@ -31,7 +32,7 @@ export class MetadataRepository implements IMetadataRepository {
|
|||||||
@InjectDataSource() private dataSource: DataSource,
|
@InjectDataSource() private dataSource: DataSource,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
private logger = new Logger(MetadataRepository.name);
|
private logger = new ImmichLogger(MetadataRepository.name);
|
||||||
|
|
||||||
@RequireLock(DatabaseLock.GeodataImport)
|
@RequireLock(DatabaseLock.GeodataImport)
|
||||||
async init(): Promise<void> {
|
async init(): Promise<void> {
|
||||||
|
@ -2,7 +2,8 @@ import { Embedding, EmbeddingSearch, ISmartInfoRepository } from '@app/domain';
|
|||||||
import { getCLIPModelInfo } from '@app/domain/smart-info/smart-info.constant';
|
import { getCLIPModelInfo } from '@app/domain/smart-info/smart-info.constant';
|
||||||
import { DatabaseLock, RequireLock, asyncLock } from '@app/infra';
|
import { DatabaseLock, RequireLock, asyncLock } from '@app/infra';
|
||||||
import { AssetEntity, AssetFaceEntity, SmartInfoEntity, SmartSearchEntity } from '@app/infra/entities';
|
import { AssetEntity, AssetFaceEntity, SmartInfoEntity, SmartSearchEntity } from '@app/infra/entities';
|
||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { ImmichLogger } from '@app/infra/logger';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { DummyValue, GenerateSql } from '../infra.util';
|
import { DummyValue, GenerateSql } from '../infra.util';
|
||||||
@ -10,7 +11,7 @@ import { asVector, isValidInteger } from '../infra.utils';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SmartInfoRepository implements ISmartInfoRepository {
|
export class SmartInfoRepository implements ISmartInfoRepository {
|
||||||
private logger = new Logger(SmartInfoRepository.name);
|
private logger = new ImmichLogger(SmartInfoRepository.name);
|
||||||
private faceColumns: string[];
|
private faceColumns: string[];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -8,37 +8,33 @@ import {
|
|||||||
MediaService,
|
MediaService,
|
||||||
MetadataService,
|
MetadataService,
|
||||||
PersonService,
|
PersonService,
|
||||||
ServerInfoService,
|
|
||||||
SmartInfoService,
|
SmartInfoService,
|
||||||
StorageService,
|
StorageService,
|
||||||
StorageTemplateService,
|
StorageTemplateService,
|
||||||
SystemConfigService,
|
SystemConfigService,
|
||||||
UserService,
|
UserService,
|
||||||
} from '@app/domain';
|
} from '@app/domain';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
import { Injectable, Logger } from '@nestjs/common';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AppService {
|
export class AppService {
|
||||||
private logger = new Logger(AppService.name);
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private auditService: AuditService,
|
private auditService: AuditService,
|
||||||
private assetService: AssetService,
|
private assetService: AssetService,
|
||||||
|
private configService: SystemConfigService,
|
||||||
private jobService: JobService,
|
private jobService: JobService,
|
||||||
private libraryService: LibraryService,
|
private libraryService: LibraryService,
|
||||||
private mediaService: MediaService,
|
private mediaService: MediaService,
|
||||||
private metadataService: MetadataService,
|
private metadataService: MetadataService,
|
||||||
private personService: PersonService,
|
private personService: PersonService,
|
||||||
private serverInfoService: ServerInfoService,
|
|
||||||
private smartInfoService: SmartInfoService,
|
private smartInfoService: SmartInfoService,
|
||||||
private storageTemplateService: StorageTemplateService,
|
private storageTemplateService: StorageTemplateService,
|
||||||
private storageService: StorageService,
|
private storageService: StorageService,
|
||||||
private systemConfigService: SystemConfigService,
|
|
||||||
private userService: UserService,
|
private userService: UserService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
|
await this.configService.init();
|
||||||
await this.jobService.registerHandlers({
|
await this.jobService.registerHandlers({
|
||||||
[JobName.ASSET_DELETION]: (data) => this.assetService.handleAssetDeletion(data),
|
[JobName.ASSET_DELETION]: (data) => this.assetService.handleAssetDeletion(data),
|
||||||
[JobName.ASSET_DELETION_CHECK]: () => this.assetService.handleAssetDeletionCheck(),
|
[JobName.ASSET_DELETION_CHECK]: () => this.assetService.handleAssetDeletionCheck(),
|
||||||
|
@ -1,20 +1,19 @@
|
|||||||
import { envName, getLogLevels, serverVersion } from '@app/domain';
|
import { envName, serverVersion } from '@app/domain';
|
||||||
import { WebSocketAdapter, enablePrefilter } from '@app/infra';
|
import { WebSocketAdapter, enablePrefilter } from '@app/infra';
|
||||||
import { Logger } from '@nestjs/common';
|
import { ImmichLogger } from '@app/infra/logger';
|
||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
import { AppService } from './app.service';
|
|
||||||
import { MicroservicesModule } from './microservices.module';
|
import { MicroservicesModule } from './microservices.module';
|
||||||
|
|
||||||
const logger = new Logger('ImmichMicroservice');
|
const logger = new ImmichLogger('ImmichMicroservice');
|
||||||
const port = Number(process.env.MICROSERVICES_PORT) || 3002;
|
const port = Number(process.env.MICROSERVICES_PORT) || 3002;
|
||||||
|
|
||||||
export async function bootstrap() {
|
export async function bootstrap() {
|
||||||
const app = await NestFactory.create(MicroservicesModule, { logger: getLogLevels() });
|
const app = await NestFactory.create(MicroservicesModule, { bufferLogs: true });
|
||||||
|
|
||||||
|
app.useLogger(app.get(ImmichLogger));
|
||||||
app.useWebSocketAdapter(new WebSocketAdapter(app));
|
app.useWebSocketAdapter(new WebSocketAdapter(app));
|
||||||
await enablePrefilter();
|
await enablePrefilter();
|
||||||
|
|
||||||
await app.get(AppService).init();
|
|
||||||
await app.listen(port);
|
await app.listen(port);
|
||||||
|
|
||||||
logger.log(`Immich Microservices is listening on ${await app.getUrl()} [v${serverVersion}] [${envName}] `);
|
logger.log(`Immich Microservices is listening on ${await app.getUrl()} [v${serverVersion}] [${envName}] `);
|
||||||
|
@ -1,10 +1,16 @@
|
|||||||
import { DomainModule } from '@app/domain';
|
import { DomainModule } from '@app/domain';
|
||||||
import { InfraModule } from '@app/infra';
|
import { InfraModule } from '@app/infra';
|
||||||
import { Module } from '@nestjs/common';
|
import { Module, OnModuleInit } from '@nestjs/common';
|
||||||
import { AppService } from './app.service';
|
import { AppService } from './app.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [DomainModule.register({ imports: [InfraModule] })],
|
imports: [DomainModule.register({ imports: [InfraModule] })],
|
||||||
providers: [AppService],
|
providers: [AppService],
|
||||||
})
|
})
|
||||||
export class MicroservicesModule {}
|
export class MicroservicesModule implements OnModuleInit {
|
||||||
|
constructor(private appService: AppService) {}
|
||||||
|
|
||||||
|
async onModuleInit() {
|
||||||
|
await this.appService.init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
jest.mock('@nestjs/common', () => ({
|
|
||||||
...jest.requireActual('@nestjs/common'),
|
|
||||||
Logger: jest.fn().mockReturnValue({
|
|
||||||
verbose: jest.fn(),
|
|
||||||
debug: jest.fn(),
|
|
||||||
log: jest.fn(),
|
|
||||||
info: jest.fn(),
|
|
||||||
warn: jest.fn(),
|
|
||||||
error: jest.fn(),
|
|
||||||
}),
|
|
||||||
}));
|
|
45
web/src/api/open-api/api.ts
generated
45
web/src/api/open-api/api.ts
generated
@ -2175,6 +2175,24 @@ export const LibraryType = {
|
|||||||
export type LibraryType = typeof LibraryType[keyof typeof LibraryType];
|
export type LibraryType = typeof LibraryType[keyof typeof LibraryType];
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const LogLevel = {
|
||||||
|
Verbose: 'verbose',
|
||||||
|
Debug: 'debug',
|
||||||
|
Log: 'log',
|
||||||
|
Warn: 'warn',
|
||||||
|
Error: 'error',
|
||||||
|
Fatal: 'fatal'
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type LogLevel = typeof LogLevel[keyof typeof LogLevel];
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
@ -3577,6 +3595,12 @@ export interface SystemConfigDto {
|
|||||||
* @memberof SystemConfigDto
|
* @memberof SystemConfigDto
|
||||||
*/
|
*/
|
||||||
'library': SystemConfigLibraryDto;
|
'library': SystemConfigLibraryDto;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {SystemConfigLoggingDto}
|
||||||
|
* @memberof SystemConfigDto
|
||||||
|
*/
|
||||||
|
'logging': SystemConfigLoggingDto;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {SystemConfigMachineLearningDto}
|
* @type {SystemConfigMachineLearningDto}
|
||||||
@ -3860,6 +3884,27 @@ export interface SystemConfigLibraryScanDto {
|
|||||||
*/
|
*/
|
||||||
'enabled': boolean;
|
'enabled': boolean;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface SystemConfigLoggingDto
|
||||||
|
*/
|
||||||
|
export interface SystemConfigLoggingDto {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
* @memberof SystemConfigLoggingDto
|
||||||
|
*/
|
||||||
|
'enabled': boolean;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {LogLevel}
|
||||||
|
* @memberof SystemConfigLoggingDto
|
||||||
|
*/
|
||||||
|
'level': LogLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
|
@ -0,0 +1,110 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {
|
||||||
|
notificationController,
|
||||||
|
NotificationType,
|
||||||
|
} from '$lib/components/shared-components/notification/notification';
|
||||||
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
|
import { api, LogLevel, SystemConfigLoggingDto } 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 SettingSelect from '../setting-select.svelte';
|
||||||
|
|
||||||
|
export let loggingConfig: SystemConfigLoggingDto; // this is the config that is being edited
|
||||||
|
export let disabled = false;
|
||||||
|
|
||||||
|
let savedConfig: SystemConfigLoggingDto;
|
||||||
|
let defaultConfig: SystemConfigLoggingDto;
|
||||||
|
|
||||||
|
async function getConfigs() {
|
||||||
|
[savedConfig, defaultConfig] = await Promise.all([
|
||||||
|
api.systemConfigApi.getConfig().then((res) => res.data.logging),
|
||||||
|
api.systemConfigApi.getConfigDefaults().then((res) => res.data.logging),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveSetting() {
|
||||||
|
try {
|
||||||
|
const { data: current } = await api.systemConfigApi.getConfig();
|
||||||
|
const { data: updated } = await api.systemConfigApi.updateConfig({
|
||||||
|
systemConfigDto: {
|
||||||
|
...current,
|
||||||
|
logging: loggingConfig,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
loggingConfig = { ...updated.logging };
|
||||||
|
savedConfig = { ...updated.logging };
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
loggingConfig = { ...resetConfig.logging };
|
||||||
|
savedConfig = { ...resetConfig.logging };
|
||||||
|
|
||||||
|
notificationController.show({
|
||||||
|
message: 'Reset settings to the recent saved settings',
|
||||||
|
type: NotificationType.Info,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resetToDefault() {
|
||||||
|
const { data: configs } = await api.systemConfigApi.getConfigDefaults();
|
||||||
|
|
||||||
|
loggingConfig = { ...configs.logging };
|
||||||
|
defaultConfig = { ...configs.logging };
|
||||||
|
|
||||||
|
notificationController.show({
|
||||||
|
message: 'Reset password settings to default',
|
||||||
|
type: NotificationType.Info,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{#await getConfigs() then}
|
||||||
|
<div in:fade={{ duration: 500 }}>
|
||||||
|
<form autocomplete="off" on:submit|preventDefault>
|
||||||
|
<div class="ml-4 mt-4 flex flex-col gap-4">
|
||||||
|
<div class="ml-4">
|
||||||
|
<SettingSwitch title="ENABLED" {disabled} subtitle="Logging" bind:checked={loggingConfig.enabled} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ml-4">
|
||||||
|
<SettingSelect
|
||||||
|
label="LEVEL"
|
||||||
|
desc="When enabled, what log level to use."
|
||||||
|
bind:value={loggingConfig.level}
|
||||||
|
options={[
|
||||||
|
{ value: LogLevel.Fatal, text: 'Fatal' },
|
||||||
|
{ value: LogLevel.Error, text: 'Error' },
|
||||||
|
{ value: LogLevel.Warn, text: 'Warn' },
|
||||||
|
{ value: LogLevel.Log, text: 'Log' },
|
||||||
|
{ value: LogLevel.Debug, text: 'Debug' },
|
||||||
|
{ value: LogLevel.Verbose, text: 'Verbose' },
|
||||||
|
]}
|
||||||
|
name="level"
|
||||||
|
isEdited={loggingConfig.level !== savedConfig.level}
|
||||||
|
disabled={disabled || !loggingConfig.enabled}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SettingButtonsRow
|
||||||
|
on:reset={reset}
|
||||||
|
on:save={saveSetting}
|
||||||
|
on:reset-to-default={resetToDefault}
|
||||||
|
showResetToDefault={!isEqual(savedConfig, defaultConfig)}
|
||||||
|
{disabled}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{/await}
|
||||||
|
</div>
|
@ -21,6 +21,7 @@
|
|||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
import NewVersionCheckSettings from '$lib/components/admin-page/settings/new-version-check-settings/new-version-check-settings.svelte';
|
import NewVersionCheckSettings from '$lib/components/admin-page/settings/new-version-check-settings/new-version-check-settings.svelte';
|
||||||
import LibrarySettings from '$lib/components/admin-page/settings/library-settings/library-settings.svelte';
|
import LibrarySettings from '$lib/components/admin-page/settings/library-settings/library-settings.svelte';
|
||||||
|
import LoggingSettings from '$lib/components/admin-page/settings/logging-settings/logging-settings.svelte';
|
||||||
import { mdiAlert, mdiContentCopy, mdiDownload } from '@mdi/js';
|
import { mdiAlert, mdiContentCopy, mdiDownload } from '@mdi/js';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
@ -74,6 +75,10 @@
|
|||||||
<LibrarySettings disabled={$featureFlags.configFile} libraryConfig={configs.library} />
|
<LibrarySettings disabled={$featureFlags.configFile} libraryConfig={configs.library} />
|
||||||
</SettingAccordion>
|
</SettingAccordion>
|
||||||
|
|
||||||
|
<SettingAccordion title="Logging" subtitle="Manage log settings">
|
||||||
|
<LoggingSettings disabled={$featureFlags.configFile} loggingConfig={configs.logging} />
|
||||||
|
</SettingAccordion>
|
||||||
|
|
||||||
<SettingAccordion title="Machine Learning Settings" subtitle="Manage machine learning features and settings">
|
<SettingAccordion title="Machine Learning Settings" subtitle="Manage machine learning features and settings">
|
||||||
<MachineLearningSettings disabled={$featureFlags.configFile} machineLearningConfig={configs.machineLearning} />
|
<MachineLearningSettings disabled={$featureFlags.configFile} machineLearningConfig={configs.machineLearning} />
|
||||||
</SettingAccordion>
|
</SettingAccordion>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user