16 changed files with 1018 additions and 80 deletions
@ -0,0 +1,52 @@ |
|||
<!-- |
|||
|
|||
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" style="width: 800px;"> |
|||
<mat-toolbar color="primary"> |
|||
<h2 translate>{{ title }}</h2> |
|||
<span fxFlex></span> |
|||
<button mat-icon-button |
|||
(click)="cancel()" |
|||
type="button"> |
|||
<mat-icon class="material-icons">close</mat-icon> |
|||
</button> |
|||
</mat-toolbar> |
|||
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> |
|||
</mat-progress-bar> |
|||
<div mat-dialog-content> |
|||
<fieldset [disabled]="isLoading$ | async"> |
|||
<div fxFlex fxLayout="column"> |
|||
<tb-rate-limits-list |
|||
formControlName="rateLimits" |
|||
[disabled]="isLoading$ | async"> |
|||
</tb-rate-limits-list> |
|||
</div> |
|||
</fieldset> |
|||
</div> |
|||
<div mat-dialog-actions fxLayoutAlign="end center"> |
|||
<button mat-button color="primary" |
|||
type="button" |
|||
[disabled]="(isLoading$ | async)" |
|||
(click)="cancel()" cdkFocusInitial> |
|||
{{ 'action.cancel' | translate }} |
|||
</button> |
|||
<button *ngIf="!data.readonly" mat-raised-button color="primary" (click)="save()" |
|||
[disabled]="(isLoading$ | async) || editDetailsFormGroup.invalid || !editDetailsFormGroup.dirty"> |
|||
{{ 'action.save' | translate }} |
|||
</button> |
|||
</div> |
|||
</form> |
|||
@ -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<RateLimitsDetailsDialogComponent, any> |
|||
implements OnInit, ErrorStateMatcher { |
|||
|
|||
editDetailsFormGroup: FormGroup; |
|||
|
|||
submitted: boolean = false; |
|||
|
|||
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, |
|||
@SkipSelf() private errorStateMatcher: ErrorStateMatcher, |
|||
public dialogRef: MatDialogRef<RateLimitsDetailsDialogComponent, any>, |
|||
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); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,61 @@ |
|||
<!-- |
|||
|
|||
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 fxFlex fxLayout="column" [formGroup]="rateLimitsListFormGroup"> |
|||
<div fxFlex fxLayout="column" [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-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-form-field> |
|||
<button mat-icon-button type="button" color="primary" |
|||
*ngIf="!rateLimitsFormArray().disabled" |
|||
(click)="removeRateLimits($index)" |
|||
[matTooltip]="'tenant-profile.rate-limits.remove-limit' | translate" matTooltipPosition="above"> |
|||
<mat-icon>remove_circle_outline</mat-icon> |
|||
</button> |
|||
</div> |
|||
</div> |
|||
<div> |
|||
<button mat-raised-button color="primary" *ngIf="!rateLimitsFormArray().disabled" |
|||
(click)="addRateLimits()" |
|||
[matTooltip]="'tenant-profile.rate-limits.add-limit' | translate" matTooltipPosition="above"> |
|||
<span translate>tenant-profile.rate-limits.add-limit</span> |
|||
</button> |
|||
</div> |
|||
<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> |
|||
<div> |
|||
<tb-rate-limits-text [formControl]="rateLimitsControl"></tb-rate-limits-text> |
|||
</div> |
|||
</div> |
|||
</section> |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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<FormGroup> = []; |
|||
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<RateLimits>) { |
|||
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}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
<!-- |
|||
|
|||
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, required: requiredClass, nowrap: nowrap}" |
|||
[innerHTML]="rateLimitsText"></div> |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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<RateLimits>): void { |
|||
this.updateText(value); |
|||
} |
|||
|
|||
private updateText(value: Array<RateLimits>) { |
|||
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; |
|||
} |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
<!-- |
|||
|
|||
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, 'edit')" |
|||
[matTooltip]="'tenant-profile.rate-limits.edit-limit' | translate" matTooltipPosition="above"> |
|||
<tb-rate-limits-text formControlName="rateLimits"></tb-rate-limits-text> |
|||
</div> |
|||
</fieldset> |
|||
<div style="padding-top: 12px"> |
|||
<button mat-icon-button color="primary" type="button" *ngIf="!disabled" |
|||
(click)="onClick($event, 'add')" |
|||
[matTooltip]="'tenant-profile.rate-limits.add-limit' | translate" matTooltipPosition="above"> |
|||
<mat-icon>add_circle_outline</mat-icon> |
|||
</button> |
|||
</div> |
|||
</div> |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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, RateLimitsDetailsDialogData, |
|||
string>(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); |
|||
} |
|||
|
|||
} |
|||
@ -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, 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'], |
|||
] |
|||
); |
|||
|
|||
export const editRateLimitTranslationMap = 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-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, string>( |
|||
[ |
|||
[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<RateLimits> { |
|||
const result: Array<RateLimits> = []; |
|||
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<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 ? '' : ','; |
|||
const result = `<span class="tb-rate-limits-value">${value}</span>
|
|||
<span>${operation}</span> |
|||
<span class="tb-rate-limits-value"> ${time}</span> |
|||
<span>${seconds}${comma}</span><br>`;
|
|||
return result; |
|||
} |
|||
Loading…
Reference in new issue