diff --git a/ui-ngx/src/app/modules/common/modules-map.ts b/ui-ngx/src/app/modules/common/modules-map.ts index b4f4ea1328..bfc8f60604 100644 --- a/ui-ngx/src/app/modules/common/modules-map.ts +++ b/ui-ngx/src/app/modules/common/modules-map.ts @@ -287,6 +287,8 @@ import * as DisplayWidgetTypesPanelComponent from '@home/components/dashboard-pa import * as AlarmDurationPredicateValueComponent from '@home/components/profile/alarm/alarm-duration-predicate-value.component'; import * as DashboardImageDialogComponent from '@home/components/dashboard-page/dashboard-image-dialog.component'; import * as WidgetContainerComponent from '@home/components/widget/widget-container.component'; +import * as TenantProfileQueuesComponent from '@home/components/profile/queue/tenant-profile-queues.component'; +import { TenantProfileQueueComponent } from '@home/components/profile/queue/tenant-profile-queue.component'; import { IModulesMap } from '@modules/common/modules-map.models'; @@ -570,6 +572,8 @@ class ModulesMap implements IModulesMap { '@home/components/profile/alarm/alarm-duration-predicate-value.component': AlarmDurationPredicateValueComponent, '@home/components/dashboard-page/dashboard-image-dialog.component': DashboardImageDialogComponent, '@home/components/widget/widget-container.component': WidgetContainerComponent, + '@home/components/profile/queue/tenant-profile-queues.component': TenantProfileQueuesComponent, + '@home/components/profile/queue/tenant-profile-queue.component': TenantProfileQueueComponent }; init() { diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index 0c58328e46..282a494026 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -148,6 +148,8 @@ import { } from '@home/components/tokens'; import { DashboardStateComponent } from '@home/components/dashboard-page/dashboard-state.component'; import { EntityDetailsPageComponent } from '@home/components/entity/entity-details-page.component'; +import { TenantProfileQueuesComponent } from '@home/components/profile/queue/tenant-profile-queues.component'; +import { TenantProfileQueueComponent } from '@home/components/profile/queue/tenant-profile-queue.component'; @NgModule({ declarations: @@ -267,7 +269,9 @@ import { EntityDetailsPageComponent } from '@home/components/entity/entity-detai DashboardStateDialogComponent, DashboardImageDialogComponent, EmbedDashboardDialogComponent, - DisplayWidgetTypesPanelComponent + DisplayWidgetTypesPanelComponent, + TenantProfileQueuesComponent, + TenantProfileQueueComponent ], imports: [ CommonModule, @@ -380,7 +384,9 @@ import { EntityDetailsPageComponent } from '@home/components/entity/entity-detai DashboardStateDialogComponent, DashboardImageDialogComponent, EmbedDashboardDialogComponent, - DisplayWidgetTypesPanelComponent + DisplayWidgetTypesPanelComponent, + TenantProfileQueuesComponent, + TenantProfileQueueComponent ], providers: [ WidgetComponentService, diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts index d30f5a8753..0d33079b0b 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts @@ -42,7 +42,7 @@ import { ServiceType } from '@shared/models/queue.models'; import { EntityId } from '@shared/models/id/entity-id'; import { OtaUpdateType } from '@shared/models/ota-package.models'; import { DashboardId } from '@shared/models/id/dashboard-id'; -import { QueueId } from "@shared/models/id/queue-id"; +import { QueueId } from '@shared/models/id/queue-id'; @Component({ selector: 'tb-device-profile', @@ -198,7 +198,7 @@ export class DeviceProfileComponent extends EntityComponent { }}, {emitEvent: false}); this.entityForm.patchValue({defaultRuleChainId: entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null}, {emitEvent: false}); this.entityForm.patchValue({defaultDashboardId: entity.defaultDashboardId ? entity.defaultDashboardId.id : null}, {emitEvent: false}); - this.entityForm.patchValue({defaultQueueId: entity.defaultQueueId ? entity.defaultQueueId.id: null}, {emitEvent: false}); + this.entityForm.patchValue({defaultQueueId: entity.defaultQueueId ? entity.defaultQueueId.id : null}, {emitEvent: false}); this.entityForm.patchValue({firmwareId: entity.firmwareId}, {emitEvent: false}); this.entityForm.patchValue({softwareId: entity.softwareId}, {emitEvent: false}); this.entityForm.patchValue({description: entity.description}, {emitEvent: false}); diff --git a/ui-ngx/src/app/modules/home/components/profile/queue/tenant-profile-queue.component.html b/ui-ngx/src/app/modules/home/components/profile/queue/tenant-profile-queue.component.html new file mode 100644 index 0000000000..07bcb5d9e8 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/queue/tenant-profile-queue.component.html @@ -0,0 +1,194 @@ + + + +
+ +
+ {{ queueTitle }} +
+
+ + +
+
+ +
+ + + admin.queue-name + + + {{ 'queue.name-required' | translate }} + + + + queue.poll-interval + + + {{ 'queue.poll-interval-required' | translate }} + + + {{ 'queue.poll-interval-min-value' | translate }} + + + + queue.partitions + + + {{ 'queue.partitions-required' | translate }} + + + {{ 'queue.partitions-min-value' | translate }} + + + + +
{{ 'queue.consumer-per-partition' | translate }}
+
{{'queue.consumer-per-partition-hint' | translate}}
+
+ + + queue.processing-timeout + + + {{ 'queue.pack-processing-timeout-required' | translate }} + + + {{ 'queue.pack-processing-timeout-min-value' | translate }} + + + + + + + queue.submit-strategy + + + +
+ + queue.submit-strategy + + + {{ strategy }} + + + + {{ 'queue.submit-strategy-type-required' | translate }} + + + + queue.batch-size + + + {{ 'queue.batch-size-required' | translate }} + + + {{ 'queue.batch-size-min-value' | translate }} + + +
+
+
+ + + + queue.processing-strategy + + + +
+ + queue.processing-strategy + + + {{ strategy }} + + + + {{ 'queue.processing-strategy-type-required' | translate }} + + + + queue.retries + + + {{ 'queue.retries-required' | translate }} + + + {{ 'queue.retries-min-value' | translate }} + + + + queue.failure-percentage + + + {{ 'queue.failure-percentage-required' | translate }} + + + {{ 'queue.failure-percentage-min-value' | translate }} + + + {{ 'queue.failure-percentage-max-value' | translate }} + + + + queue.pause-between-retries + + + {{ 'queue.pause-between-retries-required' | translate }} + + + {{ 'queue.pause-between-retries-min-value' | translate }} + + + + queue.max-pause-between-retries + + + {{ 'queue.max-pause-between-retries-required' | translate }} + + + {{ 'queue.max-pause-between-retries-min-value' | translate }} + + +
+
+
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/queue/tenant-profile-queue.component.scss b/ui-ngx/src/app/modules/home/components/profile/queue/tenant-profile-queue.component.scss new file mode 100644 index 0000000000..d20faee49c --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/queue/tenant-profile-queue.component.scss @@ -0,0 +1,22 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host ::ng-deep { + .queue-strategy { + .mat-expansion-panel-body { + padding-bottom: 0 !important; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/profile/queue/tenant-profile-queue.component.ts b/ui-ngx/src/app/modules/home/components/profile/queue/tenant-profile-queue.component.ts new file mode 100644 index 0000000000..7eb0062293 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/queue/tenant-profile-queue.component.ts @@ -0,0 +1,206 @@ +/// +/// Copyright © 2016-2022 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core'; +import { + ControlValueAccessor, + FormBuilder, + FormControl, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + Validator, + Validators +} from '@angular/forms'; +import { DeviceProfileAlarm } from '@shared/models/device.models'; +import { MatDialog } from '@angular/material/dialog'; +import { UtilsService } from '@core/services/utils.service'; +import { QueueProcessingStrategyTypes, QueueSubmitStrategyTypes } from '@shared/models/queue.models'; + +@Component({ + selector: 'tb-tenant-profile-queue', + templateUrl: './tenant-profile-queue.component.html', + styleUrls: ['./tenant-profile-queue.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => TenantProfileQueueComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => TenantProfileQueueComponent), + multi: true, + } + ] +}) +export class TenantProfileQueueComponent implements ControlValueAccessor, OnInit, Validator { + + @Input() + disabled: boolean; + + @Output() + removeQueue = new EventEmitter(); + + @Input() + expanded = false; + + @Input() + mainQueue = false; + + @Input() + newQueue = false; + + private modelValue: DeviceProfileAlarm; + + queueFormGroup: FormGroup; + + submitStrategies: string[] = []; + processingStrategies: string[] = []; + + hideBatchSize = false; + + private propagateChange = null; + private propagateChangePending = false; + + constructor(private dialog: MatDialog, + private utils: UtilsService, + private fb: FormBuilder) { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + if (this.propagateChangePending) { + this.propagateChangePending = false; + setTimeout(() => { + this.propagateChange(this.modelValue); + }, 0); + } + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + this.submitStrategies = Object.values(QueueSubmitStrategyTypes); + this.processingStrategies = Object.values(QueueProcessingStrategyTypes); + this.queueFormGroup = this.fb.group( + { + name: ['', [Validators.required]], + pollInterval: [25, [Validators.min(1), Validators.required]], + partitions: [10, [Validators.min(1), Validators.required]], + consumerPerPartition: [false, []], + packProcessingTimeout: [2000, [Validators.min(1), Validators.required]], + submitStrategy: this.fb.group({ + type: [null, [Validators.required]], + batchSize: [0, [Validators.min(1), Validators.required]], + }), + processingStrategy: this.fb.group({ + type: [null, [Validators.required]], + retries: [3, [Validators.min(0), Validators.required]], + failurePercentage: [ 0, [Validators.min(0), Validators.required, Validators.max(100)]], + pauseBetweenRetries: [3, [Validators.min(1), Validators.required]], + maxPauseBetweenRetries: [3, [Validators.min(1), Validators.required]], + }), + topic: [''] + }); + this.queueFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + this.queueFormGroup.get('name').valueChanges.subscribe((value) => this.queueFormGroup.patchValue({topic: `tb_rule_engine.${value}`})); + this.queueFormGroup.get('submitStrategy').get('type').valueChanges.subscribe(() => { + this.submitStrategyTypeChanged(); + }); + if (this.newQueue) { + this.queueFormGroup.get('name').enable({emitEvent: false}); + } else { + this.queueFormGroup.get('name').disable({emitEvent: false}); + } + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.queueFormGroup.disable({emitEvent: false}); + } else { + this.queueFormGroup.enable({emitEvent: false}); + this.queueFormGroup.get('name').disable({emitEvent: false}); + } + } + + writeValue(value: DeviceProfileAlarm): void { + this.propagateChangePending = false; + this.modelValue = value; + if (!this.modelValue.alarmType) { + this.expanded = true; + } + this.queueFormGroup.reset(this.modelValue || undefined, {emitEvent: false}); + if (!this.disabled && !this.queueFormGroup.valid) { + this.updateModel(); + } + } + + public validate(c: FormControl) { + if (c.parent) { + const queueName = c.value.name; + const profileQueues = []; + c.parent.getRawValue().forEach((queue) => { + profileQueues.push(queue.name); + } + ); + if (profileQueues.filter(profileQueue => profileQueue === queueName).length > 1) { + this.queueFormGroup.get('name').setErrors({ + unique: true + }); + } + } + return (this.queueFormGroup.valid) ? null : { + queue: { + valid: false, + }, + }; + } + + get queueTitle(): string { + const queueName = this.queueFormGroup.get('name').value; + return this.utils.customTranslation(queueName, queueName); + } + + private updateModel() { + const value = this.queueFormGroup.value; + this.modelValue = {...this.modelValue, ...value}; + if (this.propagateChange) { + this.propagateChange(this.modelValue); + } else { + this.propagateChangePending = true; + } + } + + submitStrategyTypeChanged() { + const form = this.queueFormGroup.get('submitStrategy') as FormGroup; + const type: QueueSubmitStrategyTypes = form.get('type').value; + const batchSizeField = form.get('batchSize'); + if (type === QueueSubmitStrategyTypes.BATCH) { + batchSizeField.enable(); + batchSizeField.patchValue(1000); + this.hideBatchSize = true; + } else { + batchSizeField.patchValue(null); + batchSizeField.disable(); + this.hideBatchSize = false; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/profile/queue/tenant-profile-queues.component.html b/ui-ngx/src/app/modules/home/components/profile/queue/tenant-profile-queues.component.html new file mode 100644 index 0000000000..59b7e063a1 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/queue/tenant-profile-queues.component.html @@ -0,0 +1,42 @@ + +
+
+ +
+ + +
+
+
+
+ tenant-profile.no-queue +
+
+ +
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/queue/tenant-profile-queues.component.scss b/ui-ngx/src/app/modules/home/components/profile/queue/tenant-profile-queues.component.scss new file mode 100644 index 0000000000..9702d807a6 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/queue/tenant-profile-queues.component.scss @@ -0,0 +1,31 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import '../../../../../../scss/constants'; + +:host { + .tb-tenant-profile-queues { + &.mat-padding { + padding: 8px; + @media #{$mat-gt-sm} { + padding: 16px; + } + } + } + + .tb-prompt{ + margin: 30px 0; + } +} diff --git a/ui-ngx/src/app/modules/home/components/profile/queue/tenant-profile-queues.component.ts b/ui-ngx/src/app/modules/home/components/profile/queue/tenant-profile-queues.component.ts new file mode 100644 index 0000000000..abcde44419 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/queue/tenant-profile-queues.component.ts @@ -0,0 +1,178 @@ +/// +/// Copyright © 2016-2022 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, forwardRef, Input, OnInit } from '@angular/core'; +import { + AbstractControl, + ControlValueAccessor, + FormArray, + FormBuilder, + FormControl, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + Validator, + Validators +} from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@app/core/core.state'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { Subscription } from 'rxjs'; +import { QueueInfo } from '@shared/models/queue.models'; + +@Component({ + selector: 'tb-tenant-profile-queues', + templateUrl: './tenant-profile-queues.component.html', + styleUrls: ['./tenant-profile-queues.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => TenantProfileQueuesComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => TenantProfileQueuesComponent), + multi: true, + } + ] +}) +export class TenantProfileQueuesComponent implements ControlValueAccessor, OnInit, Validator { + + tenantProfileQueuesFormGroup: FormGroup; + newQueue = false; + + private requiredValue: boolean; + get required(): boolean { + return this.requiredValue; + } + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + @Input() + disabled: boolean; + + private valueChangeSubscription: Subscription = null; + + private propagateChange = (v: any) => { }; + + constructor(private store: Store, + private fb: FormBuilder) { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + this.tenantProfileQueuesFormGroup = this.fb.group({ + queues: this.fb.array([]) + }); + } + + queuesFormArray(): FormArray { + return this.tenantProfileQueuesFormGroup.get('queues') as FormArray; + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.tenantProfileQueuesFormGroup.disable({emitEvent: false}); + } else { + this.tenantProfileQueuesFormGroup.enable({emitEvent: false}); + } + } + + writeValue(queues: Array | null): void { + if (this.valueChangeSubscription) { + this.valueChangeSubscription.unsubscribe(); + } + const queuesControls: Array = []; + if (queues) { + queues.forEach((queue) => { + queuesControls.push(this.fb.control(queue, [Validators.required])); + }); + } + this.tenantProfileQueuesFormGroup.setControl('queues', this.fb.array(queuesControls)); + if (this.disabled) { + this.tenantProfileQueuesFormGroup.disable({emitEvent: false}); + } else { + this.tenantProfileQueuesFormGroup.enable({emitEvent: false}); + } + this.valueChangeSubscription = this.tenantProfileQueuesFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + } + + public trackByQueue(index: number, queueControl: AbstractControl): string { + if (queueControl) { + return queueControl.value.id; + } else { + return null; + } + } + + public removeQueue(index: number) { + (this.tenantProfileQueuesFormGroup.get('queues') as FormArray).removeAt(index); + } + + public addQueue() { + const queue = { + consumerPerPartition: false, + name: '', + packProcessingTimeout: 2000, + partitions: 10, + pollInterval: 25, + processingStrategy: { + failurePercentage: 0, + maxPauseBetweenRetries: 3, + pauseBetweenRetries: 3, + retries: 3, + type: '' + }, + submitStrategy: { + batchSize: 0, + type: '' + }, + topic: '' + }; + this.newQueue = true; + const queuesArray = this.tenantProfileQueuesFormGroup.get('queues') as FormArray; + queuesArray.push(this.fb.control(queue, [])); + this.tenantProfileQueuesFormGroup.updateValueAndValidity(); + if (!this.tenantProfileQueuesFormGroup.valid) { + this.updateModel(); + } + } + + public validate(c: FormControl) { + return (this.tenantProfileQueuesFormGroup.valid) ? null : { + queues: { + valid: false, + }, + }; + } + + private updateModel() { + const queues: Array = this.tenantProfileQueuesFormGroup.get('queues').value; + this.propagateChange(queues); + } +} diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant-profile-data.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant-profile-data.component.html index 115a224374..81ca768911 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant-profile-data.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/tenant-profile-data.component.html @@ -16,11 +16,6 @@ -->
- - - - - diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant-profile-data.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant-profile-data.component.ts index 421915ed83..af59fbdf17 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant-profile-data.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/tenant-profile-data.component.ts @@ -79,7 +79,7 @@ export class TenantProfileDataComponent implements ControlValueAccessor, OnInit } writeValue(value: TenantProfileData | null): void { - this.tenantProfileDataFormGroup.patchValue({configuration: value?.configuration}, {emitEvent: false}); + this.tenantProfileDataFormGroup.patchValue({configuration: value}, {emitEvent: false}); } private updateModel() { diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.html index e2eb776e5b..4b511037e7 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.html @@ -68,10 +68,26 @@
{{'tenant.isolated-tb-rule-engine-details' | translate}}
- - + + + + +
{{'tenant-profile.queues-with-count' | translate: + {count: entityForm.get('profileData').get('queueConfiguration').value ? + entityForm.get('profileData').get('queueConfiguration').value.length : 0} }}
+
+
+ + + +
+ + +
tenant-profile.description diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.scss b/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.scss index 21c4f53c08..e6e5d3b9cf 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.scss +++ b/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.scss @@ -25,4 +25,14 @@ white-space: normal; } } + .fields-group { + padding: 0 16px 8px; + margin-bottom: 10px; + border: 1px groove rgba(0, 0, 0, .25); + border-radius: 4px; + legend { + color: rgba(0, 0, 0, .7); + width: fit-content; + } + } } diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.ts index 9fd3bb47dd..285cecc5e6 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.ts @@ -18,12 +18,7 @@ import { ChangeDetectorRef, Component, Inject, Input, Optional } from '@angular/ import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { - createTenantProfileConfiguration, - TenantProfile, - TenantProfileData, - TenantProfileType -} from '@shared/models/tenant.model'; +import { createTenantProfileConfiguration, TenantProfile, TenantProfileType } from '@shared/models/tenant.model'; import { ActionNotificationShow } from '@app/core/notification/notification.actions'; import { TranslateService } from '@ngx-translate/core'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; @@ -48,11 +43,6 @@ export class TenantProfileComponent extends EntityComponent { super(store, fb, entityValue, entitiesTableConfigValue, cd); } - ngOnInit() { - this.showQueueParams(); - this.entityForm.get('isolatedTbRuleEngine').valueChanges.subscribe(() => this.showQueueParams()); - } - hideDelete() { if (this.entitiesTableConfig) { return !this.entitiesTableConfig.deleteEnabled(this.entity); @@ -62,37 +52,61 @@ export class TenantProfileComponent extends EntityComponent { } buildForm(entity: TenantProfile): FormGroup { - return this.fb.group( + const mainQueue = [ + { + consumerPerPartition: true, + name: 'Main', + packProcessingTimeout: 2000, + partitions: 10, + pollInterval: 25, + processingStrategy: { + failurePercentage: 0, + maxPauseBetweenRetries: 3, + pauseBetweenRetries: 3, + retries: 3, + type: 'SKIP_ALL_FAILURES' + }, + submitStrategy: { + batchSize: 1000, + type: 'BURST' + }, + topic: 'tb_rule_engine.main' + } + ]; + const formGroup = this.fb.group( { name: [entity ? entity.name : '', [Validators.required, Validators.maxLength(255)]], isolatedTbCore: [entity ? entity.isolatedTbCore : false, []], isolatedTbRuleEngine: [entity ? entity.isolatedTbRuleEngine : false, []], - profileData: [entity && !this.isAdd ? entity.profileData : { - configuration: createTenantProfileConfiguration(TenantProfileType.DEFAULT) - } as TenantProfileData, []], + profileData: this.fb.group({ + configuration: [entity && !this.isAdd ? entity?.profileData.configuration + : createTenantProfileConfiguration(TenantProfileType.DEFAULT), []], + queueConfiguration: [null, []] + }), description: [entity ? entity.description : '', []], } ); + formGroup.get('isolatedTbRuleEngine').valueChanges.subscribe((value) => { + if (value) { + formGroup.get('profileData').patchValue({ + queueConfiguration: mainQueue + }, {emitEvent: false}); + } else { + formGroup.get('profileData').patchValue({ + queueConfiguration: null + }, {emitEvent: false}); + } + }); + return formGroup; } updateForm(entity: TenantProfile) { - this.entityForm.patchValue({name: entity.name}); - this.entityForm.patchValue({isolatedTbCore: entity.isolatedTbCore}); - this.entityForm.patchValue({isolatedTbRuleEngine: entity.isolatedTbRuleEngine}); - this.entityForm.patchValue({profileData: !this.isAdd ? entity.profileData : { - configuration: createTenantProfileConfiguration(TenantProfileType.DEFAULT) - } as TenantProfileData}); - this.entityForm.patchValue({description: entity.description}); - } - - showQueueParams(): boolean { - let isolatedTbRuleEngine: boolean = this.entityForm.get('isolatedTbRuleEngine').value; - if (isolatedTbRuleEngine) { -//enable - } else { -//disable - } - return isolatedTbRuleEngine; + this.entityForm.patchValue({name: entity.name}, {emitEvent: false}); + this.entityForm.patchValue({isolatedTbCore: entity.isolatedTbCore}, {emitEvent: false}); + this.entityForm.patchValue({isolatedTbRuleEngine: entity.isolatedTbRuleEngine}, {emitEvent: false}); + this.entityForm.get('profileData').patchValue({configuration: entity.profileData?.configuration}, {emitEvent: false}); + this.entityForm.get('profileData').patchValue({queueConfiguration: entity.profileData?.queueConfiguration}, {emitEvent: false}); + this.entityForm.patchValue({description: entity.description}, {emitEvent: false}); } updateFormState() { diff --git a/ui-ngx/src/app/modules/home/pages/admin/queue/queue.component.html b/ui-ngx/src/app/modules/home/pages/admin/queue/queue.component.html index ae8f08e348..9d7b9ead45 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/queue/queue.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/queue/queue.component.html @@ -20,12 +20,12 @@ [disabled]="(isLoading$ | async)" (click)="onEntityAction($event, 'delete')" [fxShow]="!hideDelete() && !isEdit"> - {{'queue.delete' | translate }} + {{ 'queue.delete' | translate }}
- +
admin.queue-name @@ -57,10 +57,6 @@ - - - -
{{ 'queue.consumer-per-partition' | translate }}
{{'queue.consumer-per-partition-hint' | translate}}
@@ -77,115 +73,113 @@ {{ 'queue.pack-processing-timeout-min-value' | translate }} - -
- + - - queue.submit-strategy - {{panel1.expanded ? 'keyboard_arrow_up' : 'keyboard_arrow_down' }} + + queue.submit-strategy -
- - queue.submit-strategy - - - {{ strategy }} - - - - {{ 'queue.submit-strategy-type-required' | translate }} - - - - queue.batch-size - - - {{ 'queue.batch-size-required' | translate }} - - - {{ 'queue.batch-size-min-value' | translate }} - - -
+ +
+ + queue.submit-strategy + + + {{ strategy }} + + + + {{ 'queue.submit-strategy-type-required' | translate }} + + + + queue.batch-size + + + {{ 'queue.batch-size-required' | translate }} + + + {{ 'queue.batch-size-min-value' | translate }} + + +
+
- + - - queue.processing-strategy - {{panel2.expanded ? 'keyboard_arrow_up' : 'keyboard_arrow_down' }} + + queue.processing-strategy - -
- - queue.processing-strategy - - - {{ strategy }} - - - - {{ 'queue.processing-strategy-type-required' | translate }} - - - - queue.retries - - - {{ 'queue.retries-required' | translate }} - - - {{ 'queue.retries-min-value' | translate }} - - - - queue.failure-percentage - - - {{ 'queue.failure-percentage-required' | translate }} - - - {{ 'queue.failure-percentage-min-value' | translate }} - - - {{ 'queue.failure-percentage-max-value' | translate }} - - - - queue.pause-between-retries - - - {{ 'queue.pause-between-retries-required' | translate }} - - - {{ 'queue.pause-between-retries-min-value' | translate }} - - - - queue.max-pause-between-retries - - - {{ 'queue.max-pause-between-retries-required' | translate }} - - - {{ 'queue.max-pause-between-retries-min-value' | translate }} - - -
+ +
+ + queue.processing-strategy + + + {{ strategy }} + + + + {{ 'queue.processing-strategy-type-required' | translate }} + + + + queue.retries + + + {{ 'queue.retries-required' | translate }} + + + {{ 'queue.retries-min-value' | translate }} + + + + queue.failure-percentage + + + {{ 'queue.failure-percentage-required' | translate }} + + + {{ 'queue.failure-percentage-min-value' | translate }} + + + {{ 'queue.failure-percentage-max-value' | translate }} + + + + queue.pause-between-retries + + + {{ 'queue.pause-between-retries-required' | translate }} + + + {{ 'queue.pause-between-retries-min-value' | translate }} + + + + queue.max-pause-between-retries + + + {{ 'queue.max-pause-between-retries-required' | translate }} + + + {{ 'queue.max-pause-between-retries-min-value' | translate }} + + +
+
-
diff --git a/ui-ngx/src/app/modules/home/pages/admin/queue/queue.component.scss b/ui-ngx/src/app/modules/home/pages/admin/queue/queue.component.scss index 852e391b28..c87504819e 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/queue/queue.component.scss +++ b/ui-ngx/src/app/modules/home/pages/admin/queue/queue.component.scss @@ -14,46 +14,9 @@ * limitations under the License. */ :host ::ng-deep { - - .mat-expansion-panel:not([class*='mat-elevation-z']) { - box-shadow: none; - } - - .mat-accordion-container { - margin-bottom: 16px; - } - - .mat-expansion-panel { - - &:hover { - box-shadow: 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 5px 5px -3px rgba(0, 0, 0, 0.2); - } - - &-header { - height: 56px; - border: 1px solid #f2f2f2; - - &-title { - align-items: center; - justify-content: space-between; - margin-right: 0; - } - } - - &.mat-expanded { - border: none; - box-shadow: 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 5px 5px -3px rgba(0, 0, 0, 0.2); - - .mat-expansion-panel { - - &-content { - margin-top: 20px; - } - - &-body { - padding-bottom: 10px; - } - } + .queue-form { + .mat-expansion-panel-body { + padding-bottom: 0 !important; } } } diff --git a/ui-ngx/src/app/modules/home/pages/admin/queue/queue.component.ts b/ui-ngx/src/app/modules/home/pages/admin/queue/queue.component.ts index f9a60773df..f436264793 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/queue/queue.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/queue/queue.component.ts @@ -23,8 +23,6 @@ import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { TranslateService } from '@ngx-translate/core'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; -import set = Reflect.set; -import { distinctUntilChanged } from 'rxjs/operators'; @Component({ selector: 'tb-queue', @@ -39,7 +37,7 @@ export class QueueComponent extends EntityComponent { processingStrategies: string[] = []; QueueSubmitStrategyTypes = QueueSubmitStrategyTypes; - hideBatchSize: boolean = false; + hideBatchSize = false; constructor(protected store: Store, protected translate: TranslateService, @@ -140,7 +138,7 @@ export class QueueComponent extends EntityComponent { } submitStrategyTypeChanged() { - const form = this.entityForm.get("submitStrategy") as FormGroup; + const form = this.entityForm.get('submitStrategy') as FormGroup; const type: QueueSubmitStrategyTypes = form.get('type').value; const batchSizeField = form.get('batchSize'); if (type === QueueSubmitStrategyTypes.BATCH) { diff --git a/ui-ngx/src/app/shared/models/queue.models.ts b/ui-ngx/src/app/shared/models/queue.models.ts index 508cf2aee1..3224d7dc23 100644 --- a/ui-ngx/src/app/shared/models/queue.models.ts +++ b/ui-ngx/src/app/shared/models/queue.models.ts @@ -16,7 +16,7 @@ import { BaseData, HasId } from '@shared/models/base-data'; import { TenantId } from '@shared/models/id/tenant-id'; -import {QueueId} from "@shared/models/id/queue-id"; +import {QueueId} from '@shared/models/id/queue-id'; export enum ServiceType { TB_CORE = 'TB_CORE', @@ -43,9 +43,10 @@ export enum QueueProcessingStrategyTypes { } export interface QueueInfo extends BaseData { + name: string; packProcessingTimeout: number; partitions: number; - consumerPerPartition: boolean, + consumerPerPartition: boolean; pollInterval: number; processingStrategy: { type: QueueProcessingStrategyTypes, @@ -58,6 +59,6 @@ export interface QueueInfo extends BaseData { type: QueueSubmitStrategyTypes, batchSize: number, }; - tenantId: TenantId; + tenantId?: TenantId; topic: string; } diff --git a/ui-ngx/src/app/shared/models/tenant.model.ts b/ui-ngx/src/app/shared/models/tenant.model.ts index cceb1cc7d7..14c0d232df 100644 --- a/ui-ngx/src/app/shared/models/tenant.model.ts +++ b/ui-ngx/src/app/shared/models/tenant.model.ts @@ -18,7 +18,7 @@ import { ContactBased } from '@shared/models/contact-based.model'; import { TenantId } from './id/tenant-id'; import { TenantProfileId } from '@shared/models/id/tenant-profile-id'; import { BaseData } from '@shared/models/base-data'; -import {Validators} from "@angular/forms"; +import { QueueInfo } from '@shared/models/queue.models'; export enum TenantProfileType { DEFAULT = 'DEFAULT' @@ -98,7 +98,7 @@ export function createTenantProfileConfiguration(type: TenantProfileType): Tenan export interface TenantProfileData { configuration: TenantProfileConfiguration; - queueConfiguration?: any; + queueConfiguration?: QueueInfo; } export interface TenantProfile extends BaseData { diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 4414e71847..880778d6c4 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2862,7 +2862,10 @@ "max-sms-range": "Maximum number of SMS sent can't be negative", "max-created-alarms": "Maximum number of alarms created (0 - unlimited)", "max-created-alarms-required": "Maximum number of alarms created is required.", - "max-created-alarms-range": "Maximum number of alarms created can't be negative" + "max-created-alarms-range": "Maximum number of alarms created can't be negative", + "no-queue": "No Queue configured", + "add-queue": "Add Queue", + "queues-with-count": "Queues ({{count}})" }, "timeinterval": { "seconds-interval": "{ seconds, plural, 1 {1 second} other {# seconds} }",