diff --git a/i18n/en.json b/i18n/en.json index 210e05459d..8870b31a9a 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -181,6 +181,10 @@ "maintenance_settings_description": "Put Immich into maintenance mode.", "maintenance_start": "Start maintenance mode", "maintenance_start_error": "Failed to start maintenance mode.", + "maintenance_integrity_report": "Integrity Report", + "maintenance_integrity_orphan_file": "Orphan Files", + "maintenance_integrity_missing_file": "Missing Files", + "maintenance_integrity_checksum_mismatch": "Checksum Mismatch", "manage_concurrency": "Manage Concurrency", "manage_log_settings": "Manage log settings", "map_dark_style": "Dark style", diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 3b424e8d79..d9eb463717 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -161,7 +161,8 @@ Class | Method | HTTP request | Description *LibrariesApi* | [**scanLibrary**](doc//LibrariesApi.md#scanlibrary) | **POST** /libraries/{id}/scan | Scan a library *LibrariesApi* | [**updateLibrary**](doc//LibrariesApi.md#updatelibrary) | **PUT** /libraries/{id} | Update a library *LibrariesApi* | [**validate**](doc//LibrariesApi.md#validate) | **POST** /libraries/{id}/validate | Validate library settings -*MaintenanceAdminApi* | [**getIntegrityReport**](doc//MaintenanceAdminApi.md#getintegrityreport) | **GET** /admin/maintenance | Get integrity report +*MaintenanceAdminApi* | [**getIntegrityReport**](doc//MaintenanceAdminApi.md#getintegrityreport) | **POST** /admin/maintenance/integrity/report | Get integrity report by type +*MaintenanceAdminApi* | [**getIntegrityReportSummary**](doc//MaintenanceAdminApi.md#getintegrityreportsummary) | **GET** /admin/maintenance/integrity/summary | Get integrity report summary *MaintenanceAdminApi* | [**maintenanceLogin**](doc//MaintenanceAdminApi.md#maintenancelogin) | **POST** /admin/maintenance/login | Log into maintenance mode *MaintenanceAdminApi* | [**setMaintenanceMode**](doc//MaintenanceAdminApi.md#setmaintenancemode) | **POST** /admin/maintenance | Set maintenance mode *MapApi* | [**getMapMarkers**](doc//MapApi.md#getmapmarkers) | **GET** /map/markers | Retrieve map markers @@ -403,6 +404,7 @@ Class | Method | HTTP request | Description - [FoldersResponse](doc//FoldersResponse.md) - [FoldersUpdate](doc//FoldersUpdate.md) - [ImageFormat](doc//ImageFormat.md) + - [IntegrityReportType](doc//IntegrityReportType.md) - [JobCreateDto](doc//JobCreateDto.md) - [JobName](doc//JobName.md) - [JobSettingsDto](doc//JobSettingsDto.md) @@ -417,8 +419,10 @@ Class | Method | HTTP request | Description - [MachineLearningAvailabilityChecksDto](doc//MachineLearningAvailabilityChecksDto.md) - [MaintenanceAction](doc//MaintenanceAction.md) - [MaintenanceAuthDto](doc//MaintenanceAuthDto.md) + - [MaintenanceGetIntegrityReportDto](doc//MaintenanceGetIntegrityReportDto.md) - [MaintenanceIntegrityReportDto](doc//MaintenanceIntegrityReportDto.md) - [MaintenanceIntegrityReportResponseDto](doc//MaintenanceIntegrityReportResponseDto.md) + - [MaintenanceIntegrityReportSummaryResponseDto](doc//MaintenanceIntegrityReportSummaryResponseDto.md) - [MaintenanceLoginDto](doc//MaintenanceLoginDto.md) - [ManualJobName](doc//ManualJobName.md) - [MapMarkerResponseDto](doc//MapMarkerResponseDto.md) diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index ddacd27a3b..3ee5e5c886 100644 --- a/mobile/openapi/lib/api.dart +++ b/mobile/openapi/lib/api.dart @@ -154,6 +154,7 @@ part 'model/facial_recognition_config.dart'; part 'model/folders_response.dart'; part 'model/folders_update.dart'; part 'model/image_format.dart'; +part 'model/integrity_report_type.dart'; part 'model/job_create_dto.dart'; part 'model/job_name.dart'; part 'model/job_settings_dto.dart'; @@ -168,8 +169,10 @@ part 'model/logout_response_dto.dart'; part 'model/machine_learning_availability_checks_dto.dart'; part 'model/maintenance_action.dart'; part 'model/maintenance_auth_dto.dart'; +part 'model/maintenance_get_integrity_report_dto.dart'; part 'model/maintenance_integrity_report_dto.dart'; part 'model/maintenance_integrity_report_response_dto.dart'; +part 'model/maintenance_integrity_report_summary_response_dto.dart'; part 'model/maintenance_login_dto.dart'; part 'model/manual_job_name.dart'; part 'model/map_marker_response_dto.dart'; diff --git a/mobile/openapi/lib/api/maintenance_admin_api.dart b/mobile/openapi/lib/api/maintenance_admin_api.dart index c0a9aa862f..6023b36fc6 100644 --- a/mobile/openapi/lib/api/maintenance_admin_api.dart +++ b/mobile/openapi/lib/api/maintenance_admin_api.dart @@ -16,14 +16,70 @@ class MaintenanceAdminApi { final ApiClient apiClient; - /// Get integrity report + /// Get integrity report by type /// /// ... /// /// Note: This method returns the HTTP [Response]. - Future getIntegrityReportWithHttpInfo() async { + /// + /// Parameters: + /// + /// * [MaintenanceGetIntegrityReportDto] maintenanceGetIntegrityReportDto (required): + Future getIntegrityReportWithHttpInfo(MaintenanceGetIntegrityReportDto maintenanceGetIntegrityReportDto,) async { // ignore: prefer_const_declarations - final apiPath = r'/admin/maintenance'; + final apiPath = r'/admin/maintenance/integrity/report'; + + // ignore: prefer_final_locals + Object? postBody = maintenanceGetIntegrityReportDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Get integrity report by type + /// + /// ... + /// + /// Parameters: + /// + /// * [MaintenanceGetIntegrityReportDto] maintenanceGetIntegrityReportDto (required): + Future getIntegrityReport(MaintenanceGetIntegrityReportDto maintenanceGetIntegrityReportDto,) async { + final response = await getIntegrityReportWithHttpInfo(maintenanceGetIntegrityReportDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MaintenanceIntegrityReportResponseDto',) as MaintenanceIntegrityReportResponseDto; + + } + return null; + } + + /// Get integrity report summary + /// + /// ... + /// + /// Note: This method returns the HTTP [Response]. + Future getIntegrityReportSummaryWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/admin/maintenance/integrity/summary'; // ignore: prefer_final_locals Object? postBody; @@ -46,11 +102,11 @@ class MaintenanceAdminApi { ); } - /// Get integrity report + /// Get integrity report summary /// /// ... - Future getIntegrityReport() async { - final response = await getIntegrityReportWithHttpInfo(); + Future getIntegrityReportSummary() async { + final response = await getIntegrityReportSummaryWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -58,7 +114,7 @@ class MaintenanceAdminApi { // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" // FormatException when trying to decode an empty string. if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { - return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MaintenanceIntegrityReportResponseDto',) as MaintenanceIntegrityReportResponseDto; + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MaintenanceIntegrityReportSummaryResponseDto',) as MaintenanceIntegrityReportSummaryResponseDto; } return null; diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index c522db3023..889c18bde0 100644 --- a/mobile/openapi/lib/api_client.dart +++ b/mobile/openapi/lib/api_client.dart @@ -356,6 +356,8 @@ class ApiClient { return FoldersUpdate.fromJson(value); case 'ImageFormat': return ImageFormatTypeTransformer().decode(value); + case 'IntegrityReportType': + return IntegrityReportTypeTypeTransformer().decode(value); case 'JobCreateDto': return JobCreateDto.fromJson(value); case 'JobName': @@ -384,10 +386,14 @@ class ApiClient { return MaintenanceActionTypeTransformer().decode(value); case 'MaintenanceAuthDto': return MaintenanceAuthDto.fromJson(value); + case 'MaintenanceGetIntegrityReportDto': + return MaintenanceGetIntegrityReportDto.fromJson(value); case 'MaintenanceIntegrityReportDto': return MaintenanceIntegrityReportDto.fromJson(value); case 'MaintenanceIntegrityReportResponseDto': return MaintenanceIntegrityReportResponseDto.fromJson(value); + case 'MaintenanceIntegrityReportSummaryResponseDto': + return MaintenanceIntegrityReportSummaryResponseDto.fromJson(value); case 'MaintenanceLoginDto': return MaintenanceLoginDto.fromJson(value); case 'ManualJobName': diff --git a/mobile/openapi/lib/api_helper.dart b/mobile/openapi/lib/api_helper.dart index 2c97eeb314..85477a4f6c 100644 --- a/mobile/openapi/lib/api_helper.dart +++ b/mobile/openapi/lib/api_helper.dart @@ -94,6 +94,9 @@ String parameterToString(dynamic value) { if (value is ImageFormat) { return ImageFormatTypeTransformer().encode(value).toString(); } + if (value is IntegrityReportType) { + return IntegrityReportTypeTypeTransformer().encode(value).toString(); + } if (value is JobName) { return JobNameTypeTransformer().encode(value).toString(); } diff --git a/mobile/openapi/lib/model/integrity_report_type.dart b/mobile/openapi/lib/model/integrity_report_type.dart new file mode 100644 index 0000000000..f027cd6f5a --- /dev/null +++ b/mobile/openapi/lib/model/integrity_report_type.dart @@ -0,0 +1,88 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// 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 IntegrityReportType { + /// Instantiate a new enum with the provided [value]. + const IntegrityReportType._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const orphanFile = IntegrityReportType._(r'orphan_file'); + static const missingFile = IntegrityReportType._(r'missing_file'); + static const checksumMismatch = IntegrityReportType._(r'checksum_mismatch'); + + /// List of all possible values in this [enum][IntegrityReportType]. + static const values = [ + orphanFile, + missingFile, + checksumMismatch, + ]; + + static IntegrityReportType? fromJson(dynamic value) => IntegrityReportTypeTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = IntegrityReportType.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [IntegrityReportType] to String, +/// and [decode] dynamic data back to [IntegrityReportType]. +class IntegrityReportTypeTypeTransformer { + factory IntegrityReportTypeTypeTransformer() => _instance ??= const IntegrityReportTypeTypeTransformer._(); + + const IntegrityReportTypeTypeTransformer._(); + + String encode(IntegrityReportType data) => data.value; + + /// Decodes a [dynamic value][data] to a IntegrityReportType. + /// + /// 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. + IntegrityReportType? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'orphan_file': return IntegrityReportType.orphanFile; + case r'missing_file': return IntegrityReportType.missingFile; + case r'checksum_mismatch': return IntegrityReportType.checksumMismatch; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [IntegrityReportTypeTypeTransformer] instance. + static IntegrityReportTypeTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/maintenance_get_integrity_report_dto.dart b/mobile/openapi/lib/model/maintenance_get_integrity_report_dto.dart new file mode 100644 index 0000000000..691daa5ea7 --- /dev/null +++ b/mobile/openapi/lib/model/maintenance_get_integrity_report_dto.dart @@ -0,0 +1,99 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// 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 MaintenanceGetIntegrityReportDto { + /// Returns a new [MaintenanceGetIntegrityReportDto] instance. + MaintenanceGetIntegrityReportDto({ + required this.type, + }); + + IntegrityReportType type; + + @override + bool operator ==(Object other) => identical(this, other) || other is MaintenanceGetIntegrityReportDto && + other.type == type; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (type.hashCode); + + @override + String toString() => 'MaintenanceGetIntegrityReportDto[type=$type]'; + + Map toJson() { + final json = {}; + json[r'type'] = this.type; + return json; + } + + /// Returns a new [MaintenanceGetIntegrityReportDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static MaintenanceGetIntegrityReportDto? fromJson(dynamic value) { + upgradeDto(value, "MaintenanceGetIntegrityReportDto"); + if (value is Map) { + final json = value.cast(); + + return MaintenanceGetIntegrityReportDto( + type: IntegrityReportType.fromJson(json[r'type'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = MaintenanceGetIntegrityReportDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = MaintenanceGetIntegrityReportDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of MaintenanceGetIntegrityReportDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = MaintenanceGetIntegrityReportDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'type', + }; +} + diff --git a/mobile/openapi/lib/model/maintenance_integrity_report_dto.dart b/mobile/openapi/lib/model/maintenance_integrity_report_dto.dart index c5e4baad86..777a6f0534 100644 --- a/mobile/openapi/lib/model/maintenance_integrity_report_dto.dart +++ b/mobile/openapi/lib/model/maintenance_integrity_report_dto.dart @@ -22,7 +22,7 @@ class MaintenanceIntegrityReportDto { String path; - MaintenanceIntegrityReportDtoTypeEnum type; + IntegrityReportType type; @override bool operator ==(Object other) => identical(this, other) || other is MaintenanceIntegrityReportDto && @@ -59,7 +59,7 @@ class MaintenanceIntegrityReportDto { return MaintenanceIntegrityReportDto( id: mapValueOfType(json, r'id')!, path: mapValueOfType(json, r'path')!, - type: MaintenanceIntegrityReportDtoTypeEnum.fromJson(json[r'type'])!, + type: IntegrityReportType.fromJson(json[r'type'])!, ); } return null; @@ -113,80 +113,3 @@ class MaintenanceIntegrityReportDto { }; } - -class MaintenanceIntegrityReportDtoTypeEnum { - /// Instantiate a new enum with the provided [value]. - const MaintenanceIntegrityReportDtoTypeEnum._(this.value); - - /// The underlying value of this enum member. - final String value; - - @override - String toString() => value; - - String toJson() => value; - - static const orphanFile = MaintenanceIntegrityReportDtoTypeEnum._(r'orphan_file'); - static const missingFile = MaintenanceIntegrityReportDtoTypeEnum._(r'missing_file'); - static const checksumMismatch = MaintenanceIntegrityReportDtoTypeEnum._(r'checksum_mismatch'); - - /// List of all possible values in this [enum][MaintenanceIntegrityReportDtoTypeEnum]. - static const values = [ - orphanFile, - missingFile, - checksumMismatch, - ]; - - static MaintenanceIntegrityReportDtoTypeEnum? fromJson(dynamic value) => MaintenanceIntegrityReportDtoTypeEnumTypeTransformer().decode(value); - - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; - if (json is List && json.isNotEmpty) { - for (final row in json) { - final value = MaintenanceIntegrityReportDtoTypeEnum.fromJson(row); - if (value != null) { - result.add(value); - } - } - } - return result.toList(growable: growable); - } -} - -/// Transformation class that can [encode] an instance of [MaintenanceIntegrityReportDtoTypeEnum] to String, -/// and [decode] dynamic data back to [MaintenanceIntegrityReportDtoTypeEnum]. -class MaintenanceIntegrityReportDtoTypeEnumTypeTransformer { - factory MaintenanceIntegrityReportDtoTypeEnumTypeTransformer() => _instance ??= const MaintenanceIntegrityReportDtoTypeEnumTypeTransformer._(); - - const MaintenanceIntegrityReportDtoTypeEnumTypeTransformer._(); - - String encode(MaintenanceIntegrityReportDtoTypeEnum data) => data.value; - - /// Decodes a [dynamic value][data] to a MaintenanceIntegrityReportDtoTypeEnum. - /// - /// 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. - MaintenanceIntegrityReportDtoTypeEnum? decode(dynamic data, {bool allowNull = true}) { - if (data != null) { - switch (data) { - case r'orphan_file': return MaintenanceIntegrityReportDtoTypeEnum.orphanFile; - case r'missing_file': return MaintenanceIntegrityReportDtoTypeEnum.missingFile; - case r'checksum_mismatch': return MaintenanceIntegrityReportDtoTypeEnum.checksumMismatch; - default: - if (!allowNull) { - throw ArgumentError('Unknown enum value to decode: $data'); - } - } - } - return null; - } - - /// Singleton [MaintenanceIntegrityReportDtoTypeEnumTypeTransformer] instance. - static MaintenanceIntegrityReportDtoTypeEnumTypeTransformer? _instance; -} - - diff --git a/mobile/openapi/lib/model/maintenance_integrity_report_summary_response_dto.dart b/mobile/openapi/lib/model/maintenance_integrity_report_summary_response_dto.dart new file mode 100644 index 0000000000..fbf53b9436 --- /dev/null +++ b/mobile/openapi/lib/model/maintenance_integrity_report_summary_response_dto.dart @@ -0,0 +1,115 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// 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 MaintenanceIntegrityReportSummaryResponseDto { + /// Returns a new [MaintenanceIntegrityReportSummaryResponseDto] instance. + MaintenanceIntegrityReportSummaryResponseDto({ + required this.checksumMismatch, + required this.missingFile, + required this.orphanFile, + }); + + int checksumMismatch; + + int missingFile; + + int orphanFile; + + @override + bool operator ==(Object other) => identical(this, other) || other is MaintenanceIntegrityReportSummaryResponseDto && + other.checksumMismatch == checksumMismatch && + other.missingFile == missingFile && + other.orphanFile == orphanFile; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (checksumMismatch.hashCode) + + (missingFile.hashCode) + + (orphanFile.hashCode); + + @override + String toString() => 'MaintenanceIntegrityReportSummaryResponseDto[checksumMismatch=$checksumMismatch, missingFile=$missingFile, orphanFile=$orphanFile]'; + + Map toJson() { + final json = {}; + json[r'checksum_mismatch'] = this.checksumMismatch; + json[r'missing_file'] = this.missingFile; + json[r'orphan_file'] = this.orphanFile; + return json; + } + + /// Returns a new [MaintenanceIntegrityReportSummaryResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static MaintenanceIntegrityReportSummaryResponseDto? fromJson(dynamic value) { + upgradeDto(value, "MaintenanceIntegrityReportSummaryResponseDto"); + if (value is Map) { + final json = value.cast(); + + return MaintenanceIntegrityReportSummaryResponseDto( + checksumMismatch: mapValueOfType(json, r'checksum_mismatch')!, + missingFile: mapValueOfType(json, r'missing_file')!, + orphanFile: mapValueOfType(json, r'orphan_file')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = MaintenanceIntegrityReportSummaryResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = MaintenanceIntegrityReportSummaryResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of MaintenanceIntegrityReportSummaryResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = MaintenanceIntegrityReportSummaryResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'checksum_mismatch', + 'missing_file', + 'orphan_file', + }; +} + diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index 8e39950f20..4be3cfadfb 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -323,51 +323,6 @@ } }, "/admin/maintenance": { - "get": { - "description": "...", - "operationId": "getIntegrityReport", - "parameters": [], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MaintenanceIntegrityReportResponseDto" - } - } - }, - "description": "" - } - }, - "security": [ - { - "bearer": [] - }, - { - "cookie": [] - }, - { - "api_key": [] - } - ], - "summary": "Get integrity report", - "tags": [ - "Maintenance (admin)" - ], - "x-immich-admin-only": true, - "x-immich-history": [ - { - "version": "v9.9.9", - "state": "Added" - }, - { - "version": "v9.9.9", - "state": "Alpha" - } - ], - "x-immich-permission": "maintenance", - "x-immich-state": "Alpha" - }, "post": { "description": "Put Immich into or take it out of maintenance mode", "operationId": "setMaintenanceMode", @@ -417,6 +372,110 @@ "x-immich-state": "Alpha" } }, + "/admin/maintenance/integrity/report": { + "post": { + "description": "...", + "operationId": "getIntegrityReport", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MaintenanceGetIntegrityReportDto" + } + } + }, + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MaintenanceIntegrityReportResponseDto" + } + } + }, + "description": "" + } + }, + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ], + "summary": "Get integrity report by type", + "tags": [ + "Maintenance (admin)" + ], + "x-immich-admin-only": true, + "x-immich-history": [ + { + "version": "v9.9.9", + "state": "Added" + }, + { + "version": "v9.9.9", + "state": "Alpha" + } + ], + "x-immich-permission": "maintenance", + "x-immich-state": "Alpha" + } + }, + "/admin/maintenance/integrity/summary": { + "get": { + "description": "...", + "operationId": "getIntegrityReportSummary", + "parameters": [], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MaintenanceIntegrityReportSummaryResponseDto" + } + } + }, + "description": "" + } + }, + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ], + "summary": "Get integrity report summary", + "tags": [ + "Maintenance (admin)" + ], + "x-immich-admin-only": true, + "x-immich-history": [ + { + "version": "v9.9.9", + "state": "Added" + }, + { + "version": "v9.9.9", + "state": "Alpha" + } + ], + "x-immich-permission": "maintenance", + "x-immich-state": "Alpha" + } + }, "/admin/maintenance/login": { "post": { "description": "Login with maintenance token or cookie to receive current information and perform further actions.", @@ -16634,6 +16693,14 @@ ], "type": "string" }, + "IntegrityReportType": { + "enum": [ + "orphan_file", + "missing_file", + "checksum_mismatch" + ], + "type": "string" + }, "JobCreateDto": { "properties": { "name": { @@ -16965,6 +17032,21 @@ ], "type": "object" }, + "MaintenanceGetIntegrityReportDto": { + "properties": { + "type": { + "allOf": [ + { + "$ref": "#/components/schemas/IntegrityReportType" + } + ] + } + }, + "required": [ + "type" + ], + "type": "object" + }, "MaintenanceIntegrityReportDto": { "properties": { "id": { @@ -16974,12 +17056,11 @@ "type": "string" }, "type": { - "enum": [ - "orphan_file", - "missing_file", - "checksum_mismatch" - ], - "type": "string" + "allOf": [ + { + "$ref": "#/components/schemas/IntegrityReportType" + } + ] } }, "required": [ @@ -17003,6 +17084,25 @@ ], "type": "object" }, + "MaintenanceIntegrityReportSummaryResponseDto": { + "properties": { + "checksum_mismatch": { + "type": "integer" + }, + "missing_file": { + "type": "integer" + }, + "orphan_file": { + "type": "integer" + } + }, + "required": [ + "checksum_mismatch", + "missing_file", + "orphan_file" + ], + "type": "object" + }, "MaintenanceLoginDto": { "properties": { "token": { diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts index d4802f2abc..79ed06a7f8 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/open-api/typescript-sdk/src/fetch-client.ts @@ -40,16 +40,24 @@ export type ActivityStatisticsResponseDto = { comments: number; likes: number; }; +export type SetMaintenanceModeDto = { + action: MaintenanceAction; +}; +export type MaintenanceGetIntegrityReportDto = { + "type": IntegrityReportType; +}; export type MaintenanceIntegrityReportDto = { id: string; path: string; - "type": Type; + "type": IntegrityReportType; }; export type MaintenanceIntegrityReportResponseDto = { items: MaintenanceIntegrityReportDto[]; }; -export type SetMaintenanceModeDto = { - action: MaintenanceAction; +export type MaintenanceIntegrityReportSummaryResponseDto = { + checksum_mismatch: number; + missing_file: number; + orphan_file: number; }; export type MaintenanceLoginDto = { token?: string; @@ -1874,17 +1882,6 @@ export function unlinkAllOAuthAccountsAdmin(opts?: Oazapfts.RequestOpts) { method: "POST" })); } -/** - * Get integrity report - */ -export function getIntegrityReport(opts?: Oazapfts.RequestOpts) { - return oazapfts.ok(oazapfts.fetchJson<{ - status: 200; - data: MaintenanceIntegrityReportResponseDto; - }>("/admin/maintenance", { - ...opts - })); -} /** * Set maintenance mode */ @@ -1897,6 +1894,32 @@ export function setMaintenanceMode({ setMaintenanceModeDto }: { body: setMaintenanceModeDto }))); } +/** + * Get integrity report by type + */ +export function getIntegrityReport({ maintenanceGetIntegrityReportDto }: { + maintenanceGetIntegrityReportDto: MaintenanceGetIntegrityReportDto; +}, opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchJson<{ + status: 201; + data: MaintenanceIntegrityReportResponseDto; + }>("/admin/maintenance/integrity/report", oazapfts.json({ + ...opts, + method: "POST", + body: maintenanceGetIntegrityReportDto + }))); +} +/** + * Get integrity report summary + */ +export function getIntegrityReportSummary(opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchJson<{ + status: 200; + data: MaintenanceIntegrityReportSummaryResponseDto; + }>("/admin/maintenance/integrity/summary", { + ...opts + })); +} /** * Log into maintenance mode */ @@ -5173,15 +5196,15 @@ export enum UserAvatarColor { Gray = "gray", Amber = "amber" } -export enum Type { - OrphanFile = "orphan_file", - MissingFile = "missing_file", - ChecksumMismatch = "checksum_mismatch" -} export enum MaintenanceAction { Start = "start", End = "end" } +export enum IntegrityReportType { + OrphanFile = "orphan_file", + MissingFile = "missing_file", + ChecksumMismatch = "checksum_mismatch" +} export enum NotificationLevel { Success = "success", Error = "error", diff --git a/server/src/controllers/maintenance.controller.ts b/server/src/controllers/maintenance.controller.ts index 36e8003521..8f0d7ab723 100644 --- a/server/src/controllers/maintenance.controller.ts +++ b/server/src/controllers/maintenance.controller.ts @@ -7,6 +7,7 @@ import { MaintenanceAuthDto, MaintenanceGetIntegrityReportDto, MaintenanceIntegrityReportResponseDto, + MaintenanceIntegrityReportSummaryResponseDto, MaintenanceLoginDto, SetMaintenanceModeDto, } from 'src/dtos/maintenance.dto'; @@ -53,14 +54,25 @@ export class MaintenanceController { } } - @Get() + @Get('integrity/summary') @Endpoint({ - summary: 'Get integrity report', + summary: 'Get integrity report summary', description: '...', history: new HistoryBuilder().added('v9.9.9').alpha('v9.9.9'), }) @Authenticated({ permission: Permission.Maintenance, admin: true }) - getIntegrityReport(dto: MaintenanceGetIntegrityReportDto): Promise { + getIntegrityReportSummary(): Promise { + return this.service.getIntegrityReportSummary(); // + } + + @Post('integrity/report') + @Endpoint({ + summary: 'Get integrity report by type', + description: '...', + history: new HistoryBuilder().added('v9.9.9').alpha('v9.9.9'), + }) + @Authenticated({ permission: Permission.Maintenance, admin: true }) + getIntegrityReport(@Body() dto: MaintenanceGetIntegrityReportDto): Promise { return this.service.getIntegrityReport(dto); } } diff --git a/server/src/dtos/maintenance.dto.ts b/server/src/dtos/maintenance.dto.ts index 444b843e92..c4bf26a315 100644 --- a/server/src/dtos/maintenance.dto.ts +++ b/server/src/dtos/maintenance.dto.ts @@ -1,4 +1,4 @@ -import { IsEnum } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger'; import { IntegrityReportType, MaintenanceAction } from 'src/enum'; import { ValidateEnum, ValidateString } from 'src/validation'; @@ -16,7 +16,19 @@ export class MaintenanceAuthDto { username!: string; } +export class MaintenanceIntegrityReportSummaryResponseDto { + @ApiProperty({ type: 'integer' }) + [IntegrityReportType.ChecksumFail]!: number; + @ApiProperty({ type: 'integer' }) + [IntegrityReportType.MissingFile]!: number; + @ApiProperty({ type: 'integer' }) + [IntegrityReportType.OrphanFile]!: number; +} + export class MaintenanceGetIntegrityReportDto { + @ValidateEnum({ enum: IntegrityReportType, name: 'IntegrityReportType' }) + type!: IntegrityReportType; + // todo: paginate // @IsInt() // @Min(1) @@ -27,7 +39,7 @@ export class MaintenanceGetIntegrityReportDto { class MaintenanceIntegrityReportDto { id!: string; - @IsEnum(IntegrityReportType) + @ValidateEnum({ enum: IntegrityReportType, name: 'IntegrityReportType' }) type!: IntegrityReportType; path!: string; } diff --git a/server/src/repositories/integrity-report.repository.ts b/server/src/repositories/integrity-report.repository.ts index e3b9af6457..9dcf8ca8e6 100644 --- a/server/src/repositories/integrity-report.repository.ts +++ b/server/src/repositories/integrity-report.repository.ts @@ -1,7 +1,12 @@ import { Injectable } from '@nestjs/common'; import { Insertable, Kysely } from 'kysely'; import { InjectKysely } from 'nestjs-kysely'; -import { MaintenanceGetIntegrityReportDto, MaintenanceIntegrityReportResponseDto } from 'src/dtos/maintenance.dto'; +import { + MaintenanceGetIntegrityReportDto, + MaintenanceIntegrityReportResponseDto, + MaintenanceIntegrityReportSummaryResponseDto, +} from 'src/dtos/maintenance.dto'; +import { IntegrityReportType } from 'src/enum'; import { DB } from 'src/schema'; import { IntegrityReportTable } from 'src/schema/tables/integrity-report.table'; @@ -18,11 +23,36 @@ export class IntegrityReportRepository { .executeTakeFirst(); } - async getIntegrityReport(_dto: MaintenanceGetIntegrityReportDto): Promise { + async getIntegrityReportSummary(): Promise { + return await this.db + .selectFrom('integrity_report') + .select((eb) => + eb.fn + .countAll() + .filterWhere('type', '=', IntegrityReportType.ChecksumFail) + .as(IntegrityReportType.ChecksumFail), + ) + .select((eb) => + eb.fn + .countAll() + .filterWhere('type', '=', IntegrityReportType.MissingFile) + .as(IntegrityReportType.MissingFile), + ) + .select((eb) => + eb.fn + .countAll() + .filterWhere('type', '=', IntegrityReportType.OrphanFile) + .as(IntegrityReportType.OrphanFile), + ) + .executeTakeFirstOrThrow(); + } + + async getIntegrityReport(dto: MaintenanceGetIntegrityReportDto): Promise { return { items: await this.db .selectFrom('integrity_report') .select(['id', 'type', 'path']) + .where('type', '=', dto.type) .orderBy('createdAt', 'desc') .execute(), }; diff --git a/server/src/services/maintenance.service.ts b/server/src/services/maintenance.service.ts index 7438acfc1d..af41eeed8f 100644 --- a/server/src/services/maintenance.service.ts +++ b/server/src/services/maintenance.service.ts @@ -4,6 +4,7 @@ import { MaintenanceAuthDto, MaintenanceGetIntegrityReportDto, MaintenanceIntegrityReportResponseDto, + MaintenanceIntegrityReportSummaryResponseDto, } from 'src/dtos/maintenance.dto'; import { SystemMetadataKey } from 'src/enum'; import { BaseService } from 'src/services/base.service'; @@ -55,6 +56,10 @@ export class MaintenanceService extends BaseService { return await createMaintenanceLoginUrl(baseUrl, auth, secret); } + getIntegrityReportSummary(): Promise { + return this.integrityReportRepository.getIntegrityReportSummary(); + } + getIntegrityReport(dto: MaintenanceGetIntegrityReportDto): Promise { return this.integrityReportRepository.getIntegrityReport(dto); } diff --git a/web/src/lib/components/server-statistics/ServerStatisticsCard.svelte b/web/src/lib/components/server-statistics/ServerStatisticsCard.svelte index 61d4c643c8..f48d48805d 100644 --- a/web/src/lib/components/server-statistics/ServerStatisticsCard.svelte +++ b/web/src/lib/components/server-statistics/ServerStatisticsCard.svelte @@ -1,15 +1,17 @@ @@ -11,7 +11,7 @@ - + diff --git a/web/src/routes/admin/maintenance/+page.svelte b/web/src/routes/admin/maintenance/+page.svelte index 12a0e4067d..b16fec94be 100644 --- a/web/src/routes/admin/maintenance/+page.svelte +++ b/web/src/routes/admin/maintenance/+page.svelte @@ -1,12 +1,11 @@ + + + + +
+
+ + + + + + + + + {#each data.integrityReport.items as { id, path } (id)} + + + + + {/each} + +
{$t('filename')}
{path} +
+
+
+
diff --git a/web/src/routes/admin/maintenance/integrity-report/[type]/+page.ts b/web/src/routes/admin/maintenance/integrity-report/[type]/+page.ts new file mode 100644 index 0000000000..9c6b73ec0e --- /dev/null +++ b/web/src/routes/admin/maintenance/integrity-report/[type]/+page.ts @@ -0,0 +1,23 @@ +import { authenticate } from '$lib/utils/auth'; +import { getFormatter } from '$lib/utils/i18n'; +import { getIntegrityReport, IntegrityReportType } from '@immich/sdk'; +import type { PageLoad } from './$types'; + +export const load = (async ({ params, url }) => { + const type = params.type as IntegrityReportType; + + await authenticate(url, { admin: true }); + const integrityReport = await getIntegrityReport({ + maintenanceGetIntegrityReportDto: { + type, + }, + }); + const $t = await getFormatter(); + + return { + integrityReport, + meta: { + title: $t(`admin.maintenance_integrity_${type}`), + }, + }; +}) satisfies PageLoad;