Browse Source

Merge pull request #6979 from deaflynx/tenant-profile-rate-limits

[3.4.1] UI: Improvement of Rate limits view in Tenant profiles
pull/7084/head
Igor Kulikov 4 years ago
committed by GitHub
parent
commit
76768de6c7
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 16
      ui-ngx/src/app/modules/home/components/home-components.module.ts
  2. 122
      ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html
  3. 24
      ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts
  4. 43
      ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-details-dialog.component.html
  5. 59
      ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-details-dialog.component.ts
  6. 66
      ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-list.component.html
  7. 57
      ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-list.component.scss
  8. 152
      ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-list.component.ts
  9. 18
      ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-text.component.html
  10. 41
      ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-text.component.scss
  11. 55
      ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-text.component.ts
  12. 33
      ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.component.html
  13. 35
      ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.component.scss
  14. 149
      ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.component.ts
  15. 125
      ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.models.ts
  16. 38
      ui-ngx/src/assets/locale/locale.constant-en_US.json

16
ui-ngx/src/app/modules/home/components/home-components.module.ts

@ -166,6 +166,10 @@ import { EntityTypesVersionLoadComponent } from '@home/components/vc/entity-type
import { ComplexVersionLoadComponent } from '@home/components/vc/complex-version-load.component';
import { RemoveOtherEntitiesConfirmComponent } from '@home/components/vc/remove-other-entities-confirm.component';
import { AutoCommitSettingsComponent } from '@home/components/vc/auto-commit-settings.component';
import { RateLimitsListComponent } from '@home/components/profile/tenant/rate-limits/rate-limits-list.component';
import { RateLimitsComponent } from '@home/components/profile/tenant/rate-limits/rate-limits.component';
import { RateLimitsTextComponent } from '@home/components/profile/tenant/rate-limits/rate-limits-text.component';
import { RateLimitsDetailsDialogComponent } from '@home/components/profile/tenant/rate-limits/rate-limits-details-dialog.component';
@NgModule({
declarations:
@ -302,7 +306,11 @@ import { AutoCommitSettingsComponent } from '@home/components/vc/auto-commit-set
EntityTypesVersionLoadComponent,
ComplexVersionLoadComponent,
RemoveOtherEntitiesConfirmComponent,
AutoCommitSettingsComponent
AutoCommitSettingsComponent,
RateLimitsListComponent,
RateLimitsComponent,
RateLimitsTextComponent,
RateLimitsDetailsDialogComponent
],
imports: [
CommonModule,
@ -432,7 +440,11 @@ import { AutoCommitSettingsComponent } from '@home/components/vc/auto-commit-set
EntityTypesVersionLoadComponent,
ComplexVersionLoadComponent,
RemoveOtherEntitiesConfirmComponent,
AutoCommitSettingsComponent
AutoCommitSettingsComponent,
RateLimitsListComponent,
RateLimitsComponent,
RateLimitsTextComponent,
RateLimitsDetailsDialogComponent
],
providers: [
WidgetComponentService,

122
ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html

@ -418,38 +418,24 @@
</fieldset>
<fieldset class="fields-group">
<legend class="group-title">Rate limits</legend>
<legend class="group-title">
{{ 'tenant-profile.rate-limits.rate-limits' | translate }}
</legend>
<div fxFlex fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="16px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>tenant-profile.transport-tenant-msg-rate-limit</mat-label>
<input matInput formControlName="transportTenantMsgRateLimit">
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('transportTenantMsgRateLimit').hasError('pattern')">
{{ 'tenant-profile.incorrect-pattern-for-rate-limits' | translate}}
</mat-error>
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>tenant-profile.transport-device-msg-rate-limit</mat-label>
<input matInput formControlName="transportDeviceMsgRateLimit">
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('transportDeviceMsgRateLimit').hasError('pattern')">
{{ 'tenant-profile.incorrect-pattern-for-rate-limits' | translate}}
</mat-error>
</mat-form-field>
<tb-rate-limits fxFlex formControlName="transportTenantMsgRateLimit"
[type]="rateLimitsType.TENANT_MESSAGES">
</tb-rate-limits>
<tb-rate-limits fxFlex formControlName="transportDeviceMsgRateLimit"
[type]="rateLimitsType.DEVICE_MESSAGES">
</tb-rate-limits>
</div>
<div fxFlex fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="16px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>tenant-profile.transport-tenant-telemetry-msg-rate-limit</mat-label>
<input matInput formControlName="transportTenantTelemetryMsgRateLimit">
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('transportTenantTelemetryMsgRateLimit').hasError('pattern')">
{{ 'tenant-profile.incorrect-pattern-for-rate-limits' | translate}}
</mat-error>
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>tenant-profile.transport-device-telemetry-msg-rate-limit</mat-label>
<input matInput formControlName="transportDeviceTelemetryMsgRateLimit">
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('transportTenantTelemetryMsgRateLimit').hasError('pattern')">
{{ 'tenant-profile.incorrect-pattern-for-rate-limits' | translate}}
</mat-error>
</mat-form-field>
<tb-rate-limits fxFlex formControlName="transportTenantTelemetryMsgRateLimit"
[type]="rateLimitsType.TENANT_TELEMETRY_MESSAGES">
</tb-rate-limits>
<tb-rate-limits fxFlex formControlName="transportDeviceTelemetryMsgRateLimit"
[type]="rateLimitsType.DEVICE_TELEMETRY_MESSAGES">
</tb-rate-limits>
</div>
<mat-expansion-panel class="configuration-panel">
<mat-expansion-panel-header>
@ -459,66 +445,36 @@
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div fxFlex fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="16px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>tenant-profile.transport-tenant-telemetry-data-points-rate-limit</mat-label>
<input matInput formControlName="transportTenantTelemetryDataPointsRateLimit">
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('transportTenantTelemetryDataPointsRateLimit').hasError('pattern')">
{{ 'tenant-profile.incorrect-pattern-for-rate-limits' | translate}}
</mat-error>
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>tenant-profile.transport-device-telemetry-data-points-rate-limit</mat-label>
<input matInput formControlName="transportDeviceTelemetryDataPointsRateLimit">
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('transportDeviceTelemetryDataPointsRateLimit').hasError('pattern')">
{{ 'tenant-profile.incorrect-pattern-for-rate-limits' | translate}}
</mat-error>
</mat-form-field>
<tb-rate-limits fxFlex formControlName="transportTenantTelemetryDataPointsRateLimit"
[type]="rateLimitsType.TENANT_TELEMETRY_DATA_POINTS">
</tb-rate-limits>
<tb-rate-limits fxFlex formControlName="transportDeviceTelemetryDataPointsRateLimit"
[type]="rateLimitsType.DEVICE_TELEMETRY_DATA_POINTS">
</tb-rate-limits>
</div>
<div fxFlex fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="16px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>tenant-profile.tenant-rest-limits</mat-label>
<input matInput
formControlName="tenantServerRestLimitsConfiguration"
type="text">
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('tenantServerRestLimitsConfiguration').hasError('pattern')">
{{ 'tenant-profile.incorrect-pattern-for-rate-limits' | translate}}
</mat-error>
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>tenant-profile.customer-rest-limits</mat-label>
<input matInput
formControlName="customerServerRestLimitsConfiguration"
type="text">
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('customerServerRestLimitsConfiguration').hasError('pattern')">
{{ 'tenant-profile.incorrect-pattern-for-rate-limits' | translate}}
</mat-error>
</mat-form-field>
<tb-rate-limits fxFlex formControlName="tenantServerRestLimitsConfiguration"
[type]="rateLimitsType.TENANT_SERVER_REST_LIMITS_CONFIGURATION">
</tb-rate-limits>
<tb-rate-limits fxFlex formControlName="customerServerRestLimitsConfiguration"
[type]="rateLimitsType.CUSTOMER_SERVER_REST_LIMITS_CONFIGURATION">
</tb-rate-limits>
</div>
<div fxFlex fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="16px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>tenant-profile.tenant-entity-export-rate-limit</mat-label>
<input matInput formControlName="tenantEntityExportRateLimit">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>tenant-profile.tenant-entity-import-rate-limit</mat-label>
<input matInput formControlName="tenantEntityImportRateLimit">
</mat-form-field>
<tb-rate-limits fxFlex formControlName="tenantEntityExportRateLimit"
[type]="rateLimitsType.TENANT_ENTITY_EXPORT_RATE_LIMIT">
</tb-rate-limits>
<tb-rate-limits fxFlex formControlName="tenantEntityImportRateLimit"
[type]="rateLimitsType.TENANT_ENTITY_IMPORT_RATE_LIMIT">
</tb-rate-limits>
</div>
<div fxFlex fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="16px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>tenant-profile.ws-limit-updates-per-session</mat-label>
<input matInput type="text" formControlName="wsUpdatesPerSessionRateLimit">
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('wsUpdatesPerSessionRateLimit').hasError('pattern')">
{{ 'tenant-profile.incorrect-pattern-for-rate-limits' | translate}}
</mat-error>
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>tenant-profile.cassandra-tenant-limits-configuration</mat-label>
<input matInput type="text" formControlName="cassandraQueryTenantRateLimitsConfiguration">
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('cassandraQueryTenantRateLimitsConfiguration').hasError('pattern')">
{{ 'tenant-profile.incorrect-pattern-for-rate-limits' | translate}}
</mat-error>
</mat-form-field>
<tb-rate-limits fxFlex formControlName="wsUpdatesPerSessionRateLimit"
[type]="rateLimitsType.WS_UPDATE_PER_SESSION_RATE_LIMIT">
</tb-rate-limits>
<tb-rate-limits fxFlex formControlName="cassandraQueryTenantRateLimitsConfiguration"
[type]="rateLimitsType.CASSANDRA_QUERY_TENANT_RATE_LIMITS_CONFIGURATION">
</tb-rate-limits>
</div>
</ng-template>
</mat-expansion-panel>

24
ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts

@ -21,6 +21,7 @@ import { AppState } from '@app/core/core.state';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { DefaultTenantProfileConfiguration, TenantProfileConfiguration } from '@shared/models/tenant.model';
import { isDefinedAndNotNull } from '@core/utils';
import { RateLimitsType } from './rate-limits/rate-limits.models';
@Component({
selector: 'tb-default-tenant-profile-configuration',
@ -35,7 +36,6 @@ import { isDefinedAndNotNull } from '@core/utils';
export class DefaultTenantProfileConfigurationComponent implements ControlValueAccessor, OnInit {
defaultTenantProfileConfigurationFormGroup: FormGroup;
rateLimitsPattern = '([1-9]\\d*:[1-9]\\d*)(,[1-9]\\d*:[1-9]\\d*)*';
private requiredValue: boolean;
get required(): boolean {
@ -49,6 +49,8 @@ export class DefaultTenantProfileConfigurationComponent implements ControlValueA
@Input()
disabled: boolean;
rateLimitsType = RateLimitsType;
private propagateChange = (v: any) => { };
constructor(private store: Store<AppState>,
@ -62,12 +64,12 @@ export class DefaultTenantProfileConfigurationComponent implements ControlValueA
maxRuleChains: [null, [Validators.required, Validators.min(0)]],
maxResourcesInBytes: [null, [Validators.required, Validators.min(0)]],
maxOtaPackagesInBytes: [null, [Validators.required, Validators.min(0)]],
transportTenantMsgRateLimit: [null, [Validators.pattern(this.rateLimitsPattern)]],
transportTenantTelemetryMsgRateLimit: [null, [Validators.pattern(this.rateLimitsPattern)]],
transportTenantTelemetryDataPointsRateLimit: [null, [Validators.pattern(this.rateLimitsPattern)]],
transportDeviceMsgRateLimit: [null, [Validators.pattern(this.rateLimitsPattern)]],
transportDeviceTelemetryMsgRateLimit: [null, [Validators.pattern(this.rateLimitsPattern)]],
transportDeviceTelemetryDataPointsRateLimit: [null, [Validators.pattern(this.rateLimitsPattern)]],
transportTenantMsgRateLimit: [null, []],
transportTenantTelemetryMsgRateLimit: [null, []],
transportTenantTelemetryDataPointsRateLimit: [null, []],
transportDeviceMsgRateLimit: [null, []],
transportDeviceTelemetryMsgRateLimit: [null, []],
transportDeviceTelemetryDataPointsRateLimit: [null, []],
tenantEntityExportRateLimit: [null, []],
tenantEntityImportRateLimit: [null, []],
maxTransportMessages: [null, [Validators.required, Validators.min(0)]],
@ -82,8 +84,8 @@ export class DefaultTenantProfileConfigurationComponent implements ControlValueA
defaultStorageTtlDays: [null, [Validators.required, Validators.min(0)]],
alarmsTtlDays: [null, [Validators.required, Validators.min(0)]],
rpcTtlDays: [null, [Validators.required, Validators.min(0)]],
tenantServerRestLimitsConfiguration: [null, [Validators.pattern(this.rateLimitsPattern)]],
customerServerRestLimitsConfiguration: [null, [Validators.pattern(this.rateLimitsPattern)]],
tenantServerRestLimitsConfiguration: [null, []],
customerServerRestLimitsConfiguration: [null, []],
maxWsSessionsPerTenant: [null, [Validators.min(0)]],
maxWsSessionsPerCustomer: [null, [Validators.min(0)]],
maxWsSessionsPerRegularUser: [null, [Validators.min(0)]],
@ -93,8 +95,8 @@ export class DefaultTenantProfileConfigurationComponent implements ControlValueA
maxWsSubscriptionsPerCustomer: [null, [Validators.min(0)]],
maxWsSubscriptionsPerRegularUser: [null, [Validators.min(0)]],
maxWsSubscriptionsPerPublicUser: [null, [Validators.min(0)]],
wsUpdatesPerSessionRateLimit: [null, [Validators.pattern(this.rateLimitsPattern)]],
cassandraQueryTenantRateLimitsConfiguration: [null, [Validators.pattern(this.rateLimitsPattern)]]
wsUpdatesPerSessionRateLimit: [null, []],
cassandraQueryTenantRateLimitsConfiguration: [null, []]
});
this.defaultTenantProfileConfigurationFormGroup.valueChanges.subscribe(() => {
this.updateModel();

43
ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-details-dialog.component.html

@ -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>

59
ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-details-dialog.component.ts

@ -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);
}
}

66
ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-list.component.html

@ -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>

57
ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-list.component.scss

@ -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%;
}
}
}

152
ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-list.component.ts

@ -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();
}
}

