From 48e5aed0b628f98b009fc625d42869da08b02512 Mon Sep 17 00:00:00 2001 From: Artem Babak Date: Wed, 6 Jul 2022 14:33:18 +0300 Subject: [PATCH] Rate limits implementation --- .../home/components/home-components.module.ts | 16 +- ...enant-profile-configuration.component.html | 118 +++++-------- ...-tenant-profile-configuration.component.ts | 3 + .../rate-limits-details-dialog.component.html | 52 ++++++ .../rate-limits-details-dialog.component.ts | 83 +++++++++ .../rate-limits-list.component.html | 61 +++++++ .../rate-limits-list.component.scss | 47 +++++ .../rate-limits/rate-limits-list.component.ts | 161 ++++++++++++++++++ .../rate-limits-text.component.html | 19 +++ .../rate-limits-text.component.scss | 49 ++++++ .../rate-limits/rate-limits-text.component.ts | 98 +++++++++++ .../rate-limits/rate-limits.component.html | 34 ++++ .../rate-limits/rate-limits.component.scss | 31 ++++ .../rate-limits/rate-limits.component.ts | 153 +++++++++++++++++ .../app/shared/models/rate-limits.models.ts | 137 +++++++++++++++ .../assets/locale/locale.constant-en_US.json | 36 +++- 16 files changed, 1018 insertions(+), 80 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-details-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-details-dialog.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-list.component.html create mode 100644 ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-list.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-list.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-text.component.html create mode 100644 ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-text.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-text.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.component.html create mode 100644 ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.component.ts create mode 100644 ui-ngx/src/app/shared/models/rate-limits.models.ts 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 d195137509..9468289df8 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 @@ -166,6 +166,10 @@ import { EntityTypesVersionLoadComponent } from '@home/components/vc/entity-type import { ComplexVersionLoadComponent } from '@home/components/vc/complex-version-load.component'; import { RemoveOtherEntitiesConfirmComponent } from '@home/components/vc/remove-other-entities-confirm.component'; import { AutoCommitSettingsComponent } from '@home/components/vc/auto-commit-settings.component'; +import { RateLimitsListComponent } from '@home/components/profile/tenant/rate-limits/rate-limits-list.component'; +import { RateLimitsComponent } from '@home/components/profile/tenant/rate-limits/rate-limits.component'; +import { RateLimitsTextComponent } from '@home/components/profile/tenant/rate-limits/rate-limits-text.component'; +import { RateLimitsDetailsDialogComponent } from '@home/components/profile/tenant/rate-limits/rate-limits-details-dialog.component'; @NgModule({ declarations: @@ -302,7 +306,11 @@ import { AutoCommitSettingsComponent } from '@home/components/vc/auto-commit-set EntityTypesVersionLoadComponent, ComplexVersionLoadComponent, RemoveOtherEntitiesConfirmComponent, - AutoCommitSettingsComponent + AutoCommitSettingsComponent, + RateLimitsListComponent, + RateLimitsComponent, + RateLimitsTextComponent, + RateLimitsDetailsDialogComponent ], imports: [ CommonModule, @@ -432,7 +440,11 @@ import { AutoCommitSettingsComponent } from '@home/components/vc/auto-commit-set EntityTypesVersionLoadComponent, ComplexVersionLoadComponent, RemoveOtherEntitiesConfirmComponent, - AutoCommitSettingsComponent + AutoCommitSettingsComponent, + RateLimitsListComponent, + RateLimitsComponent, + RateLimitsTextComponent, + RateLimitsDetailsDialogComponent ], providers: [ WidgetComponentService, diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html index 0e1553806a..5484aaed28 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html @@ -418,38 +418,24 @@
- Rate limits + + {{ 'tenant-profile.rate-limits.rate-limits' | translate }} +
- - tenant-profile.transport-tenant-msg-rate-limit - - - {{ 'tenant-profile.incorrect-pattern-for-rate-limits' | translate}} - - - - tenant-profile.transport-device-msg-rate-limit - - - {{ 'tenant-profile.incorrect-pattern-for-rate-limits' | translate}} - - + + + +
- - tenant-profile.transport-tenant-telemetry-msg-rate-limit - - - {{ 'tenant-profile.incorrect-pattern-for-rate-limits' | translate}} - - - - tenant-profile.transport-device-telemetry-msg-rate-limit - - - {{ 'tenant-profile.incorrect-pattern-for-rate-limits' | translate}} - - + + + +
@@ -459,40 +445,28 @@
- - tenant-profile.transport-tenant-telemetry-data-points-rate-limit - - - {{ 'tenant-profile.incorrect-pattern-for-rate-limits' | translate}} - - - - tenant-profile.transport-device-telemetry-data-points-rate-limit - - - {{ 'tenant-profile.incorrect-pattern-for-rate-limits' | translate}} - - + + + +
- - tenant-profile.tenant-rest-limits - - - {{ 'tenant-profile.incorrect-pattern-for-rate-limits' | translate}} - - - - tenant-profile.customer-rest-limits - - - {{ 'tenant-profile.incorrect-pattern-for-rate-limits' | translate}} - - + + + + +
+
+ + + +
@@ -503,22 +477,12 @@ tenant-profile.tenant-entity-import-rate-limit -
-
- - tenant-profile.ws-limit-updates-per-session - - - {{ 'tenant-profile.incorrect-pattern-for-rate-limits' | translate}} - - - - tenant-profile.cassandra-tenant-limits-configuration - - - {{ 'tenant-profile.incorrect-pattern-for-rate-limits' | translate}} - - +
diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts index 65f030a3a6..ac2702ca43 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts @@ -21,6 +21,7 @@ import { AppState } from '@app/core/core.state'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { DefaultTenantProfileConfiguration, TenantProfileConfiguration } from '@shared/models/tenant.model'; import { isDefinedAndNotNull } from '@core/utils'; +import { RateLimitsType } from '@shared/models/rate-limits.models'; @Component({ selector: 'tb-default-tenant-profile-configuration', @@ -49,6 +50,8 @@ export class DefaultTenantProfileConfigurationComponent implements ControlValueA @Input() disabled: boolean; + rateLimitsType = RateLimitsType; + private propagateChange = (v: any) => { }; constructor(private store: Store, diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-details-dialog.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-details-dialog.component.html new file mode 100644 index 0000000000..911b44ecd8 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-details-dialog.component.html @@ -0,0 +1,52 @@ + +
+ +

{{ title }}

+ + +
+ + +
+
+
+ + +
+
+
+
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-details-dialog.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-details-dialog.component.ts new file mode 100644 index 0000000000..69bf5cda4f --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-details-dialog.component.ts @@ -0,0 +1,83 @@ +/// +/// 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, Inject, OnInit, SkipSelf } from '@angular/core'; +import { ErrorStateMatcher } from '@angular/material/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm } from '@angular/forms'; +import { Router } from '@angular/router'; +import { DialogComponent } from '@app/shared/components/dialog.component'; +import { TranslateService } from '@ngx-translate/core'; + +export interface RateLimitsDetailsDialogData { + rateLimits: string; + title: string; + readonly: boolean; +} + +@Component({ + templateUrl: './rate-limits-details-dialog.component.html', + providers: [{provide: ErrorStateMatcher, useExisting: RateLimitsDetailsDialogComponent}] +}) +export class RateLimitsDetailsDialogComponent extends DialogComponent + implements OnInit, ErrorStateMatcher { + + editDetailsFormGroup: FormGroup; + + submitted: boolean = false; + + rateLimits: string = this.data.rateLimits; + + title: string = this.data.title; + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: RateLimitsDetailsDialogData, + @SkipSelf() private errorStateMatcher: ErrorStateMatcher, + public dialogRef: MatDialogRef, + private fb: FormBuilder, + public translate: TranslateService) { + super(store, router, dialogRef); + + this.editDetailsFormGroup = this.fb.group({ + rateLimits: [this.rateLimits, []] + }); + if (this.data.readonly) { + this.editDetailsFormGroup.disable(); + } + } + + ngOnInit(): void { + } + + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + const originalErrorState = this.errorStateMatcher.isErrorState(control, form); + const customErrorState = !!(control && control.invalid && this.submitted); + return originalErrorState || customErrorState; + } + + cancel(): void { + this.dialogRef.close(null); + } + + save(): void { + this.submitted = true; + this.dialogRef.close(this.editDetailsFormGroup.get('rateLimits').value); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-list.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-list.component.html new file mode 100644 index 0000000000..c0923927d8 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-list.component.html @@ -0,0 +1,61 @@ + +
+
+
+ tenant-profile.rate-limits.but-less-than +
+
+ + tenant-profile.rate-limits.number-of-messages + + + {{ 'tenant-profile.rate-limits.number-of-messages-required' | translate }} + + + + tenant-profile.rate-limits.per-seconds + + + {{ 'tenant-profile.rate-limits.per-seconds-required' | translate }} + + + +
+
+
+ +
+
+ tenant-profile.rate-limits.preview +
+ +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-list.component.scss b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-list.component.scss new file mode 100644 index 0000000000..0db3694166 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-list.component.scss @@ -0,0 +1,47 @@ +/** + * 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 { + button { + min-width: 40px; + mat-icon { + margin-top: 0.6em; + } + } + .tb-rate-limits-preview { + margin-top: 1.5em; + span { + padding-left: 1em; + } + div { + border: 1px groove rgba(0, 0, 0, .25); + border-radius: 4px; + padding: 1em; + } + } + .tb-rate-limits-operation { + font-size: 12px; + color: rgba(0,0,0,.54); + margin-bottom: 16px; + } +} + +:host ::ng-deep { + mat-form-field { + .mat-form-field-wrapper { + padding-bottom: 1em; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-list.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-list.component.ts new file mode 100644 index 0000000000..eb7a37cfde --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-list.component.ts @@ -0,0 +1,161 @@ +/// +/// 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 { + ControlValueAccessor, + FormArray, + FormBuilder, + FormControl, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + ValidationErrors, + Validator, + Validators +} from '@angular/forms'; +import { Subscription } from 'rxjs'; +import { + RateLimits, + rateLimitsArrayToString, + stringToRateLimitsArray +} from '@shared/models/rate-limits.models'; +import { isDefinedAndNotNull } from '@core/utils'; + +@Component({ + selector: 'tb-rate-limits-list', + templateUrl: './rate-limits-list.component.html', + styleUrls: ['./rate-limits-list.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => RateLimitsListComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => RateLimitsListComponent), + multi: true + } + ] +}) +export class RateLimitsListComponent implements ControlValueAccessor, Validator, OnInit { + + @Input() disabled: boolean; + + rateLimitsListFormGroup: FormGroup; + + rateLimitsControl: FormControl; + + private propagateChange = (v: any) => { }; + + private valueChangeSubscription: Subscription = null; + + constructor(private fb: FormBuilder) { + } + + ngOnInit(): void { + this.rateLimitsListFormGroup = this.fb.group({}); + this.rateLimitsListFormGroup.addControl('rateLimits', + this.fb.array([])); + this.rateLimitsControl = this.fb.control(null); + this.rateLimitsListFormGroup.valueChanges.subscribe((value) => { + this.updateView(value?.rateLimits); + } + ); + } + + rateLimitsFormArray(): FormArray { + return this.rateLimitsListFormGroup.get('rateLimits') as FormArray; + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState?(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.rateLimitsListFormGroup.disable({emitEvent: false}); + this.rateLimitsControl.disable({emitEvent: false}); + } else { + this.rateLimitsListFormGroup.enable({emitEvent: false}); + this.rateLimitsControl.enable({emitEvent: false}); + } + } + + validate(): ValidationErrors | null { + return this.rateLimitsListFormGroup.valid && this.rateLimitsControl.valid ? null : { + rateLimitsList: {valid: false} + }; + } + + writeValue(value: string) { + if (this.valueChangeSubscription) { + this.valueChangeSubscription.unsubscribe(); + } + const rateLimitsControls: Array = []; + if (value) { + let rateLimitsArray = value.split(','); + for (let i = 0; i < rateLimitsArray.length; i++) { + let valueTime = rateLimitsArray[i].split(':'); + let value = valueTime[0]; + let time = valueTime[1]; + const rateLimitsControl = this.fb.group({ + value: [value, [Validators.required]], + time: [time, [Validators.required]] + }); + if (this.disabled) { + rateLimitsControl.disable(); + } + rateLimitsControls.push(rateLimitsControl); + } + } + this.rateLimitsListFormGroup.setControl('rateLimits', this.fb.array(rateLimitsControls)); + this.rateLimitsControl.patchValue(stringToRateLimitsArray(value), {emitEvent: false}); + this.valueChangeSubscription = this.rateLimitsListFormGroup.valueChanges.subscribe((value) => { + this.updateView(value?.rateLimits); + }); + } + + public removeRateLimits(index: number) { + (this.rateLimitsListFormGroup.get('rateLimits') as FormArray).removeAt(index); + } + + public addRateLimits() { + const rateLimitsArray = this.rateLimitsListFormGroup.get('rateLimits') as FormArray; + rateLimitsArray.push(this.fb.group({ + value: [null, [Validators.required]], + time: [null, [Validators.required]] + })); + this.rateLimitsListFormGroup.updateValueAndValidity(); + } + + updateView(rateLimitsArray: Array) { + if (rateLimitsArray.length > 0) { + const notNullRateLimits = rateLimitsArray.filter(rateLimits => isDefinedAndNotNull(rateLimits.value) && isDefinedAndNotNull(rateLimits.time)); + const rateLimitsString = rateLimitsArrayToString(notNullRateLimits); + this.propagateChange(rateLimitsString); + this.rateLimitsControl.patchValue(stringToRateLimitsArray(rateLimitsString), {emitEvent: false}); + } else { + this.propagateChange(null); + this.rateLimitsControl.patchValue(null, {emitEvent: false}); + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-text.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-text.component.html new file mode 100644 index 0000000000..98a30b055e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-text.component.html @@ -0,0 +1,19 @@ + +
diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-text.component.scss b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-text.component.scss new file mode 100644 index 0000000000..a8f8f8553f --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-text.component.scss @@ -0,0 +1,49 @@ +/** + * 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 { + .tb-rate-limits-text { + overflow: hidden; + + &.disabled { + opacity: 0.7; + } + &.required { + color: #f44336; + padding: 0 4px; + } + &.nowrap { + white-space: nowrap; + overflow: hidden; + } + } +} + +:host ::ng-deep { + .tb-rate-limits-text { + span { + font-size: 14px; + line-height: 1.8em; + } + .tb-rate-limits-value { + font-weight: bold; + border: 1px groove rgba(0, 0, 0, .25); + border-radius: 4px; + padding-left: 4px; + padding-right: 4px; + color: #305680; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-text.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-text.component.ts new file mode 100644 index 0000000000..9ab7e9b56b --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-text.component.ts @@ -0,0 +1,98 @@ +/// +/// 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 { ControlValueAccessor, FormBuilder, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { MatDialog } from '@angular/material/dialog'; +import { TranslateService } from '@ngx-translate/core'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { RateLimits, rateLimitsArrayToHtml } from '@shared/models/rate-limits.models'; + +@Component({ + selector: 'tb-rate-limits-text', + templateUrl: './rate-limits-text.component.html', + styleUrls: ['./rate-limits-text.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => RateLimitsTextComponent), + multi: true + } + ] +}) +export class RateLimitsTextComponent implements ControlValueAccessor, OnInit { + + private requiredValue: boolean; + get required(): boolean { + return this.requiredValue; + } + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + @Input() + disabled: boolean; + + @Input() + noRateLimitsText = this.translate.instant('tenant-profile.rate-limits.not-set'); + + @Input() + nowrap = false; + + requiredClass = false; + + public rateLimitsText: string; + + private propagateChange = (v: any) => { }; + + constructor(private dialog: MatDialog, + private fb: FormBuilder, + private translate: TranslateService) { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + writeValue(value: Array): void { + this.updateText(value); + } + + private updateText(value: Array) { + this.requiredClass = false; + if (value && value.length) { + this.rateLimitsText = rateLimitsArrayToHtml(this.translate, value); + } else { + if (this.required && !this.disabled) { + this.requiredClass = true; + } else { + this.rateLimitsText = this.noRateLimitsText; + } + } + } + +} diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.component.html new file mode 100644 index 0000000000..3f1b698553 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.component.html @@ -0,0 +1,34 @@ + +
+
+ {{ label | translate }} +
+ +
+
+
+ +
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.component.scss b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.component.scss new file mode 100644 index 0000000000..6d45b33b73 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.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. + */ +:host { + padding: 12px 0 12px 0; + + .fieldset-element { + cursor: pointer; + padding: 0.5em; + border: 1px groove rgba(0, 0, 0, 0.25); + border-radius: 4px; + width: 100%; + + .legend-element { + color: rgba(0, 0, 0, 0.54); + font-size: 12px; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.component.ts new file mode 100644 index 0000000000..34cbadb755 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.component.ts @@ -0,0 +1,153 @@ +/// +/// 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 { + ControlValueAccessor, + FormBuilder, + FormControl, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + Validator +} from '@angular/forms'; +import { MatDialog } from '@angular/material/dialog'; +import { + RateLimitsDetailsDialogComponent, + RateLimitsDetailsDialogData +} from '@home/components/profile/tenant/rate-limits/rate-limits-details-dialog.component'; +import { + addRateLimitTranslationMap, + editRateLimitTranslationMap, + rateLimitLabelTranslationMap, + RateLimitsType, + stringToRateLimitsArray +} from '@shared/models/rate-limits.models'; + +@Component({ + selector: 'tb-rate-limits', + templateUrl: './rate-limits.component.html', + styleUrls: ['./rate-limits.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => RateLimitsComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => RateLimitsComponent), + multi: true, + } + ] +}) +export class RateLimitsComponent implements ControlValueAccessor, OnInit, Validator { + + @Input() + disabled: boolean; + + @Input() + type: RateLimitsType; + + label: string; + + rateLimitsFormGroup: FormGroup; + + private modelValue: string; + + private propagateChange = null; + + constructor(private dialog: MatDialog, + private fb: FormBuilder) { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + this.label = rateLimitLabelTranslationMap.get(this.type); + this.rateLimitsFormGroup = this.fb.group({ + rateLimits: [null, []] + }); + } + + setDisabledState(isDisabled: boolean) { + this.disabled = isDisabled; + if (this.disabled) { + this.rateLimitsFormGroup.disable({emitEvent: false}); + } else { + this.rateLimitsFormGroup.enable({emitEvent: false}); + } + } + + writeValue(value: string) { + this.modelValue = value; + this.updateRateLimitsInfo(); + } + + public validate(c: FormControl) { + return null; + } + + public onClick($event: Event, action: string) { + if ($event) { + $event.stopPropagation(); + } + const title = this.setTitle(action); + this.dialog.open(RateLimitsDetailsDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + rateLimits: this.modelValue, + title, + readonly: this.disabled + } + }).afterClosed().subscribe((result) => { + if (result) { + this.modelValue = result; + this.updateModel(); + } + }); + } + + private setTitle(action: string): string { + switch (action) { + case 'add': + return addRateLimitTranslationMap.get(this.type); + case 'edit': + return editRateLimitTranslationMap.get(this.type); + } + } + + private updateRateLimitsInfo() { + this.rateLimitsFormGroup.patchValue( + { + rateLimits: stringToRateLimitsArray(this.modelValue) + } + ); + } + + private updateModel() { + this.updateRateLimitsInfo(); + this.propagateChange(this.modelValue); + } + +} diff --git a/ui-ngx/src/app/shared/models/rate-limits.models.ts b/ui-ngx/src/app/shared/models/rate-limits.models.ts new file mode 100644 index 0000000000..8b8eae794a --- /dev/null +++ b/ui-ngx/src/app/shared/models/rate-limits.models.ts @@ -0,0 +1,137 @@ +/// +/// 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 { TranslateService } from '@ngx-translate/core'; + +export interface RateLimits { + value: string; + time: string; +} + +export enum RateLimitsType { + DEVICE_MESSAGES = 'DEVICE_MESSAGES', + DEVICE_TELEMETRY_MESSAGES = 'DEVICE_TELEMETRY_MESSAGES', + DEVICE_TELEMETRY_DATA_POINTS = 'DEVICE_TELEMETRY_DATA_POINTS', + TENANT_MESSAGES = 'TENANT_MESSAGES', + TENANT_TELEMETRY_MESSAGES = 'TENANT_TELEMETRY_MESSAGES', + TENANT_TELEMETRY_DATA_POINTS = 'TENANT_TELEMETRY_DATA_POINTS', + TENANT_SERVER_REST_LIMITS_CONFIGURATION = 'TENANT_SERVER_REST_LIMITS_CONFIGURATION', + CUSTOMER_SERVER_REST_LIMITS_CONFIGURATION = 'CUSTOMER_SERVER_REST_LIMITS_CONFIGURATION', + WS_UPDATE_PER_SESSION_RATE_LIMIT = 'WS_UPDATE_PER_SESSION_RATE_LIMIT', + CASSANDRA_QUERY_TENANT_RATE_LIMITS_CONFIGURATION = 'CASSANDRA_QUERY_TENANT_RATE_LIMITS_CONFIGURATION', +} + +export const rateLimitLabelTranslationMap = new Map( + [ + [RateLimitsType.TENANT_MESSAGES, 'tenant-profile.rate-limits.transport-tenant-msg'], + [RateLimitsType.TENANT_TELEMETRY_MESSAGES, 'tenant-profile.rate-limits.transport-tenant-telemetry-msg'], + [RateLimitsType.TENANT_TELEMETRY_DATA_POINTS, 'tenant-profile.rate-limits.transport-tenant-telemetry-data-points'], + [RateLimitsType.DEVICE_MESSAGES, 'tenant-profile.rate-limits.transport-device-msg'], + [RateLimitsType.DEVICE_TELEMETRY_MESSAGES, 'tenant-profile.rate-limits.transport-device-telemetry-msg'], + [RateLimitsType.DEVICE_TELEMETRY_DATA_POINTS, 'tenant-profile.rate-limits.transport-device-telemetry-data-points'], + [RateLimitsType.TENANT_SERVER_REST_LIMITS_CONFIGURATION, 'tenant-profile.transport-tenant-msg-rate-limit'], + [RateLimitsType.CUSTOMER_SERVER_REST_LIMITS_CONFIGURATION, 'tenant-profile.customer-rest-limits'], + [RateLimitsType.WS_UPDATE_PER_SESSION_RATE_LIMIT, 'tenant-profile.ws-limit-updates-per-session'], + [RateLimitsType.CASSANDRA_QUERY_TENANT_RATE_LIMITS_CONFIGURATION, 'tenant-profile.cassandra-tenant-limits-configuration'], + ] +); + +export const editRateLimitTranslationMap = new Map( + [ + [RateLimitsType.TENANT_MESSAGES, 'tenant-profile.rate-limits.edit-transport-tenant-msg-title'], + [RateLimitsType.TENANT_TELEMETRY_MESSAGES, 'tenant-profile.rate-limits.edit-transport-tenant-telemetry-msg-title'], + [RateLimitsType.TENANT_TELEMETRY_DATA_POINTS, 'tenant-profile.rate-limits.edit-transport-tenant-telemetry-data-points-title'], + [RateLimitsType.DEVICE_MESSAGES, 'tenant-profile.rate-limits.edit-transport-device-msg-title'], + [RateLimitsType.DEVICE_TELEMETRY_MESSAGES, 'tenant-profile.rate-limits.edit-transport-device-telemetry-msg-title'], + [RateLimitsType.DEVICE_TELEMETRY_DATA_POINTS, 'tenant-profile.rate-limits.edit-transport-device-telemetry-data-points-title'], + [RateLimitsType.TENANT_SERVER_REST_LIMITS_CONFIGURATION, 'tenant-profile.rate-limits.edit-transport-device-telemetry-data-points-title'], + [RateLimitsType.CUSTOMER_SERVER_REST_LIMITS_CONFIGURATION, 'tenant-profile.rate-limits.edit-transport-device-telemetry-data-points-title'], + [RateLimitsType.WS_UPDATE_PER_SESSION_RATE_LIMIT, 'tenant-profile.rate-limits.edit-transport-device-telemetry-data-points-title'], + [RateLimitsType.CASSANDRA_QUERY_TENANT_RATE_LIMITS_CONFIGURATION, 'tenant-profile.rate-limits.edit-transport-device-telemetry-data-points-title'], + ] +); + +export const addRateLimitTranslationMap = new Map( + [ + [RateLimitsType.TENANT_MESSAGES, 'tenant-profile.rate-limits.add-transport-tenant-msg-title'], + [RateLimitsType.TENANT_TELEMETRY_MESSAGES, 'tenant-profile.rate-limits.add-transport-tenant-telemetry-msg-title'], + [RateLimitsType.TENANT_TELEMETRY_DATA_POINTS, 'tenant-profile.rate-limits.add-transport-tenant-telemetry-data-points-title'], + [RateLimitsType.DEVICE_MESSAGES, 'tenant-profile.rate-limits.add-transport-device-msg-title'], + [RateLimitsType.DEVICE_TELEMETRY_MESSAGES, 'tenant-profile.rate-limits.add-transport-device-telemetry-msg-title'], + [RateLimitsType.DEVICE_TELEMETRY_DATA_POINTS, 'tenant-profile.rate-limits.add-transport-device-telemetry-data-points-title'], + [RateLimitsType.TENANT_SERVER_REST_LIMITS_CONFIGURATION, 'tenant-profile.rate-limits.add-transport-device-telemetry-data-points-title'], + [RateLimitsType.CUSTOMER_SERVER_REST_LIMITS_CONFIGURATION, 'tenant-profile.rate-limits.add-transport-device-telemetry-data-points-title'], + [RateLimitsType.WS_UPDATE_PER_SESSION_RATE_LIMIT, 'tenant-profile.rate-limits.add-transport-device-telemetry-data-points-title'], + [RateLimitsType.CASSANDRA_QUERY_TENANT_RATE_LIMITS_CONFIGURATION, 'tenant-profile.rate-limits.add-transport-device-telemetry-data-points-title'], + ] +); + +export function stringToRateLimitsArray(rateLimits: string): Array { + const result: Array = []; + if (rateLimits?.length > 0) { + let rateLimitsArrays = rateLimits.split(','); + for (let i = 0; i < rateLimitsArrays.length; i++) { + let valueTime = rateLimitsArrays[i].split(':'); + let value = valueTime[0]; + let time = valueTime[1]; + const rateLimitControl = { + value, + time + } + result.push(rateLimitControl); + } + } + return result; +} + +export function rateLimitsArrayToString(rateLimits: Array): string { + let result = ''; + for (let i = 0; i < rateLimits.length; i++) { + result = result.concat(rateLimits[i].value, ':', rateLimits[i].time); + if ((rateLimits.length > 1) && (i !== rateLimits.length - 1)) { + result = result.concat(','); + } + } + return result; +} + +export function rateLimitsArrayToHtml(translate: TranslateService, rateLimitsArray: Array): string { + const rateLimitsHtml = rateLimitsArray.map((rateLimits, index) => { + const isLast: boolean = index === rateLimitsArray.length-1; + return rateLimitsToHtml(translate, rateLimits, isLast); + }); + let result: string; + if (rateLimitsHtml.length > 1) { + const butLessThanText = translate.instant('tenant-profile.rate-limits.but-less-than'); + result = rateLimitsHtml.join(' ' + butLessThanText + ' '); + } else { + result = rateLimitsHtml[0]; + } + return result; +} + +function rateLimitsToHtml(translate: TranslateService, rateLimit: RateLimits, isLast: boolean): string { + const value = rateLimit.value; + const time = rateLimit.time; + const operation = translate.instant('tenant-profile.rate-limits.messages-per'); + const seconds = translate.instant('tenant-profile.rate-limits.sec'); + const comma = isLast ? '' : ','; + const result = `${value} + ${operation} + ${time} + ${seconds}${comma}
`; + return result; +} 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 6e76448847..4e83984480 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -3187,7 +3187,41 @@ "ws-limit-max-subscriptions-per-customer": "Subscriptions per customer maximum number", "ws-limit-max-subscriptions-per-regular-user": "Subscriptions per regular user maximum number", "ws-limit-max-subscriptions-per-public-user": "Subscriptions per public user maximum number", - "ws-limit-updates-per-session": "WS updates per session" + "ws-limit-updates-per-session": "WS updates per session", + "rate-limits": { + "add-limit": "Add limit", + "add-transport-tenant-msg-title": "Add transport tenant messages rate limit", + "add-transport-tenant-telemetry-msg-title": "Add transport tenant telemetry messages rate limit", + "add-transport-tenant-telemetry-data-points-title": "Add transport tenant telemetry data points rate limit", + "add-transport-device-msg-title": "Add transport device messages rate limit", + "add-transport-device-telemetry-msg-title": "Add transport device telemetry messages rate limit", + "add-transport-device-telemetry-data-points-title": "Add transport device telemetry data points rate limit", + "advanced-settings": "Advanced settings", + "edit-limit": "Edit limit", + "but-less-than": "but less than", + "edit-transport-tenant-msg-title": "Edit transport tenant messages rate limit", + "edit-transport-tenant-telemetry-msg-title": "Edit transport tenant telemetry messages rate limit", + "edit-transport-tenant-telemetry-data-points-title": "Edit transport tenant telemetry data points rate limit", + "edit-transport-device-msg-title": "Edit transport device messages rate limit", + "edit-transport-device-telemetry-msg-title": "Edit transport device telemetry messages rate limit", + "edit-transport-device-telemetry-data-points-title": "Edit transport device telemetry data points rate limit", + "messages-per": "messages per", + "not-set": "Not set", + "number-of-messages": "Number of messages", + "number-of-messages-required": "Number of messages is required.", + "preview": "Preview", + "per-seconds": "Per seconds", + "per-seconds-required": "Time rate is required.", + "rate-limits": "Rate limits", + "remove-limit": "Remove rate limit", + "transport-tenant-msg": "Transport tenant messages", + "transport-tenant-telemetry-msg": "Transport tenant telemetry messages", + "transport-tenant-telemetry-data-points": "Transport tenant telemetry data points", + "transport-device-msg": "Transport device messages", + "transport-device-telemetry-msg": "Transport device telemetry messages", + "transport-device-telemetry-data-points": "Transport device telemetry data points", + "sec": "sec" + } }, "timeinterval": { "seconds-interval": "{ seconds, plural, 1 {1 second} other {# seconds} }",