committed by
GitHub
16 changed files with 936 additions and 97 deletions
@ -0,0 +1,43 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
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. |
||||
|
|
||||
|
--> |
||||
|
<form [formGroup]="editDetailsFormGroup"> |
||||
|
<mat-toolbar color="primary"> |
||||
|
<h2>{{ title | translate }}</h2> |
||||
|
<span fxFlex></span> |
||||
|
<button mat-icon-button |
||||
|
[matDialogClose]="undefined" |
||||
|
type="button"> |
||||
|
<mat-icon class="material-icons">close</mat-icon> |
||||
|
</button> |
||||
|
</mat-toolbar> |
||||
|
<div mat-dialog-content> |
||||
|
<tb-rate-limits-list formControlName="rateLimits"></tb-rate-limits-list> |
||||
|
</div> |
||||
|
<div mat-dialog-actions fxLayoutAlign="end center"> |
||||
|
<button mat-button color="primary" |
||||
|
type="button" |
||||
|
[matDialogClose]="undefined" |
||||
|
cdkFocusInitial> |
||||
|
{{ 'action.cancel' | translate }} |
||||
|
</button> |
||||
|
<button *ngIf="!data.readonly" mat-raised-button color="primary" (click)="save()" |
||||
|
[disabled]="editDetailsFormGroup.invalid || !editDetailsFormGroup.dirty"> |
||||
|
{{ 'action.save' | translate }} |
||||
|
</button> |
||||
|
</div> |
||||
|
</form> |
||||
@ -0,0 +1,59 @@ |
|||||
|
///
|
||||
|
/// 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 } from '@angular/core'; |
||||
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { FormBuilder, FormGroup } from '@angular/forms'; |
||||
|
import { Router } from '@angular/router'; |
||||
|
import { DialogComponent } from '@app/shared/components/dialog.component'; |
||||
|
|
||||
|
export interface RateLimitsDetailsDialogData { |
||||
|
rateLimits: string; |
||||
|
title: string; |
||||
|
readonly: boolean; |
||||
|
} |
||||
|
|
||||
|
@Component({ |
||||
|
templateUrl: './rate-limits-details-dialog.component.html' |
||||
|
}) |
||||
|
export class RateLimitsDetailsDialogComponent extends DialogComponent<RateLimitsDetailsDialogComponent> { |
||||
|
|
||||
|
editDetailsFormGroup: FormGroup; |
||||
|
|
||||
|
rateLimits: string = this.data.rateLimits; |
||||
|
|
||||
|
title: string = this.data.title; |
||||
|
|
||||
|
constructor(protected store: Store<AppState>, |
||||
|
protected router: Router, |
||||
|
@Inject(MAT_DIALOG_DATA) public data: RateLimitsDetailsDialogData, |
||||
|
public dialogRef: MatDialogRef<RateLimitsDetailsDialogComponent>, |
||||
|
private fb: FormBuilder) { |
||||
|
super(store, router, dialogRef); |
||||
|
this.editDetailsFormGroup = this.fb.group({ |
||||
|
rateLimits: [this.rateLimits, []] |
||||
|
}); |
||||
|
if (this.data.readonly) { |
||||
|
this.editDetailsFormGroup.disable(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
save(): void { |
||||
|
this.dialogRef.close(this.editDetailsFormGroup.get('rateLimits').value); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,66 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
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. |
||||
|
|
||||
|
--> |
||||
|
<section class="tb-rate-limits-form" [formGroup]="rateLimitsListFormGroup"> |
||||
|
<div fxFlex [formGroup]="rateLimit" *ngFor="let rateLimit of rateLimitsFormArray.controls; let $index = index"> |
||||
|
<div class="tb-rate-limits-operation" *ngIf="$index > 0 && rateLimitsFormArray.controls.length > 1" translate> |
||||
|
tenant-profile.rate-limits.but-less-than |
||||
|
</div> |
||||
|
<div fxFlex fxLayout="row" fxLayoutGap="8px"> |
||||
|
<mat-form-field fxFlex hideRequiredMarker appearance="fill" class="mat-block"> |
||||
|
<mat-label translate>tenant-profile.rate-limits.number-of-messages</mat-label> |
||||
|
<input matInput placeholder="{{ 'tenant-profile.rate-limits.number-of-messages' | translate }}" |
||||
|
type="number" min="1" step="1" formControlName="value" required> |
||||
|
<mat-error *ngIf="rateLimit.get('value').hasError('required')"> |
||||
|
{{ 'tenant-profile.rate-limits.number-of-messages-required' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="rateLimit.get('value').hasError('min')"> |
||||
|
{{ 'tenant-profile.rate-limits.number-of-messages-min' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field fxFlex hideRequiredMarker appearance="fill" class="mat-block"> |
||||
|
<mat-label translate>tenant-profile.rate-limits.per-seconds</mat-label> |
||||
|
<input matInput placeholder="{{ 'tenant-profile.rate-limits.per-seconds' | translate }}" |
||||
|
type="number" min="1" step="1" formControlName="time" required> |
||||
|
<mat-error *ngIf="rateLimit.get('time').hasError('required')"> |
||||
|
{{ 'tenant-profile.rate-limits.per-seconds-required' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="rateLimit.get('time').hasError('min')"> |
||||
|
{{ 'tenant-profile.rate-limits.per-seconds-min' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<button mat-icon-button type="button" color="primary" |
||||
|
class="tb-rate-limits-button" |
||||
|
*ngIf="!rateLimitsFormArray.disabled" |
||||
|
(click)="removeRateLimits($index)" |
||||
|
[disabled]="disabled" |
||||
|
[matTooltip]="'tenant-profile.rate-limits.remove-limit' | translate" matTooltipPosition="above"> |
||||
|
<mat-icon>remove_circle_outline</mat-icon> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
<button mat-raised-button color="primary" *ngIf="!rateLimitsFormArray.disabled" |
||||
|
(click)="addRateLimits()" |
||||
|
[disabled]="disabled" |
||||
|
[matTooltip]="'tenant-profile.rate-limits.add-limit' | translate" matTooltipPosition="above"> |
||||
|
<span translate>tenant-profile.rate-limits.add-limit</span> |
||||
|
</button> |
||||
|
<div fxFlex fxLayoutGap="8px" fxLayout="column" class="tb-rate-limits-preview" [ngClass]="{'tb-rate-limits-preview-short': !disabled}"> |
||||
|
<span translate>tenant-profile.rate-limits.preview</span> |
||||
|
<tb-rate-limits-text [rateLimitsArray]="rateLimitsArray" [disabled]="disabled"></tb-rate-limits-text> |
||||
|
</div> |
||||
|
</section> |
||||
@ -0,0 +1,57 @@ |
|||||
|
/** |
||||
|
* 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-rate-limits-form { |
||||
|
@media #{$mat-gt-sm} { |
||||
|
min-width: 600px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.tb-rate-limits-preview { |
||||
|
margin-top: 1.5em; |
||||
|
span { |
||||
|
padding-left: 1em; |
||||
|
} |
||||
|
tb-rate-limits-text { |
||||
|
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; |
||||
|
} |
||||
|
|
||||
|
.tb-rate-limits-button { |
||||
|
margin-top: 0.5em; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
:host ::ng-deep { |
||||
|
mat-form-field { |
||||
|
.mat-form-field-wrapper { |
||||
|
padding-bottom: 1em; |
||||
|
} |
||||
|
.mat-form-field-infix { |
||||
|
width: 100%; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,152 @@ |
|||||
|
///
|
||||
|
/// 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, OnDestroy, OnInit } from '@angular/core'; |
||||
|
import { |
||||
|
ControlValueAccessor, |
||||
|
FormArray, |
||||
|
FormBuilder, |
||||
|
FormGroup, |
||||
|
NG_VALIDATORS, |
||||
|
NG_VALUE_ACCESSOR, |
||||
|
ValidationErrors, |
||||
|
Validator, |
||||
|
Validators |
||||
|
} from '@angular/forms'; |
||||
|
import { Subject, Subscription } from 'rxjs'; |
||||
|
import { RateLimits, rateLimitsArrayToString, stringToRateLimitsArray } from './rate-limits.models'; |
||||
|
import { isDefinedAndNotNull } from '@core/utils'; |
||||
|
import { takeUntil } from 'rxjs/operators'; |
||||
|
|
||||
|
@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, OnDestroy { |
||||
|
|
||||
|
@Input() disabled: boolean; |
||||
|
|
||||
|
rateLimitsListFormGroup: FormGroup; |
||||
|
|
||||
|
rateLimitsArray: Array<RateLimits>; |
||||
|
|
||||
|
private valueChangeSubscription: Subscription = null; |
||||
|
private destroy$ = new Subject(); |
||||
|
private propagateChange = (v: any) => { }; |
||||
|
|
||||
|
constructor(private fb: FormBuilder) {} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
this.rateLimitsListFormGroup = this.fb.group({ |
||||
|
rateLimits: this.fb.array([]) |
||||
|
}); |
||||
|
this.valueChangeSubscription = this.rateLimitsListFormGroup.valueChanges |
||||
|
.pipe(takeUntil(this.destroy$)) |
||||
|
.subscribe((value) => { |
||||
|
this.updateView(value?.rateLimits); |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
public removeRateLimits(index: number) { |
||||
|
(this.rateLimitsListFormGroup.get('rateLimits') as FormArray).removeAt(index); |
||||
|
} |
||||
|
|
||||
|
public addRateLimits() { |
||||
|
this.rateLimitsFormArray.push(this.fb.group({ |
||||
|
value: [null, [Validators.required]], |
||||
|
time: [null, [Validators.required]] |
||||
|
})); |
||||
|
} |
||||
|
|
||||
|
get 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}); |
||||
|
} else { |
||||
|
this.rateLimitsListFormGroup.enable({emitEvent: false}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
validate(): ValidationErrors | null { |
||||
|
return this.rateLimitsListFormGroup.valid ? null : { |
||||
|
rateLimitsList: {valid: false} |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
writeValue(rateLimits: string) { |
||||
|
const rateLimitsControls: Array<FormGroup> = []; |
||||
|
if (rateLimits) { |
||||
|
const rateLimitsArray = rateLimits.split(','); |
||||
|
for (let i = 0; i < rateLimitsArray.length; i++) { |
||||
|
const [value, time] = rateLimitsArray[i].split(':'); |
||||
|
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), {emitEvent: false}); |
||||
|
this.rateLimitsArray = stringToRateLimitsArray(rateLimits); |
||||
|
} |
||||
|
|
||||
|
updateView(rateLimitsArray: Array<RateLimits>) { |
||||
|
if (rateLimitsArray.length > 0) { |
||||
|
const notNullRateLimits = rateLimitsArray.filter(rateLimits => |
||||
|
isDefinedAndNotNull(rateLimits.value) && isDefinedAndNotNull(rateLimits.time) |
||||
|
); |
||||
|
const rateLimitsString = rateLimitsArrayToString(notNullRateLimits); |
||||
|
this.propagateChange(rateLimitsString); |
||||
|
this.rateLimitsArray = stringToRateLimitsArray(rateLimitsString); |
||||
|
} else { |
||||
|
this.propagateChange(null); |
||||
|
this.rateLimitsArray = null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
ngOnDestroy() { |
||||
|
this.destroy$.next(); |
||||
|
this.destroy$.complete(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
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. |
||||
|
|
||||
|
--> |
||||
|
<div class="tb-rate-limits-text" [ngClass]="{disabled: disabled}" [innerHTML]="rateLimitsText"></div> |
||||
@ -0,0 +1,41 @@ |
|||||
|
/** |
||||
|
* 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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
: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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
///
|
||||
|
/// 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, Input, OnChanges, SimpleChanges } from '@angular/core'; |
||||
|
import { TranslateService } from '@ngx-translate/core'; |
||||
|
import { RateLimits, rateLimitsArrayToHtml } from './rate-limits.models'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-rate-limits-text', |
||||
|
templateUrl: './rate-limits-text.component.html', |
||||
|
styleUrls: ['./rate-limits-text.component.scss'] |
||||
|
}) |
||||
|
export class RateLimitsTextComponent implements OnChanges { |
||||
|
|
||||
|
@Input() |
||||
|
rateLimitsArray: Array<RateLimits>; |
||||
|
|
||||
|
@Input() |
||||
|
disabled: boolean; |
||||
|
|
||||
|
rateLimitsText: string; |
||||
|
|
||||
|
constructor(private translate: TranslateService) { |
||||
|
} |
||||
|
|
||||
|
ngOnChanges(changes: SimpleChanges): void { |
||||
|
for (const propName of Object.keys(changes)) { |
||||
|
if (propName === 'rateLimitsArray') { |
||||
|
const change = changes[propName]; |
||||
|
this.updateView(change.currentValue); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private updateView(value: Array<RateLimits>): void { |
||||
|
if (value?.length) { |
||||
|
this.rateLimitsText = rateLimitsArrayToHtml(this.translate, value); |
||||
|
} else { |
||||
|
this.rateLimitsText = this.translate.instant('tenant-profile.rate-limits.not-set'); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
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. |
||||
|
|
||||
|
--> |
||||
|
<div fxFlex fxLayout="row" fxLayoutGap="8px"> |
||||
|
<fieldset class="fieldset-element"> |
||||
|
<legend class="legend-element">{{ label | translate }}</legend> |
||||
|
<div [formGroup]="rateLimitsFormGroup" fxFlex fxLayout="row" fxLayoutGap="8px" |
||||
|
(click)="onClick($event)" |
||||
|
[matTooltip]="'tenant-profile.rate-limits.edit-limit' | translate" matTooltipPosition="above"> |
||||
|
<tb-rate-limits-text [rateLimitsArray]="rateLimitsArray" [disabled]="disabled"></tb-rate-limits-text> |
||||
|
</div> |
||||
|
</fieldset> |
||||
|
<button mat-icon-button color="primary" type="button" *ngIf="!disabled" |
||||
|
class="tb-rate-limits-button" |
||||
|
(click)="onClick($event)" |
||||
|
[matTooltip]="'tenant-profile.rate-limits.add-limit' | translate" matTooltipPosition="above"> |
||||
|
<mat-icon>add_circle_outline</mat-icon> |
||||
|
</button> |
||||
|
</div> |
||||
@ -0,0 +1,35 @@ |
|||||
|
/** |
||||
|
* 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; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.tb-rate-limits-button { |
||||
|
margin-top: 0.5em; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,149 @@ |
|||||
|
///
|
||||
|
/// 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 { |
||||
|
RateLimits, |
||||
|
rateLimitsDialogTitleTranslationMap, |
||||
|
rateLimitsLabelTranslationMap, |
||||
|
RateLimitsType, |
||||
|
stringToRateLimitsArray |
||||
|
} from './rate-limits.models'; |
||||
|
import { isDefined } from '@core/utils'; |
||||
|
|
||||
|
@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; |
||||
|
|
||||
|
get rateLimitsArray(): Array<RateLimits> { |
||||
|
return this.rateLimitsFormGroup.get('rateLimits').value; |
||||
|
} |
||||
|
|
||||
|
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 = rateLimitsLabelTranslationMap.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) { |
||||
|
if ($event) { |
||||
|
$event.stopPropagation(); |
||||
|
} |
||||
|
const title = rateLimitsDialogTitleTranslationMap.get(this.type); |
||||
|
this.dialog.open<RateLimitsDetailsDialogComponent, RateLimitsDetailsDialogData, |
||||
|
string>(RateLimitsDetailsDialogComponent, { |
||||
|
disableClose: true, |
||||
|
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], |
||||
|
data: { |
||||
|
rateLimits: this.modelValue, |
||||
|
title, |
||||
|
readonly: this.disabled |
||||
|
} |
||||
|
}).afterClosed().subscribe((result) => { |
||||
|
if (isDefined(result)) { |
||||
|
this.modelValue = result; |
||||
|
this.updateModel(); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private updateRateLimitsInfo() { |
||||
|
this.rateLimitsFormGroup.patchValue( |
||||
|
{ |
||||
|
rateLimits: stringToRateLimitsArray(this.modelValue) |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
private updateModel() { |
||||
|
this.updateRateLimitsInfo(); |
||||
|
this.propagateChange(this.modelValue); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,125 @@ |
|||||
|
///
|
||||
|
/// 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', |
||||
|
TENANT_ENTITY_EXPORT_RATE_LIMIT = 'TENANT_ENTITY_EXPORT_RATE_LIMIT', |
||||
|
TENANT_ENTITY_IMPORT_RATE_LIMIT = 'TENANT_ENTITY_IMPORT_RATE_LIMIT' |
||||
|
} |
||||
|
|
||||
|
export const rateLimitsLabelTranslationMap = new Map<RateLimitsType, string>( |
||||
|
[ |
||||
|
[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'], |
||||
|
[RateLimitsType.TENANT_ENTITY_EXPORT_RATE_LIMIT, 'tenant-profile.tenant-entity-export-rate-limit'], |
||||
|
[RateLimitsType.TENANT_ENTITY_IMPORT_RATE_LIMIT, 'tenant-profile.tenant-entity-import-rate-limit'], |
||||
|
] |
||||
|
); |
||||
|
|
||||
|
export const rateLimitsDialogTitleTranslationMap = new Map<RateLimitsType, string>( |
||||
|
[ |
||||
|
[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-tenant-msg-rate-limit-title'], |
||||
|
[RateLimitsType.CUSTOMER_SERVER_REST_LIMITS_CONFIGURATION, 'tenant-profile.rate-limits.edit-customer-rest-limits-title'], |
||||
|
[RateLimitsType.WS_UPDATE_PER_SESSION_RATE_LIMIT, 'tenant-profile.rate-limits.edit-ws-limit-updates-per-session-title'], |
||||
|
[RateLimitsType.CASSANDRA_QUERY_TENANT_RATE_LIMITS_CONFIGURATION, 'tenant-profile.rate-limits.edit-cassandra-tenant-limits-configuration-title'], |
||||
|
[RateLimitsType.TENANT_ENTITY_EXPORT_RATE_LIMIT, 'tenant-profile.rate-limits.edit-tenant-entity-export-rate-limit-title'], |
||||
|
[RateLimitsType.TENANT_ENTITY_IMPORT_RATE_LIMIT, 'tenant-profile.rate-limits.edit-tenant-entity-import-rate-limit-title'], |
||||
|
] |
||||
|
); |
||||
|
|
||||
|
export function stringToRateLimitsArray(rateLimits: string): Array<RateLimits> { |
||||
|
const result: Array<RateLimits> = []; |
||||
|
if (rateLimits?.length > 0) { |
||||
|
const rateLimitsArrays = rateLimits.split(','); |
||||
|
for (let i = 0; i < rateLimitsArrays.length; i++) { |
||||
|
const [value, time] = rateLimitsArrays[i].split(':'); |
||||
|
const rateLimitControl = { |
||||
|
value, |
||||
|
time |
||||
|
}; |
||||
|
result.push(rateLimitControl); |
||||
|
} |
||||
|
} |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
export function rateLimitsArrayToString(rateLimits: Array<RateLimits>): 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<RateLimits>): 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(' <span class="disabled">' + butLessThanText + '</span> '); |
||||
|
} 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 ? '' : ','; |
||||
|
return `<span class="tb-rate-limits-value">${value}</span>
|
||||
|
<span>${operation}</span> |
||||
|
<span class="tb-rate-limits-value"> ${time}</span> |
||||
|
<span>${seconds}${comma}</span><br>`;
|
||||
|
} |
||||
Loading…
Reference in new issue