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