18
ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-text.component.html

@ -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>

41
ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-text.component.scss

@ -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;
}
}
}

55
ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-text.component.ts

@ -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');
}
}
}

33
ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.component.html

@ -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>

35
ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.component.scss

@ -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;
}
}

149
ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.component.ts

@ -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);
}
}

125
ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.models.ts

@ -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>`;
}

38
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -3191,7 +3191,43 @@
"ws-limit-max-subscriptions-per-customer": "Subscriptions per customer maximum number",
"ws-limit-max-subscriptions-per-regular-user": "Subscriptions per regular user maximum number",
"ws-limit-max-subscriptions-per-public-user": "Subscriptions per public user maximum number",
"ws-limit-updates-per-session": "WS updates per session"
"ws-limit-updates-per-session": "WS updates per session",
"rate-limits": {
"add-limit": "Add limit",
"advanced-settings": "Advanced settings",
"edit-limit": "Edit limit",
"but-less-than": "but less than",
"edit-transport-tenant-msg-title": "Edit transport tenant messages rate limits",
"edit-transport-tenant-telemetry-msg-title": "Edit transport tenant telemetry messages rate limits",
"edit-transport-tenant-telemetry-data-points-title": "Edit transport tenant telemetry data points rate limits",
"edit-transport-device-msg-title": "Edit transport device messages rate limits",
"edit-transport-device-telemetry-msg-title": "Edit transport device telemetry messages rate limits",
"edit-transport-device-telemetry-data-points-title": "Edit transport device telemetry data points rate limits",
"edit-transport-tenant-msg-rate-limit-title": "Edit transport tenant messages rate limits",
"edit-customer-rest-limits-title": "Edit REST requests for customer rate limits",
"edit-ws-limit-updates-per-session-title": "Edit WS updates per session rate limits",
"edit-cassandra-tenant-limits-configuration-title": "Edit Cassandra query for tenant rate limits",
"edit-tenant-entity-export-rate-limit-title": "Edit entity version creation rate limits",
"edit-tenant-entity-import-rate-limit-title": "Edit entity version load rate limits",
"messages-per": "messages per",
"not-set": "Not set",
"number-of-messages": "Number of messages",
"number-of-messages-required": "Number of messages is required.",
"number-of-messages-min": "Minimum value is 1.",
"preview": "Preview",
"per-seconds": "Per seconds",
"per-seconds-required": "Time rate is required.",
"per-seconds-min": "Minimum value is 1.",
"rate-limits": "Rate limits",
"remove-limit": "Remove limit",
"transport-tenant-msg": "Transport tenant messages",
"transport-tenant-telemetry-msg": "Transport tenant telemetry messages",
"transport-tenant-telemetry-data-points": "Transport tenant telemetry data points",
"transport-device-msg": "Transport device messages",
"transport-device-telemetry-msg": "Transport device telemetry messages",
"transport-device-telemetry-data-points": "Transport device telemetry data points",
"sec": "sec"
}
},
"timeinterval": {
"seconds-interval": "{ seconds, plural, 1 {1 second} other {# seconds} }",

Loading…
Cancel
Save