mirror of
				https://github.com/KevinMidboe/immich.git
				synced 2025-10-29 17:40:28 +00:00 
			
		
		
		
	feat(server/web): jobs clear button + queue status (#2144)
* feat(server/web): jobs clear button + queue status * adjust design and colors * Adjust some styling * show status next to buttons instead of on top * Update rounded corner for badge --------- Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
		
							
								
								
									
										6
									
								
								mobile/openapi/.openapi-generator/FILES
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										6
									
								
								mobile/openapi/.openapi-generator/FILES
									
									
									
										generated
									
									
									
								
							| @@ -53,6 +53,7 @@ doc/JobCommand.md | ||||
| doc/JobCommandDto.md | ||||
| doc/JobCountsDto.md | ||||
| doc/JobName.md | ||||
| doc/JobStatusDto.md | ||||
| doc/LoginCredentialDto.md | ||||
| doc/LoginResponseDto.md | ||||
| doc/LogoutResponseDto.md | ||||
| @@ -60,6 +61,7 @@ doc/OAuthApi.md | ||||
| doc/OAuthCallbackDto.md | ||||
| doc/OAuthConfigDto.md | ||||
| doc/OAuthConfigResponseDto.md | ||||
| doc/QueueStatusDto.md | ||||
| doc/RemoveAssetsDto.md | ||||
| doc/SearchAlbumResponseDto.md | ||||
| doc/SearchApi.md | ||||
| @@ -170,12 +172,14 @@ lib/model/job_command.dart | ||||
| lib/model/job_command_dto.dart | ||||
| lib/model/job_counts_dto.dart | ||||
| lib/model/job_name.dart | ||||
| lib/model/job_status_dto.dart | ||||
| lib/model/login_credential_dto.dart | ||||
| lib/model/login_response_dto.dart | ||||
| lib/model/logout_response_dto.dart | ||||
| lib/model/o_auth_callback_dto.dart | ||||
| lib/model/o_auth_config_dto.dart | ||||
| lib/model/o_auth_config_response_dto.dart | ||||
| lib/model/queue_status_dto.dart | ||||
| lib/model/remove_assets_dto.dart | ||||
| lib/model/search_album_response_dto.dart | ||||
| lib/model/search_asset_dto.dart | ||||
| @@ -264,6 +268,7 @@ test/job_command_dto_test.dart | ||||
| test/job_command_test.dart | ||||
| test/job_counts_dto_test.dart | ||||
| test/job_name_test.dart | ||||
| test/job_status_dto_test.dart | ||||
| test/login_credential_dto_test.dart | ||||
| test/login_response_dto_test.dart | ||||
| test/logout_response_dto_test.dart | ||||
| @@ -271,6 +276,7 @@ test/o_auth_api_test.dart | ||||
| test/o_auth_callback_dto_test.dart | ||||
| test/o_auth_config_dto_test.dart | ||||
| test/o_auth_config_response_dto_test.dart | ||||
| test/queue_status_dto_test.dart | ||||
| test/remove_assets_dto_test.dart | ||||
| test/search_album_response_dto_test.dart | ||||
| test/search_api_test.dart | ||||
|   | ||||
							
								
								
									
										2
									
								
								mobile/openapi/README.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								mobile/openapi/README.md
									
									
									
										generated
									
									
									
								
							| @@ -200,12 +200,14 @@ Class | Method | HTTP request | Description | ||||
|  - [JobCommandDto](doc//JobCommandDto.md) | ||||
|  - [JobCountsDto](doc//JobCountsDto.md) | ||||
|  - [JobName](doc//JobName.md) | ||||
|  - [JobStatusDto](doc//JobStatusDto.md) | ||||
|  - [LoginCredentialDto](doc//LoginCredentialDto.md) | ||||
|  - [LoginResponseDto](doc//LoginResponseDto.md) | ||||
|  - [LogoutResponseDto](doc//LogoutResponseDto.md) | ||||
|  - [OAuthCallbackDto](doc//OAuthCallbackDto.md) | ||||
|  - [OAuthConfigDto](doc//OAuthConfigDto.md) | ||||
|  - [OAuthConfigResponseDto](doc//OAuthConfigResponseDto.md) | ||||
|  - [QueueStatusDto](doc//QueueStatusDto.md) | ||||
|  - [RemoveAssetsDto](doc//RemoveAssetsDto.md) | ||||
|  - [SearchAlbumResponseDto](doc//SearchAlbumResponseDto.md) | ||||
|  - [SearchAssetDto](doc//SearchAssetDto.md) | ||||
|   | ||||
							
								
								
									
										16
									
								
								mobile/openapi/doc/AllJobStatusResponseDto.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										16
									
								
								mobile/openapi/doc/AllJobStatusResponseDto.md
									
									
									
										generated
									
									
									
								
							| @@ -8,14 +8,14 @@ import 'package:openapi/api.dart'; | ||||
| ## Properties | ||||
| Name | Type | Description | Notes | ||||
| ------------ | ------------- | ------------- | ------------- | ||||
| **thumbnailGenerationQueue** | [**JobCountsDto**](JobCountsDto.md) |  |  | ||||
| **metadataExtractionQueue** | [**JobCountsDto**](JobCountsDto.md) |  |  | ||||
| **videoConversionQueue** | [**JobCountsDto**](JobCountsDto.md) |  |  | ||||
| **objectTaggingQueue** | [**JobCountsDto**](JobCountsDto.md) |  |  | ||||
| **clipEncodingQueue** | [**JobCountsDto**](JobCountsDto.md) |  |  | ||||
| **storageTemplateMigrationQueue** | [**JobCountsDto**](JobCountsDto.md) |  |  | ||||
| **backgroundTaskQueue** | [**JobCountsDto**](JobCountsDto.md) |  |  | ||||
| **searchQueue** | [**JobCountsDto**](JobCountsDto.md) |  |  | ||||
| **thumbnailGenerationQueue** | [**JobStatusDto**](JobStatusDto.md) |  |  | ||||
| **metadataExtractionQueue** | [**JobStatusDto**](JobStatusDto.md) |  |  | ||||
| **videoConversionQueue** | [**JobStatusDto**](JobStatusDto.md) |  |  | ||||
| **objectTaggingQueue** | [**JobStatusDto**](JobStatusDto.md) |  |  | ||||
| **clipEncodingQueue** | [**JobStatusDto**](JobStatusDto.md) |  |  | ||||
| **storageTemplateMigrationQueue** | [**JobStatusDto**](JobStatusDto.md) |  |  | ||||
| **backgroundTaskQueue** | [**JobStatusDto**](JobStatusDto.md) |  |  | ||||
| **searchQueue** | [**JobStatusDto**](JobStatusDto.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) | ||||
| 
 | ||||
|   | ||||
							
								
								
									
										9
									
								
								mobile/openapi/doc/JobApi.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										9
									
								
								mobile/openapi/doc/JobApi.md
									
									
									
										generated
									
									
									
								
							| @@ -63,7 +63,7 @@ This endpoint does not need any parameter. | ||||
| [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) | ||||
| 
 | ||||
| # **sendJobCommand** | ||||
| > sendJobCommand(jobId, jobCommandDto) | ||||
| > JobStatusDto sendJobCommand(jobId, jobCommandDto) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| @@ -88,7 +88,8 @@ final jobId = ; // JobName | | ||||
| final jobCommandDto = JobCommandDto(); // JobCommandDto |  | ||||
| 
 | ||||
| try { | ||||
|     api_instance.sendJobCommand(jobId, jobCommandDto); | ||||
|     final result = api_instance.sendJobCommand(jobId, jobCommandDto); | ||||
|     print(result); | ||||
| } catch (e) { | ||||
|     print('Exception when calling JobApi->sendJobCommand: $e\n'); | ||||
| } | ||||
| @@ -103,7 +104,7 @@ Name | Type | Description  | Notes | ||||
| 
 | ||||
| ### Return type | ||||
| 
 | ||||
| void (empty response body) | ||||
| [**JobStatusDto**](JobStatusDto.md) | ||||
| 
 | ||||
| ### Authorization | ||||
| 
 | ||||
| @@ -112,7 +113,7 @@ void (empty response body) | ||||
| ### HTTP request headers | ||||
| 
 | ||||
|  - **Content-Type**: application/json | ||||
|  - **Accept**: Not defined | ||||
|  - **Accept**: application/json | ||||
| 
 | ||||
| [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) | ||||
| 
 | ||||
|   | ||||
							
								
								
									
										16
									
								
								mobile/openapi/doc/JobStatusDto.md
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								mobile/openapi/doc/JobStatusDto.md
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| # openapi.model.JobStatusDto | ||||
| 
 | ||||
| ## Load the model package | ||||
| ```dart | ||||
| import 'package:openapi/api.dart'; | ||||
| ``` | ||||
| 
 | ||||
| ## Properties | ||||
| Name | Type | Description | Notes | ||||
| ------------ | ------------- | ------------- | ------------- | ||||
| **jobCounts** | [**JobCountsDto**](JobCountsDto.md) |  |  | ||||
| **queueStatus** | [**QueueStatusDto**](QueueStatusDto.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) | ||||
| 
 | ||||
| 
 | ||||
							
								
								
									
										16
									
								
								mobile/openapi/doc/QueueStatusDto.md
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								mobile/openapi/doc/QueueStatusDto.md
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| # openapi.model.QueueStatusDto | ||||
| 
 | ||||
| ## Load the model package | ||||
| ```dart | ||||
| import 'package:openapi/api.dart'; | ||||
| ``` | ||||
| 
 | ||||
| ## Properties | ||||
| Name | Type | Description | Notes | ||||
| ------------ | ------------- | ------------- | ------------- | ||||
| **isActive** | **bool** |  |  | ||||
| **isPaused** | **bool** |  |  | ||||
| 
 | ||||
| [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||
| 
 | ||||
| 
 | ||||
							
								
								
									
										2
									
								
								mobile/openapi/lib/api.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								mobile/openapi/lib/api.dart
									
									
									
										generated
									
									
									
								
							| @@ -86,12 +86,14 @@ part 'model/job_command.dart'; | ||||
| part 'model/job_command_dto.dart'; | ||||
| part 'model/job_counts_dto.dart'; | ||||
| part 'model/job_name.dart'; | ||||
| part 'model/job_status_dto.dart'; | ||||
| part 'model/login_credential_dto.dart'; | ||||
| part 'model/login_response_dto.dart'; | ||||
| part 'model/logout_response_dto.dart'; | ||||
| part 'model/o_auth_callback_dto.dart'; | ||||
| part 'model/o_auth_config_dto.dart'; | ||||
| part 'model/o_auth_config_response_dto.dart'; | ||||
| part 'model/queue_status_dto.dart'; | ||||
| part 'model/remove_assets_dto.dart'; | ||||
| part 'model/search_album_response_dto.dart'; | ||||
| part 'model/search_asset_dto.dart'; | ||||
|   | ||||
							
								
								
									
										10
									
								
								mobile/openapi/lib/api/job_api.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										10
									
								
								mobile/openapi/lib/api/job_api.dart
									
									
									
										generated
									
									
									
								
							| @@ -102,10 +102,18 @@ class JobApi { | ||||
|   /// * [JobName] jobId (required): | ||||
|   /// | ||||
|   /// * [JobCommandDto] jobCommandDto (required): | ||||
|   Future<void> sendJobCommand(JobName jobId, JobCommandDto jobCommandDto,) async { | ||||
|   Future<JobStatusDto?> sendJobCommand(JobName jobId, JobCommandDto jobCommandDto,) async { | ||||
|     final response = await sendJobCommandWithHttpInfo(jobId, jobCommandDto,); | ||||
|     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), 'JobStatusDto',) as JobStatusDto; | ||||
|      | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
| } | ||||
|   | ||||
							
								
								
									
										4
									
								
								mobile/openapi/lib/api_client.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4
									
								
								mobile/openapi/lib/api_client.dart
									
									
									
										generated
									
									
									
								
							| @@ -280,6 +280,8 @@ class ApiClient { | ||||
|           return JobCountsDto.fromJson(value); | ||||
|         case 'JobName': | ||||
|           return JobNameTypeTransformer().decode(value); | ||||
|         case 'JobStatusDto': | ||||
|           return JobStatusDto.fromJson(value); | ||||
|         case 'LoginCredentialDto': | ||||
|           return LoginCredentialDto.fromJson(value); | ||||
|         case 'LoginResponseDto': | ||||
| @@ -292,6 +294,8 @@ class ApiClient { | ||||
|           return OAuthConfigDto.fromJson(value); | ||||
|         case 'OAuthConfigResponseDto': | ||||
|           return OAuthConfigResponseDto.fromJson(value); | ||||
|         case 'QueueStatusDto': | ||||
|           return QueueStatusDto.fromJson(value); | ||||
|         case 'RemoveAssetsDto': | ||||
|           return RemoveAssetsDto.fromJson(value); | ||||
|         case 'SearchAlbumResponseDto': | ||||
|   | ||||
| @@ -23,21 +23,21 @@ class AllJobStatusResponseDto { | ||||
|     required this.searchQueue, | ||||
|   }); | ||||
| 
 | ||||
|   JobCountsDto thumbnailGenerationQueue; | ||||
|   JobStatusDto thumbnailGenerationQueue; | ||||
| 
 | ||||
|   JobCountsDto metadataExtractionQueue; | ||||
|   JobStatusDto metadataExtractionQueue; | ||||
| 
 | ||||
|   JobCountsDto videoConversionQueue; | ||||
|   JobStatusDto videoConversionQueue; | ||||
| 
 | ||||
|   JobCountsDto objectTaggingQueue; | ||||
|   JobStatusDto objectTaggingQueue; | ||||
| 
 | ||||
|   JobCountsDto clipEncodingQueue; | ||||
|   JobStatusDto clipEncodingQueue; | ||||
| 
 | ||||
|   JobCountsDto storageTemplateMigrationQueue; | ||||
|   JobStatusDto storageTemplateMigrationQueue; | ||||
| 
 | ||||
|   JobCountsDto backgroundTaskQueue; | ||||
|   JobStatusDto backgroundTaskQueue; | ||||
| 
 | ||||
|   JobCountsDto searchQueue; | ||||
|   JobStatusDto searchQueue; | ||||
| 
 | ||||
|   @override | ||||
|   bool operator ==(Object other) => identical(this, other) || other is AllJobStatusResponseDto && | ||||
| @@ -97,14 +97,14 @@ class AllJobStatusResponseDto { | ||||
|       }()); | ||||
| 
 | ||||
|       return AllJobStatusResponseDto( | ||||
|         thumbnailGenerationQueue: JobCountsDto.fromJson(json[r'thumbnail-generation-queue'])!, | ||||
|         metadataExtractionQueue: JobCountsDto.fromJson(json[r'metadata-extraction-queue'])!, | ||||
|         videoConversionQueue: JobCountsDto.fromJson(json[r'video-conversion-queue'])!, | ||||
|         objectTaggingQueue: JobCountsDto.fromJson(json[r'object-tagging-queue'])!, | ||||
|         clipEncodingQueue: JobCountsDto.fromJson(json[r'clip-encoding-queue'])!, | ||||
|         storageTemplateMigrationQueue: JobCountsDto.fromJson(json[r'storage-template-migration-queue'])!, | ||||
|         backgroundTaskQueue: JobCountsDto.fromJson(json[r'background-task-queue'])!, | ||||
|         searchQueue: JobCountsDto.fromJson(json[r'search-queue'])!, | ||||
|         thumbnailGenerationQueue: JobStatusDto.fromJson(json[r'thumbnail-generation-queue'])!, | ||||
|         metadataExtractionQueue: JobStatusDto.fromJson(json[r'metadata-extraction-queue'])!, | ||||
|         videoConversionQueue: JobStatusDto.fromJson(json[r'video-conversion-queue'])!, | ||||
|         objectTaggingQueue: JobStatusDto.fromJson(json[r'object-tagging-queue'])!, | ||||
|         clipEncodingQueue: JobStatusDto.fromJson(json[r'clip-encoding-queue'])!, | ||||
|         storageTemplateMigrationQueue: JobStatusDto.fromJson(json[r'storage-template-migration-queue'])!, | ||||
|         backgroundTaskQueue: JobStatusDto.fromJson(json[r'background-task-queue'])!, | ||||
|         searchQueue: JobStatusDto.fromJson(json[r'search-queue'])!, | ||||
|       ); | ||||
|     } | ||||
|     return null; | ||||
|   | ||||
							
								
								
									
										119
									
								
								mobile/openapi/lib/model/job_status_dto.dart
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								mobile/openapi/lib/model/job_status_dto.dart
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,119 @@ | ||||
| // | ||||
| // 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 JobStatusDto { | ||||
|   /// Returns a new [JobStatusDto] instance. | ||||
|   JobStatusDto({ | ||||
|     required this.jobCounts, | ||||
|     required this.queueStatus, | ||||
|   }); | ||||
| 
 | ||||
|   JobCountsDto jobCounts; | ||||
| 
 | ||||
|   QueueStatusDto queueStatus; | ||||
| 
 | ||||
|   @override | ||||
|   bool operator ==(Object other) => identical(this, other) || other is JobStatusDto && | ||||
|      other.jobCounts == jobCounts && | ||||
|      other.queueStatus == queueStatus; | ||||
| 
 | ||||
|   @override | ||||
|   int get hashCode => | ||||
|     // ignore: unnecessary_parenthesis | ||||
|     (jobCounts.hashCode) + | ||||
|     (queueStatus.hashCode); | ||||
| 
 | ||||
|   @override | ||||
|   String toString() => 'JobStatusDto[jobCounts=$jobCounts, queueStatus=$queueStatus]'; | ||||
| 
 | ||||
|   Map<String, dynamic> toJson() { | ||||
|     final json = <String, dynamic>{}; | ||||
|       json[r'jobCounts'] = this.jobCounts; | ||||
|       json[r'queueStatus'] = this.queueStatus; | ||||
|     return json; | ||||
|   } | ||||
| 
 | ||||
|   /// Returns a new [JobStatusDto] instance and imports its values from | ||||
|   /// [value] if it's a [Map], null otherwise. | ||||
|   // ignore: prefer_constructors_over_static_methods | ||||
|   static JobStatusDto? fromJson(dynamic value) { | ||||
|     if (value is Map) { | ||||
|       final json = value.cast<String, dynamic>(); | ||||
| 
 | ||||
|       // Ensure that the map contains the required keys. | ||||
|       // Note 1: the values aren't checked for validity beyond being non-null. | ||||
|       // Note 2: this code is stripped in release mode! | ||||
|       assert(() { | ||||
|         requiredKeys.forEach((key) { | ||||
|           assert(json.containsKey(key), 'Required key "JobStatusDto[$key]" is missing from JSON.'); | ||||
|           assert(json[key] != null, 'Required key "JobStatusDto[$key]" has a null value in JSON.'); | ||||
|         }); | ||||
|         return true; | ||||
|       }()); | ||||
| 
 | ||||
|       return JobStatusDto( | ||||
|         jobCounts: JobCountsDto.fromJson(json[r'jobCounts'])!, | ||||
|         queueStatus: QueueStatusDto.fromJson(json[r'queueStatus'])!, | ||||
|       ); | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
| 
 | ||||
|   static List<JobStatusDto>? listFromJson(dynamic json, {bool growable = false,}) { | ||||
|     final result = <JobStatusDto>[]; | ||||
|     if (json is List && json.isNotEmpty) { | ||||
|       for (final row in json) { | ||||
|         final value = JobStatusDto.fromJson(row); | ||||
|         if (value != null) { | ||||
|           result.add(value); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return result.toList(growable: growable); | ||||
|   } | ||||
| 
 | ||||
|   static Map<String, JobStatusDto> mapFromJson(dynamic json) { | ||||
|     final map = <String, JobStatusDto>{}; | ||||
|     if (json is Map && json.isNotEmpty) { | ||||
|       json = json.cast<String, dynamic>(); // ignore: parameter_assignments | ||||
|       for (final entry in json.entries) { | ||||
|         final value = JobStatusDto.fromJson(entry.value); | ||||
|         if (value != null) { | ||||
|           map[entry.key] = value; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return map; | ||||
|   } | ||||
| 
 | ||||
|   // maps a json object with a list of JobStatusDto-objects as value to a dart map | ||||
|   static Map<String, List<JobStatusDto>> mapListFromJson(dynamic json, {bool growable = false,}) { | ||||
|     final map = <String, List<JobStatusDto>>{}; | ||||
|     if (json is Map && json.isNotEmpty) { | ||||
|       json = json.cast<String, dynamic>(); // ignore: parameter_assignments | ||||
|       for (final entry in json.entries) { | ||||
|         final value = JobStatusDto.listFromJson(entry.value, growable: growable,); | ||||
|         if (value != null) { | ||||
|           map[entry.key] = value; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return map; | ||||
|   } | ||||
| 
 | ||||
|   /// The list of required keys that must be present in a JSON. | ||||
|   static const requiredKeys = <String>{ | ||||
|     'jobCounts', | ||||
|     'queueStatus', | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
							
								
								
									
										119
									
								
								mobile/openapi/lib/model/queue_status_dto.dart
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								mobile/openapi/lib/model/queue_status_dto.dart
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,119 @@ | ||||
| // | ||||
| // 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 QueueStatusDto { | ||||
|   /// Returns a new [QueueStatusDto] instance. | ||||
|   QueueStatusDto({ | ||||
|     required this.isActive, | ||||
|     required this.isPaused, | ||||
|   }); | ||||
| 
 | ||||
|   bool isActive; | ||||
| 
 | ||||
|   bool isPaused; | ||||
| 
 | ||||
|   @override | ||||
|   bool operator ==(Object other) => identical(this, other) || other is QueueStatusDto && | ||||
|      other.isActive == isActive && | ||||
|      other.isPaused == isPaused; | ||||
| 
 | ||||
|   @override | ||||
|   int get hashCode => | ||||
|     // ignore: unnecessary_parenthesis | ||||
|     (isActive.hashCode) + | ||||
|     (isPaused.hashCode); | ||||
| 
 | ||||
|   @override | ||||
|   String toString() => 'QueueStatusDto[isActive=$isActive, isPaused=$isPaused]'; | ||||
| 
 | ||||
|   Map<String, dynamic> toJson() { | ||||
|     final json = <String, dynamic>{}; | ||||
|       json[r'isActive'] = this.isActive; | ||||
|       json[r'isPaused'] = this.isPaused; | ||||
|     return json; | ||||
|   } | ||||
| 
 | ||||
|   /// Returns a new [QueueStatusDto] instance and imports its values from | ||||
|   /// [value] if it's a [Map], null otherwise. | ||||
|   // ignore: prefer_constructors_over_static_methods | ||||
|   static QueueStatusDto? fromJson(dynamic value) { | ||||
|     if (value is Map) { | ||||
|       final json = value.cast<String, dynamic>(); | ||||
| 
 | ||||
|       // Ensure that the map contains the required keys. | ||||
|       // Note 1: the values aren't checked for validity beyond being non-null. | ||||
|       // Note 2: this code is stripped in release mode! | ||||
|       assert(() { | ||||
|         requiredKeys.forEach((key) { | ||||
|           assert(json.containsKey(key), 'Required key "QueueStatusDto[$key]" is missing from JSON.'); | ||||
|           assert(json[key] != null, 'Required key "QueueStatusDto[$key]" has a null value in JSON.'); | ||||
|         }); | ||||
|         return true; | ||||
|       }()); | ||||
| 
 | ||||
|       return QueueStatusDto( | ||||
|         isActive: mapValueOfType<bool>(json, r'isActive')!, | ||||
|         isPaused: mapValueOfType<bool>(json, r'isPaused')!, | ||||
|       ); | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
| 
 | ||||
|   static List<QueueStatusDto>? listFromJson(dynamic json, {bool growable = false,}) { | ||||
|     final result = <QueueStatusDto>[]; | ||||
|     if (json is List && json.isNotEmpty) { | ||||
|       for (final row in json) { | ||||
|         final value = QueueStatusDto.fromJson(row); | ||||
|         if (value != null) { | ||||
|           result.add(value); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return result.toList(growable: growable); | ||||
|   } | ||||
| 
 | ||||
|   static Map<String, QueueStatusDto> mapFromJson(dynamic json) { | ||||
|     final map = <String, QueueStatusDto>{}; | ||||
|     if (json is Map && json.isNotEmpty) { | ||||
|       json = json.cast<String, dynamic>(); // ignore: parameter_assignments | ||||
|       for (final entry in json.entries) { | ||||
|         final value = QueueStatusDto.fromJson(entry.value); | ||||
|         if (value != null) { | ||||
|           map[entry.key] = value; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return map; | ||||
|   } | ||||
| 
 | ||||
|   // maps a json object with a list of QueueStatusDto-objects as value to a dart map | ||||
|   static Map<String, List<QueueStatusDto>> mapListFromJson(dynamic json, {bool growable = false,}) { | ||||
|     final map = <String, List<QueueStatusDto>>{}; | ||||
|     if (json is Map && json.isNotEmpty) { | ||||
|       json = json.cast<String, dynamic>(); // ignore: parameter_assignments | ||||
|       for (final entry in json.entries) { | ||||
|         final value = QueueStatusDto.listFromJson(entry.value, growable: growable,); | ||||
|         if (value != null) { | ||||
|           map[entry.key] = value; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return map; | ||||
|   } | ||||
| 
 | ||||
|   /// The list of required keys that must be present in a JSON. | ||||
|   static const requiredKeys = <String>{ | ||||
|     'isActive', | ||||
|     'isPaused', | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| @@ -16,42 +16,42 @@ void main() { | ||||
|   // final instance = AllJobStatusResponseDto(); | ||||
| 
 | ||||
|   group('test AllJobStatusResponseDto', () { | ||||
|     // JobCountsDto thumbnailGenerationQueue | ||||
|     // JobStatusDto thumbnailGenerationQueue | ||||
|     test('to test the property `thumbnailGenerationQueue`', () async { | ||||
|       // TODO | ||||
|     }); | ||||
| 
 | ||||
|     // JobCountsDto metadataExtractionQueue | ||||
|     // JobStatusDto metadataExtractionQueue | ||||
|     test('to test the property `metadataExtractionQueue`', () async { | ||||
|       // TODO | ||||
|     }); | ||||
| 
 | ||||
|     // JobCountsDto videoConversionQueue | ||||
|     // JobStatusDto videoConversionQueue | ||||
|     test('to test the property `videoConversionQueue`', () async { | ||||
|       // TODO | ||||
|     }); | ||||
| 
 | ||||
|     // JobCountsDto objectTaggingQueue | ||||
|     // JobStatusDto objectTaggingQueue | ||||
|     test('to test the property `objectTaggingQueue`', () async { | ||||
|       // TODO | ||||
|     }); | ||||
| 
 | ||||
|     // JobCountsDto clipEncodingQueue | ||||
|     // JobStatusDto clipEncodingQueue | ||||
|     test('to test the property `clipEncodingQueue`', () async { | ||||
|       // TODO | ||||
|     }); | ||||
| 
 | ||||
|     // JobCountsDto storageTemplateMigrationQueue | ||||
|     // JobStatusDto storageTemplateMigrationQueue | ||||
|     test('to test the property `storageTemplateMigrationQueue`', () async { | ||||
|       // TODO | ||||
|     }); | ||||
| 
 | ||||
|     // JobCountsDto backgroundTaskQueue | ||||
|     // JobStatusDto backgroundTaskQueue | ||||
|     test('to test the property `backgroundTaskQueue`', () async { | ||||
|       // TODO | ||||
|     }); | ||||
| 
 | ||||
|     // JobCountsDto searchQueue | ||||
|     // JobStatusDto searchQueue | ||||
|     test('to test the property `searchQueue`', () async { | ||||
|       // TODO | ||||
|     }); | ||||
|   | ||||
							
								
								
									
										2
									
								
								mobile/openapi/test/job_api_test.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								mobile/openapi/test/job_api_test.dart
									
									
									
										generated
									
									
									
								
							| @@ -26,7 +26,7 @@ void main() { | ||||
| 
 | ||||
|     //  | ||||
|     // | ||||
|     //Future sendJobCommand(JobName jobId, JobCommandDto jobCommandDto) async | ||||
|     //Future<JobStatusDto> sendJobCommand(JobName jobId, JobCommandDto jobCommandDto) async | ||||
|     test('test sendJobCommand', () async { | ||||
|       // TODO | ||||
|     }); | ||||
|   | ||||
							
								
								
									
										32
									
								
								mobile/openapi/test/job_status_dto_test.dart
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								mobile/openapi/test/job_status_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 JobStatusDto | ||||
| void main() { | ||||
|   // final instance = JobStatusDto(); | ||||
| 
 | ||||
|   group('test JobStatusDto', () { | ||||
|     // JobCountsDto jobCounts | ||||
|     test('to test the property `jobCounts`', () async { | ||||
|       // TODO | ||||
|     }); | ||||
| 
 | ||||
|     // QueueStatusDto queueStatus | ||||
|     test('to test the property `queueStatus`', () async { | ||||
|       // TODO | ||||
|     }); | ||||
| 
 | ||||
| 
 | ||||
|   }); | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										32
									
								
								mobile/openapi/test/queue_status_dto_test.dart
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								mobile/openapi/test/queue_status_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 QueueStatusDto | ||||
| void main() { | ||||
|   // final instance = QueueStatusDto(); | ||||
| 
 | ||||
|   group('test QueueStatusDto', () { | ||||
|     // bool isActive | ||||
|     test('to test the property `isActive`', () async { | ||||
|       // TODO | ||||
|     }); | ||||
| 
 | ||||
|     // bool isPaused | ||||
|     test('to test the property `isPaused`', () async { | ||||
|       // TODO | ||||
|     }); | ||||
| 
 | ||||
| 
 | ||||
|   }); | ||||
| 
 | ||||
| } | ||||
| @@ -1,4 +1,4 @@ | ||||
| import { AllJobStatusResponseDto, JobCommandDto, JobIdDto, JobService } from '@app/domain'; | ||||
| import { AllJobStatusResponseDto, JobCommandDto, JobStatusDto, JobIdDto, JobService } from '@app/domain'; | ||||
| import { Body, Controller, Get, Param, Put, UsePipes, ValidationPipe } from '@nestjs/common'; | ||||
| import { ApiTags } from '@nestjs/swagger'; | ||||
| import { Authenticated } from '../decorators/authenticated.decorator'; | ||||
| @@ -16,7 +16,8 @@ export class JobController { | ||||
|   } | ||||
|  | ||||
|   @Put('/:jobId') | ||||
|   sendJobCommand(@Param() { jobId }: JobIdDto, @Body() dto: JobCommandDto): Promise<void> { | ||||
|     return this.service.handleCommand(jobId, dto); | ||||
|   async sendJobCommand(@Param() { jobId }: JobIdDto, @Body() dto: JobCommandDto): Promise<JobStatusDto> { | ||||
|     await this.service.handleCommand(jobId, dto); | ||||
|     return await this.service.getJobStatus(jobId); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -541,7 +541,14 @@ | ||||
|         }, | ||||
|         "responses": { | ||||
|           "200": { | ||||
|             "description": "" | ||||
|             "description": "", | ||||
|             "content": { | ||||
|               "application/json": { | ||||
|                 "schema": { | ||||
|                   "$ref": "#/components/schemas/JobStatusDto" | ||||
|                 } | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|         }, | ||||
|         "tags": [ | ||||
| @@ -4088,32 +4095,62 @@ | ||||
|           "paused" | ||||
|         ] | ||||
|       }, | ||||
|       "QueueStatusDto": { | ||||
|         "type": "object", | ||||
|         "properties": { | ||||
|           "isActive": { | ||||
|             "type": "boolean" | ||||
|           }, | ||||
|           "isPaused": { | ||||
|             "type": "boolean" | ||||
|           } | ||||
|         }, | ||||
|         "required": [ | ||||
|           "isActive", | ||||
|           "isPaused" | ||||
|         ] | ||||
|       }, | ||||
|       "JobStatusDto": { | ||||
|         "type": "object", | ||||
|         "properties": { | ||||
|           "jobCounts": { | ||||
|             "$ref": "#/components/schemas/JobCountsDto" | ||||
|           }, | ||||
|           "queueStatus": { | ||||
|             "$ref": "#/components/schemas/QueueStatusDto" | ||||
|           } | ||||
|         }, | ||||
|         "required": [ | ||||
|           "jobCounts", | ||||
|           "queueStatus" | ||||
|         ] | ||||
|       }, | ||||
|       "AllJobStatusResponseDto": { | ||||
|         "type": "object", | ||||
|         "properties": { | ||||
|           "thumbnail-generation-queue": { | ||||
|             "$ref": "#/components/schemas/JobCountsDto" | ||||
|             "$ref": "#/components/schemas/JobStatusDto" | ||||
|           }, | ||||
|           "metadata-extraction-queue": { | ||||
|             "$ref": "#/components/schemas/JobCountsDto" | ||||
|             "$ref": "#/components/schemas/JobStatusDto" | ||||
|           }, | ||||
|           "video-conversion-queue": { | ||||
|             "$ref": "#/components/schemas/JobCountsDto" | ||||
|             "$ref": "#/components/schemas/JobStatusDto" | ||||
|           }, | ||||
|           "object-tagging-queue": { | ||||
|             "$ref": "#/components/schemas/JobCountsDto" | ||||
|             "$ref": "#/components/schemas/JobStatusDto" | ||||
|           }, | ||||
|           "clip-encoding-queue": { | ||||
|             "$ref": "#/components/schemas/JobCountsDto" | ||||
|             "$ref": "#/components/schemas/JobStatusDto" | ||||
|           }, | ||||
|           "storage-template-migration-queue": { | ||||
|             "$ref": "#/components/schemas/JobCountsDto" | ||||
|             "$ref": "#/components/schemas/JobStatusDto" | ||||
|           }, | ||||
|           "background-task-queue": { | ||||
|             "$ref": "#/components/schemas/JobCountsDto" | ||||
|             "$ref": "#/components/schemas/JobStatusDto" | ||||
|           }, | ||||
|           "search-queue": { | ||||
|             "$ref": "#/components/schemas/JobCountsDto" | ||||
|             "$ref": "#/components/schemas/JobStatusDto" | ||||
|           } | ||||
|         }, | ||||
|         "required": [ | ||||
|   | ||||
| @@ -18,6 +18,11 @@ export interface JobCounts { | ||||
|   paused: number; | ||||
| } | ||||
|  | ||||
| export interface QueueStatus { | ||||
|   isActive: boolean; | ||||
|   isPaused: boolean; | ||||
| } | ||||
|  | ||||
| export type JobItem = | ||||
|   // Asset Upload | ||||
|   | { name: JobName.ASSET_UPLOADED; data: IAssetUploadedJob } | ||||
| @@ -73,6 +78,6 @@ export interface IJobRepository { | ||||
|   pause(name: QueueName): Promise<void>; | ||||
|   resume(name: QueueName): Promise<void>; | ||||
|   empty(name: QueueName): Promise<void>; | ||||
|   isActive(name: QueueName): Promise<boolean>; | ||||
|   getQueueStatus(name: QueueName): Promise<QueueStatus>; | ||||
|   getJobCounts(name: QueueName): Promise<JobCounts>; | ||||
| } | ||||
|   | ||||
| @@ -25,72 +25,35 @@ describe(JobService.name, () => { | ||||
|         waiting: 1, | ||||
|         paused: 1, | ||||
|       }); | ||||
|       jobMock.getQueueStatus.mockResolvedValue({ | ||||
|         isActive: true, | ||||
|         isPaused: true, | ||||
|       }); | ||||
|  | ||||
|       const expectedJobStatus = { | ||||
|         jobCounts: { | ||||
|           active: 1, | ||||
|           completed: 1, | ||||
|           delayed: 1, | ||||
|           failed: 1, | ||||
|           waiting: 1, | ||||
|           paused: 1, | ||||
|         }, | ||||
|         queueStatus: { | ||||
|           isActive: true, | ||||
|           isPaused: true, | ||||
|         }, | ||||
|       }; | ||||
|  | ||||
|       await expect(sut.getAllJobsStatus()).resolves.toEqual({ | ||||
|         'background-task-queue': { | ||||
|           active: 1, | ||||
|           completed: 1, | ||||
|           delayed: 1, | ||||
|           failed: 1, | ||||
|           waiting: 1, | ||||
|           paused: 1, | ||||
|         }, | ||||
|         'clip-encoding-queue': { | ||||
|           active: 1, | ||||
|           completed: 1, | ||||
|           delayed: 1, | ||||
|           failed: 1, | ||||
|           waiting: 1, | ||||
|           paused: 1, | ||||
|         }, | ||||
|         'metadata-extraction-queue': { | ||||
|           active: 1, | ||||
|           completed: 1, | ||||
|           delayed: 1, | ||||
|           failed: 1, | ||||
|           waiting: 1, | ||||
|           paused: 1, | ||||
|         }, | ||||
|         'object-tagging-queue': { | ||||
|           active: 1, | ||||
|           completed: 1, | ||||
|           delayed: 1, | ||||
|           failed: 1, | ||||
|           waiting: 1, | ||||
|           paused: 1, | ||||
|         }, | ||||
|         'search-queue': { | ||||
|           active: 1, | ||||
|           completed: 1, | ||||
|           delayed: 1, | ||||
|           failed: 1, | ||||
|           waiting: 1, | ||||
|           paused: 1, | ||||
|         }, | ||||
|         'storage-template-migration-queue': { | ||||
|           active: 1, | ||||
|           completed: 1, | ||||
|           delayed: 1, | ||||
|           failed: 1, | ||||
|           waiting: 1, | ||||
|           paused: 1, | ||||
|         }, | ||||
|         'thumbnail-generation-queue': { | ||||
|           active: 1, | ||||
|           completed: 1, | ||||
|           delayed: 1, | ||||
|           failed: 1, | ||||
|           waiting: 1, | ||||
|           paused: 1, | ||||
|         }, | ||||
|         'video-conversion-queue': { | ||||
|           active: 1, | ||||
|           completed: 1, | ||||
|           delayed: 1, | ||||
|           failed: 1, | ||||
|           waiting: 1, | ||||
|           paused: 1, | ||||
|         }, | ||||
|         'background-task-queue': expectedJobStatus, | ||||
|         'clip-encoding-queue': expectedJobStatus, | ||||
|         'metadata-extraction-queue': expectedJobStatus, | ||||
|         'object-tagging-queue': expectedJobStatus, | ||||
|         'search-queue': expectedJobStatus, | ||||
|         'storage-template-migration-queue': expectedJobStatus, | ||||
|         'thumbnail-generation-queue': expectedJobStatus, | ||||
|         'video-conversion-queue': expectedJobStatus, | ||||
|       }); | ||||
|     }); | ||||
|   }); | ||||
| @@ -115,7 +78,7 @@ describe(JobService.name, () => { | ||||
|     }); | ||||
|  | ||||
|     it('should not start a job that is already running', async () => { | ||||
|       jobMock.isActive.mockResolvedValue(true); | ||||
|       jobMock.getQueueStatus.mockResolvedValue({ isActive: true, isPaused: false }); | ||||
|  | ||||
|       await expect( | ||||
|         sut.handleCommand(QueueName.VIDEO_CONVERSION, { command: JobCommand.START, force: false }), | ||||
| @@ -125,7 +88,7 @@ describe(JobService.name, () => { | ||||
|     }); | ||||
|  | ||||
|     it('should handle a start video conversion command', async () => { | ||||
|       jobMock.isActive.mockResolvedValue(false); | ||||
|       jobMock.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false }); | ||||
|  | ||||
|       await sut.handleCommand(QueueName.VIDEO_CONVERSION, { command: JobCommand.START, force: false }); | ||||
|  | ||||
| @@ -133,7 +96,7 @@ describe(JobService.name, () => { | ||||
|     }); | ||||
|  | ||||
|     it('should handle a start storage template migration command', async () => { | ||||
|       jobMock.isActive.mockResolvedValue(false); | ||||
|       jobMock.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false }); | ||||
|  | ||||
|       await sut.handleCommand(QueueName.STORAGE_TEMPLATE_MIGRATION, { command: JobCommand.START, force: false }); | ||||
|  | ||||
| @@ -141,7 +104,7 @@ describe(JobService.name, () => { | ||||
|     }); | ||||
|  | ||||
|     it('should handle a start object tagging command', async () => { | ||||
|       jobMock.isActive.mockResolvedValue(false); | ||||
|       jobMock.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false }); | ||||
|  | ||||
|       await sut.handleCommand(QueueName.OBJECT_TAGGING, { command: JobCommand.START, force: false }); | ||||
|  | ||||
| @@ -149,7 +112,7 @@ describe(JobService.name, () => { | ||||
|     }); | ||||
|  | ||||
|     it('should handle a start clip encoding command', async () => { | ||||
|       jobMock.isActive.mockResolvedValue(false); | ||||
|       jobMock.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false }); | ||||
|  | ||||
|       await sut.handleCommand(QueueName.CLIP_ENCODING, { command: JobCommand.START, force: false }); | ||||
|  | ||||
| @@ -157,7 +120,7 @@ describe(JobService.name, () => { | ||||
|     }); | ||||
|  | ||||
|     it('should handle a start metadata extraction command', async () => { | ||||
|       jobMock.isActive.mockResolvedValue(false); | ||||
|       jobMock.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false }); | ||||
|  | ||||
|       await sut.handleCommand(QueueName.METADATA_EXTRACTION, { command: JobCommand.START, force: false }); | ||||
|  | ||||
| @@ -165,7 +128,7 @@ describe(JobService.name, () => { | ||||
|     }); | ||||
|  | ||||
|     it('should handle a start thumbnail generation command', async () => { | ||||
|       jobMock.isActive.mockResolvedValue(false); | ||||
|       jobMock.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false }); | ||||
|  | ||||
|       await sut.handleCommand(QueueName.THUMBNAIL_GENERATION, { command: JobCommand.START, force: false }); | ||||
|  | ||||
| @@ -173,7 +136,7 @@ describe(JobService.name, () => { | ||||
|     }); | ||||
|  | ||||
|     it('should throw a bad request when an invalid queue is used', async () => { | ||||
|       jobMock.isActive.mockResolvedValue(false); | ||||
|       jobMock.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false }); | ||||
|  | ||||
|       await expect( | ||||
|         sut.handleCommand(QueueName.BACKGROUND_TASK, { command: JobCommand.START, force: false }), | ||||
|   | ||||
| @@ -3,7 +3,7 @@ import { assertMachineLearningEnabled } from '../domain.constant'; | ||||
| import { JobCommandDto } from './dto'; | ||||
| import { JobCommand, JobName, QueueName } from './job.constants'; | ||||
| import { IJobRepository } from './job.repository'; | ||||
| import { AllJobStatusResponseDto } from './response-dto'; | ||||
| import { AllJobStatusResponseDto, JobStatusDto } from './response-dto'; | ||||
|  | ||||
| @Injectable() | ||||
| export class JobService { | ||||
| @@ -29,16 +29,25 @@ export class JobService { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   async getJobStatus(queueName: QueueName): Promise<JobStatusDto> { | ||||
|     const [jobCounts, queueStatus] = await Promise.all([ | ||||
|       this.jobRepository.getJobCounts(queueName), | ||||
|       this.jobRepository.getQueueStatus(queueName), | ||||
|     ]); | ||||
|  | ||||
|     return { jobCounts, queueStatus }; | ||||
|   } | ||||
|  | ||||
|   async getAllJobsStatus(): Promise<AllJobStatusResponseDto> { | ||||
|     const response = new AllJobStatusResponseDto(); | ||||
|     for (const queueName of Object.values(QueueName)) { | ||||
|       response[queueName] = await this.jobRepository.getJobCounts(queueName); | ||||
|       response[queueName] = await this.getJobStatus(queueName); | ||||
|     } | ||||
|     return response; | ||||
|   } | ||||
|  | ||||
|   private async start(name: QueueName, { force }: JobCommandDto): Promise<void> { | ||||
|     const isActive = await this.jobRepository.isActive(name); | ||||
|     const { isActive } = await this.jobRepository.getQueueStatus(name); | ||||
|     if (isActive) { | ||||
|       throw new BadRequestException(`Job is already running`); | ||||
|     } | ||||
|   | ||||
| @@ -16,28 +16,41 @@ export class JobCountsDto { | ||||
|   paused!: number; | ||||
| } | ||||
|  | ||||
| export class AllJobStatusResponseDto implements Record<QueueName, JobCountsDto> { | ||||
|   @ApiProperty({ type: JobCountsDto }) | ||||
|   [QueueName.THUMBNAIL_GENERATION]!: JobCountsDto; | ||||
|  | ||||
|   @ApiProperty({ type: JobCountsDto }) | ||||
|   [QueueName.METADATA_EXTRACTION]!: JobCountsDto; | ||||
|  | ||||
|   @ApiProperty({ type: JobCountsDto }) | ||||
|   [QueueName.VIDEO_CONVERSION]!: JobCountsDto; | ||||
|  | ||||
|   @ApiProperty({ type: JobCountsDto }) | ||||
|   [QueueName.OBJECT_TAGGING]!: JobCountsDto; | ||||
|  | ||||
|   @ApiProperty({ type: JobCountsDto }) | ||||
|   [QueueName.CLIP_ENCODING]!: JobCountsDto; | ||||
|  | ||||
|   @ApiProperty({ type: JobCountsDto }) | ||||
|   [QueueName.STORAGE_TEMPLATE_MIGRATION]!: JobCountsDto; | ||||
|  | ||||
|   @ApiProperty({ type: JobCountsDto }) | ||||
|   [QueueName.BACKGROUND_TASK]!: JobCountsDto; | ||||
|  | ||||
|   @ApiProperty({ type: JobCountsDto }) | ||||
|   [QueueName.SEARCH]!: JobCountsDto; | ||||
| export class QueueStatusDto { | ||||
|   isActive!: boolean; | ||||
|   isPaused!: boolean; | ||||
| } | ||||
|  | ||||
| export class JobStatusDto { | ||||
|   @ApiProperty({ type: JobCountsDto }) | ||||
|   jobCounts!: JobCountsDto; | ||||
|  | ||||
|   @ApiProperty({ type: QueueStatusDto }) | ||||
|   queueStatus!: QueueStatusDto; | ||||
| } | ||||
|  | ||||
| export class AllJobStatusResponseDto implements Record<QueueName, JobStatusDto> { | ||||
|   @ApiProperty({ type: JobStatusDto }) | ||||
|   [QueueName.THUMBNAIL_GENERATION]!: JobStatusDto; | ||||
|  | ||||
|   @ApiProperty({ type: JobStatusDto }) | ||||
|   [QueueName.METADATA_EXTRACTION]!: JobStatusDto; | ||||
|  | ||||
|   @ApiProperty({ type: JobStatusDto }) | ||||
|   [QueueName.VIDEO_CONVERSION]!: JobStatusDto; | ||||
|  | ||||
|   @ApiProperty({ type: JobStatusDto }) | ||||
|   [QueueName.OBJECT_TAGGING]!: JobStatusDto; | ||||
|  | ||||
|   @ApiProperty({ type: JobStatusDto }) | ||||
|   [QueueName.CLIP_ENCODING]!: JobStatusDto; | ||||
|  | ||||
|   @ApiProperty({ type: JobStatusDto }) | ||||
|   [QueueName.STORAGE_TEMPLATE_MIGRATION]!: JobStatusDto; | ||||
|  | ||||
|   @ApiProperty({ type: JobStatusDto }) | ||||
|   [QueueName.BACKGROUND_TASK]!: JobStatusDto; | ||||
|  | ||||
|   @ApiProperty({ type: JobStatusDto }) | ||||
|   [QueueName.SEARCH]!: JobStatusDto; | ||||
| } | ||||
|   | ||||
| @@ -6,7 +6,7 @@ export const newJobRepositoryMock = (): jest.Mocked<IJobRepository> => { | ||||
|     pause: jest.fn(), | ||||
|     resume: jest.fn(), | ||||
|     queue: jest.fn().mockImplementation(() => Promise.resolve()), | ||||
|     isActive: jest.fn(), | ||||
|     getQueueStatus: jest.fn(), | ||||
|     getJobCounts: jest.fn(), | ||||
|   }; | ||||
| }; | ||||
|   | ||||
| @@ -7,6 +7,7 @@ import { | ||||
|   JobItem, | ||||
|   JobName, | ||||
|   QueueName, | ||||
|   QueueStatus, | ||||
| } from '@app/domain'; | ||||
| import { InjectQueue } from '@nestjs/bull'; | ||||
| import { Logger } from '@nestjs/common'; | ||||
| @@ -36,9 +37,13 @@ export class JobRepository implements IJobRepository { | ||||
|     @InjectQueue(QueueName.SEARCH) private searchIndex: Queue, | ||||
|   ) {} | ||||
|  | ||||
|   async isActive(name: QueueName): Promise<boolean> { | ||||
|     const counts = await this.getJobCounts(name); | ||||
|     return !!counts.active; | ||||
|   async getQueueStatus(name: QueueName): Promise<QueueStatus> { | ||||
|     const queue = this.queueMap[name]; | ||||
|  | ||||
|     return { | ||||
|       isActive: !!(await queue.getActiveCount()), | ||||
|       isPaused: await queue.isPaused(), | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   pause(name: QueueName) { | ||||
|   | ||||
							
								
								
									
										74
									
								
								web/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										74
									
								
								web/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							| @@ -291,52 +291,52 @@ export interface AlbumResponseDto { | ||||
| export interface AllJobStatusResponseDto { | ||||
|     /** | ||||
|      *  | ||||
|      * @type {JobCountsDto} | ||||
|      * @type {JobStatusDto} | ||||
|      * @memberof AllJobStatusResponseDto | ||||
|      */ | ||||
|     'thumbnail-generation-queue': JobCountsDto; | ||||
|     'thumbnail-generation-queue': JobStatusDto; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {JobCountsDto} | ||||
|      * @type {JobStatusDto} | ||||
|      * @memberof AllJobStatusResponseDto | ||||
|      */ | ||||
|     'metadata-extraction-queue': JobCountsDto; | ||||
|     'metadata-extraction-queue': JobStatusDto; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {JobCountsDto} | ||||
|      * @type {JobStatusDto} | ||||
|      * @memberof AllJobStatusResponseDto | ||||
|      */ | ||||
|     'video-conversion-queue': JobCountsDto; | ||||
|     'video-conversion-queue': JobStatusDto; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {JobCountsDto} | ||||
|      * @type {JobStatusDto} | ||||
|      * @memberof AllJobStatusResponseDto | ||||
|      */ | ||||
|     'object-tagging-queue': JobCountsDto; | ||||
|     'object-tagging-queue': JobStatusDto; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {JobCountsDto} | ||||
|      * @type {JobStatusDto} | ||||
|      * @memberof AllJobStatusResponseDto | ||||
|      */ | ||||
|     'clip-encoding-queue': JobCountsDto; | ||||
|     'clip-encoding-queue': JobStatusDto; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {JobCountsDto} | ||||
|      * @type {JobStatusDto} | ||||
|      * @memberof AllJobStatusResponseDto | ||||
|      */ | ||||
|     'storage-template-migration-queue': JobCountsDto; | ||||
|     'storage-template-migration-queue': JobStatusDto; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {JobCountsDto} | ||||
|      * @type {JobStatusDto} | ||||
|      * @memberof AllJobStatusResponseDto | ||||
|      */ | ||||
|     'background-task-queue': JobCountsDto; | ||||
|     'background-task-queue': JobStatusDto; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {JobCountsDto} | ||||
|      * @type {JobStatusDto} | ||||
|      * @memberof AllJobStatusResponseDto | ||||
|      */ | ||||
|     'search-queue': JobCountsDto; | ||||
|     'search-queue': JobStatusDto; | ||||
| } | ||||
| /** | ||||
|  *  | ||||
| @@ -1311,6 +1311,25 @@ export const JobName = { | ||||
| export type JobName = typeof JobName[keyof typeof JobName]; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
|  * @interface JobStatusDto | ||||
|  */ | ||||
| export interface JobStatusDto { | ||||
|     /** | ||||
|      *  | ||||
|      * @type {JobCountsDto} | ||||
|      * @memberof JobStatusDto | ||||
|      */ | ||||
|     'jobCounts': JobCountsDto; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {QueueStatusDto} | ||||
|      * @memberof JobStatusDto | ||||
|      */ | ||||
|     'queueStatus': QueueStatusDto; | ||||
| } | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
| @@ -1467,6 +1486,25 @@ export interface OAuthConfigResponseDto { | ||||
|      */ | ||||
|     'autoLaunch'?: boolean; | ||||
| } | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
|  * @interface QueueStatusDto | ||||
|  */ | ||||
| export interface QueueStatusDto { | ||||
|     /** | ||||
|      *  | ||||
|      * @type {boolean} | ||||
|      * @memberof QueueStatusDto | ||||
|      */ | ||||
|     'isActive': boolean; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {boolean} | ||||
|      * @memberof QueueStatusDto | ||||
|      */ | ||||
|     'isPaused': boolean; | ||||
| } | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
| @@ -6270,7 +6308,7 @@ export const JobApiFp = function(configuration?: Configuration) { | ||||
|          * @param {*} [options] Override http request option. | ||||
|          * @throws {RequiredError} | ||||
|          */ | ||||
|         async sendJobCommand(jobId: JobName, jobCommandDto: JobCommandDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> { | ||||
|         async sendJobCommand(jobId: JobName, jobCommandDto: JobCommandDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<JobStatusDto>> { | ||||
|             const localVarAxiosArgs = await localVarAxiosParamCreator.sendJobCommand(jobId, jobCommandDto, options); | ||||
|             return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); | ||||
|         }, | ||||
| @@ -6299,7 +6337,7 @@ export const JobApiFactory = function (configuration?: Configuration, basePath?: | ||||
|          * @param {*} [options] Override http request option. | ||||
|          * @throws {RequiredError} | ||||
|          */ | ||||
|         sendJobCommand(jobId: JobName, jobCommandDto: JobCommandDto, options?: any): AxiosPromise<void> { | ||||
|         sendJobCommand(jobId: JobName, jobCommandDto: JobCommandDto, options?: any): AxiosPromise<JobStatusDto> { | ||||
|             return localVarFp.sendJobCommand(jobId, jobCommandDto, options).then((request) => request(axios, basePath)); | ||||
|         }, | ||||
|     }; | ||||
|   | ||||
| @@ -109,8 +109,4 @@ input:focus-visible { | ||||
| 		display: none; | ||||
| 		scrollbar-width: none; | ||||
| 	} | ||||
|  | ||||
| 	.job-play-button { | ||||
| 		@apply h-full flex flex-col place-items-center place-content-center px-8 text-gray-600 transition-all hover:bg-immich-primary hover:text-white dark:text-gray-200 dark:hover:bg-immich-dark-primary text-sm dark:hover:text-black gap-2; | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,21 @@ | ||||
| <script lang="ts" context="module"> | ||||
| 	export type Colors = 'light-gray' | 'gray'; | ||||
| </script> | ||||
|  | ||||
| <script lang="ts"> | ||||
| 	export let color: Colors; | ||||
|  | ||||
| 	const colorClasses: Record<Colors, string> = { | ||||
| 		'light-gray': 'bg-gray-300/90 dark:bg-gray-600/90', | ||||
| 		gray: 'bg-gray-300 dark:bg-gray-600' | ||||
| 	}; | ||||
| </script> | ||||
|  | ||||
| <button | ||||
| 	class="h-full flex gap-2 flex-col place-items-center place-content-center px-8 text-gray-600 transition-colors hover:bg-immich-primary hover:text-white dark:text-gray-200 dark:hover:bg-immich-dark-primary text-xs dark:hover:text-black {colorClasses[ | ||||
| 		color | ||||
| 	]}" | ||||
| 	on:click | ||||
| > | ||||
| 	<slot /> | ||||
| </button> | ||||
| @@ -0,0 +1,16 @@ | ||||
| <script lang="ts" context="module"> | ||||
| 	export type Color = 'success' | 'warning'; | ||||
| </script> | ||||
|  | ||||
| <script lang="ts"> | ||||
| 	export let color: Color; | ||||
|  | ||||
| 	const colorClasses: Record<Color, string> = { | ||||
| 		success: 'bg-green-500/70 text-gray-900 dark:bg-green-700/90 dark:text-gray-100', | ||||
| 		warning: 'bg-orange-400/70 text-gray-900 dark:bg-orange-900 dark:text-gray-100' | ||||
| 	}; | ||||
| </script> | ||||
|  | ||||
| <div class="w-full text-center text-sm p-2 {colorClasses[color]}"> | ||||
| 	<slot /> | ||||
| </div> | ||||
| @@ -4,40 +4,49 @@ | ||||
| 	import Pause from 'svelte-material-icons/Pause.svelte'; | ||||
| 	import FastForward from 'svelte-material-icons/FastForward.svelte'; | ||||
| 	import AllInclusive from 'svelte-material-icons/AllInclusive.svelte'; | ||||
| 	import Close from 'svelte-material-icons/Close.svelte'; | ||||
| 	import { locale } from '$lib/stores/preferences.store'; | ||||
| 	import { createEventDispatcher } from 'svelte'; | ||||
| 	import { JobCommand, JobCommandDto, JobCountsDto } from '@api'; | ||||
| 	import { JobCommand, JobCommandDto, JobCountsDto, QueueStatusDto } from '@api'; | ||||
| 	import Badge from '$lib/components/elements/badge.svelte'; | ||||
| 	import JobTileButton from './job-tile-button.svelte'; | ||||
| 	import JobTileStatus from './job-tile-status.svelte'; | ||||
|  | ||||
| 	export let title: string; | ||||
| 	export let subtitle: string | undefined = undefined; | ||||
| 	export let jobCounts: JobCountsDto; | ||||
| 	export let queueStatus: QueueStatusDto; | ||||
| 	export let allowForceCommand = true; | ||||
|  | ||||
| 	$: isRunning = jobCounts.active > 0 || jobCounts.waiting > 0; | ||||
| 	$: waitingCount = jobCounts.waiting + jobCounts.paused; | ||||
| 	$: isPause = jobCounts.paused > 0; | ||||
| 	$: waitingCount = jobCounts.waiting + jobCounts.paused + jobCounts.delayed; | ||||
| 	$: isIdle = !queueStatus.isActive && !queueStatus.isPaused; | ||||
|  | ||||
| 	const dispatch = createEventDispatcher<{ command: JobCommandDto }>(); | ||||
| </script> | ||||
|  | ||||
| <div | ||||
| 	class="flex justify-between rounded-3xl bg-gray-100 dark:bg-immich-dark-gray transition-all | ||||
|   {isRunning ? 'dark:bg-immich-primary/30 bg-immich-primary/20' : ''}  | ||||
|   {isPause ? 'dark:bg-yellow-100/30 bg-yellow-500/20' : ''}" | ||||
| > | ||||
| 	<div id="job-info" class="w-full p-9"> | ||||
| 		<div class="flex flex-col gap-2 "> | ||||
| <div class="flex bg-gray-100 dark:bg-immich-dark-gray rounded-3xl overflow-hidden"> | ||||
| 	<div class="flex flex-col w-full"> | ||||
| 		{#if queueStatus.isPaused} | ||||
| 			<JobTileStatus color="warning">Paused</JobTileStatus> | ||||
| 		{:else if queueStatus.isActive} | ||||
| 			<JobTileStatus color="success">Active</JobTileStatus> | ||||
| 		{/if} | ||||
| 		<div class="flex flex-col gap-2 p-9"> | ||||
| 			<div | ||||
| 				class="flex items-center gap-4 text-xl font-semibold text-immich-primary dark:text-immich-dark-primary" | ||||
| 			> | ||||
| 				<span>{title.toUpperCase()}</span> | ||||
| 				<div class="flex gap-2"> | ||||
| 					{#if jobCounts.failed > 0} | ||||
| 						<Badge color="danger"> | ||||
| 						<Badge color="primary"> | ||||
| 							{jobCounts.failed.toLocaleString($locale)} failed | ||||
| 						</Badge> | ||||
| 					{/if} | ||||
| 					{#if jobCounts.delayed > 0} | ||||
| 						<Badge color="secondary"> | ||||
| 							{jobCounts.delayed.toLocaleString($locale)} delayed | ||||
| 						</Badge> | ||||
| 					{/if} | ||||
| 				</div> | ||||
| 			</div> | ||||
|  | ||||
| @@ -69,43 +78,54 @@ | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</div> | ||||
| 	<div id="job-action" class="flex flex-col rounded-r-3xl w-32 overflow-hidden"> | ||||
| 		{#if isRunning} | ||||
| 			<button | ||||
| 				class="job-play-button bg-gray-300/90 dark:bg-gray-600/90" | ||||
| 				on:click={() => dispatch('command', { command: JobCommand.Pause, force: false })} | ||||
| 			> | ||||
| 				<Pause size="48" /> PAUSE | ||||
| 			</button> | ||||
| 		{:else if jobCounts.paused > 0} | ||||
| 			<button | ||||
| 				class="job-play-button bg-gray-300 dark:bg-gray-600/90" | ||||
| 				on:click={() => dispatch('command', { command: JobCommand.Resume, force: false })} | ||||
| 			> | ||||
| 				<span class=" {isPause ? 'animate-pulse' : ''}"> | ||||
| 					<FastForward size="48" /> RESUME | ||||
| 				</span> | ||||
| 			</button> | ||||
| 	<div class="flex flex-col w-32 overflow-hidden"> | ||||
| 		{#if !isIdle} | ||||
| 			{#if waitingCount > 0} | ||||
| 				<JobTileButton | ||||
| 					color="gray" | ||||
| 					on:click={() => dispatch('command', { command: JobCommand.Empty, force: false })} | ||||
| 				> | ||||
| 					<Close size="24" /> CLEAR | ||||
| 				</JobTileButton> | ||||
| 			{/if} | ||||
| 			{#if queueStatus.isPaused} | ||||
| 				<JobTileButton | ||||
| 					color="light-gray" | ||||
| 					on:click={() => dispatch('command', { command: JobCommand.Resume, force: false })} | ||||
| 				> | ||||
| 					{@const size = waitingCount > 0 ? '24' : '48'} | ||||
|  | ||||
| 					<!-- size property is not reactive, so have to use width and height --> | ||||
| 					<FastForward width={size} height={size} /> RESUME | ||||
| 				</JobTileButton> | ||||
| 			{:else} | ||||
| 				<JobTileButton | ||||
| 					color="light-gray" | ||||
| 					on:click={() => dispatch('command', { command: JobCommand.Pause, force: false })} | ||||
| 				> | ||||
| 					<Pause size="24" /> PAUSE | ||||
| 				</JobTileButton> | ||||
| 			{/if} | ||||
| 		{:else if allowForceCommand} | ||||
| 			<button | ||||
| 				class="job-play-button bg-gray-300 dark:bg-gray-600" | ||||
| 			<JobTileButton | ||||
| 				color="gray" | ||||
| 				on:click={() => dispatch('command', { command: JobCommand.Start, force: true })} | ||||
| 			> | ||||
| 				<AllInclusive size="18" /> ALL | ||||
| 			</button> | ||||
| 			<button | ||||
| 				class="job-play-button bg-gray-300/90 dark:bg-gray-600/90" | ||||
| 				<AllInclusive size="24" /> ALL | ||||
| 			</JobTileButton> | ||||
| 			<JobTileButton | ||||
| 				color="light-gray" | ||||
| 				on:click={() => dispatch('command', { command: JobCommand.Start, force: false })} | ||||
| 			> | ||||
| 				<SelectionSearch size="18" /> MISSING | ||||
| 			</button> | ||||
| 				<SelectionSearch size="24" /> MISSING | ||||
| 			</JobTileButton> | ||||
| 		{:else} | ||||
| 			<button | ||||
| 				class="job-play-button bg-gray-300/90 dark:bg-gray-600/90" | ||||
| 			<JobTileButton | ||||
| 				color="light-gray" | ||||
| 				on:click={() => dispatch('command', { command: JobCommand.Start, force: false })} | ||||
| 			> | ||||
| 				<Play size="48" /> START | ||||
| 			</button> | ||||
| 			</JobTileButton> | ||||
| 		{/if} | ||||
| 	</div> | ||||
| </div> | ||||
|   | ||||
| @@ -1,4 +1,8 @@ | ||||
| <script lang="ts"> | ||||
| 	import { | ||||
| 		notificationController, | ||||
| 		NotificationType | ||||
| 	} from '$lib/components/shared-components/notification/notification'; | ||||
| 	import { handleError } from '$lib/utils/handle-error'; | ||||
| 	import { AllJobStatusResponseDto, api, JobCommand, JobCommandDto, JobName } from '@api'; | ||||
| 	import type { ComponentType } from 'svelte'; | ||||
| @@ -49,21 +53,15 @@ | ||||
| 		const title = jobDetails[jobId]?.title; | ||||
|  | ||||
| 		try { | ||||
| 			await api.jobApi.sendJobCommand(jobId, jobCommand); | ||||
| 			const { data } = await api.jobApi.sendJobCommand(jobId, jobCommand); | ||||
| 			jobs[jobId] = data; | ||||
|  | ||||
| 			// TODO: Return actual job status from server and use that. | ||||
| 			switch (jobCommand.command) { | ||||
| 				case JobCommand.Start: | ||||
| 					jobs[jobId].active += 1; | ||||
| 					break; | ||||
| 				case JobCommand.Resume: | ||||
| 					jobs[jobId].active += 1; | ||||
| 					jobs[jobId].paused = 0; | ||||
| 					break; | ||||
| 				case JobCommand.Pause: | ||||
| 					jobs[jobId].paused += 1; | ||||
| 					jobs[jobId].active = 0; | ||||
| 					jobs[jobId].waiting = 0; | ||||
| 				case JobCommand.Empty: | ||||
| 					notificationController.show({ | ||||
| 						message: `Cleared jobs for: ${title}`, | ||||
| 						type: NotificationType.Info | ||||
| 					}); | ||||
| 					break; | ||||
| 			} | ||||
| 		} catch (error) { | ||||
| @@ -74,12 +72,14 @@ | ||||
|  | ||||
| <div class="flex flex-col gap-7"> | ||||
| 	{#each jobDetailsArray as [jobName, { title, subtitle, allowForceCommand, component }]} | ||||
| 		{@const { jobCounts, queueStatus } = jobs[jobName]} | ||||
| 		<JobTile | ||||
| 			{title} | ||||
| 			{subtitle} | ||||
| 			{allowForceCommand} | ||||
| 			{jobCounts} | ||||
| 			{queueStatus} | ||||
| 			on:command={({ detail }) => runJob(jobName, detail)} | ||||
| 			jobCounts={jobs[jobName]} | ||||
| 		> | ||||
| 			<svelte:component this={component} /> | ||||
| 		</JobTile> | ||||
|   | ||||
| @@ -1,27 +1,25 @@ | ||||
| <script lang="ts" context="module"> | ||||
| 	export type BadgeColor = 'primary' | 'dark' | 'warning' | 'success' | 'danger'; | ||||
| 	export type BadgeRounded = false | true | 'full'; | ||||
| 	export type Color = 'primary' | 'secondary'; | ||||
| 	export type Rounded = false | true | 'full'; | ||||
| </script> | ||||
|  | ||||
| <script lang="ts"> | ||||
| 	export let color: BadgeColor = 'primary'; | ||||
| 	export let rounded: BadgeRounded = true; | ||||
| 	export let color: Color = 'primary'; | ||||
| 	export let rounded: Rounded = true; | ||||
|  | ||||
| 	const colorClasses: { [Key in BadgeColor]: string } = { | ||||
| 	const colorClasses: Record<Color, string> = { | ||||
| 		primary: | ||||
| 			'text-gray-100 dark:text-immich-dark-gray bg-immich-primary dark:bg-immich-dark-primary', | ||||
| 		dark: 'text-neutral-50 dark:text-neutral-50 bg-neutral-900 dark:bg-neutral-900', | ||||
| 		warning: 'text-yellow-900 bg-yellow-200', | ||||
| 		success: 'text-green-900 bg-green-200', | ||||
| 		danger: 'text-red-900 bg-red-200' | ||||
| 		secondary: | ||||
| 			'text-immich-dark-bg dark:text-immich-gray dark:bg-gray-600 bg-gray-300 dark:text-immich-gray' | ||||
| 	}; | ||||
| </script> | ||||
|  | ||||
| <span | ||||
| 	class="inline-block h-min whitespace-nowrap px-[0.65em] pt-[0.35em] pb-[0.25em] text-center align-baseline text-[0.65em] font-bold leading-none {colorClasses[ | ||||
| 	class="inline-block h-min whitespace-nowrap px-4 pt-[0.55em] pb-[0.55em] text-center align-baseline text-xs leading-none {colorClasses[ | ||||
| 		color | ||||
| 	]}" | ||||
| 	class:rounded={rounded === true} | ||||
| 	class:rounded-md={rounded === true} | ||||
| 	class:rounded-full={rounded === 'full'} | ||||
| > | ||||
| 	<slot /> | ||||
|   | ||||
| @@ -5,9 +5,10 @@ | ||||
| 	import type { PageData } from './$types'; | ||||
|  | ||||
| 	export let data: PageData; | ||||
| 	let jobs = data.jobs; | ||||
| 	let timer: NodeJS.Timer; | ||||
|  | ||||
| 	$: jobs = data.jobs; | ||||
|  | ||||
| 	const load = async () => { | ||||
| 		const { data } = await api.jobApi.getAllJobsStatus(); | ||||
| 		jobs = data; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user