diff --git a/ui-ngx/src/app/core/auth/auth.models.ts b/ui-ngx/src/app/core/auth/auth.models.ts index 55af791156..1944c693ac 100644 --- a/ui-ngx/src/app/core/auth/auth.models.ts +++ b/ui-ngx/src/app/core/auth/auth.models.ts @@ -28,6 +28,8 @@ export interface SysParamsState { userSettings: UserSettings; maxResourceSize: number; maxDebugModeDurationMinutes: number; + maxDataPointsPerRollingArg: number; + maxArgumentsPerCF: number; ruleChainDebugPerTenantLimitsConfiguration?: string; } diff --git a/ui-ngx/src/app/core/auth/auth.reducer.ts b/ui-ngx/src/app/core/auth/auth.reducer.ts index a460cf35bb..3ecf70074c 100644 --- a/ui-ngx/src/app/core/auth/auth.reducer.ts +++ b/ui-ngx/src/app/core/auth/auth.reducer.ts @@ -31,6 +31,8 @@ const emptyUserAuthState: AuthPayload = { persistDeviceStateToTelemetry: false, mobileQrEnabled: false, maxResourceSize: 0, + maxArgumentsPerCF: 0, + maxDataPointsPerRollingArg: 0, maxDebugModeDurationMinutes: 0, userSettings: initialUserSettings }; diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.html index 96dff1c1be..6bab01f976 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.html @@ -119,9 +119,22 @@ } -
- + @if (maxArgumentsPerCF && argumentsFormArray.length >= maxArgumentsPerCF) { +
+ warning + {{ 'calculated-fields.hint.max-args' | translate }} +
+ }
diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.scss b/ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.scss index c4efe323c6..877a749afa 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.scss +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.scss @@ -30,6 +30,12 @@ } } + .max-args-warning { + .mat-icon { + color: #FAA405; + } + } + .tb-form-table-row-cell-buttons { --mat-badge-legacy-small-size-container-size: 8px; --mat-badge-small-size-container-overlap-offset: -5px; diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.ts index 2537f34059..e3b19503af 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.ts @@ -55,6 +55,9 @@ import { TbPopoverComponent } from '@shared/components/popover.component'; import { TbTableDatasource } from '@shared/components/table/table-datasource.abstract'; import { EntityService } from '@core/http/entity.service'; import { MatSort } from '@angular/material/sort'; +import { getCurrentAuthState } from '@core/auth/auth.selectors'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; @Component({ selector: 'tb-calculated-field-arguments-table', @@ -93,6 +96,7 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces readonly ArgumentEntityType = ArgumentEntityType; readonly ArgumentType = ArgumentType; readonly CalculatedFieldType = CalculatedFieldType; + readonly maxArgumentsPerCF = getCurrentAuthState(this.store).maxArgumentsPerCF; private popoverComponent: TbPopoverComponent; private propagateChange: (argumentsObj: Record) => void = () => {}; @@ -105,6 +109,7 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces private renderer: Renderer2, private entityService: EntityService, private destroyRef: DestroyRef, + private store: Store ) { this.argumentsFormArray.valueChanges.pipe(takeUntilDestroyed()).subscribe(value => { this.updateEntityNameMap(value); diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.html index 0695b9d2b9..7e0f87a12e 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.html @@ -78,7 +78,7 @@
{{ 'calculated-fields.expression' | translate }}
@if (fieldFormGroup.get('type').value === CalculatedFieldType.SIMPLE) { - + @if (configFormGroup.get('expressionSIMPLE').errors && configFormGroup.get('expressionSIMPLE').touched) { @if (configFormGroup.get('expressionSIMPLE').hasError('required')) { @@ -89,6 +89,8 @@ {{ 'calculated-fields.hint.expression-max-length' | translate }} } + } @else { + {{ 'calculated-fields.hint.expression' | translate }} } } @else { diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.scss b/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.scss index 900758761c..0e994ed825 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.scss @@ -40,7 +40,7 @@ &-key { color: #C52F00; } - &-ts, &-time-window, &-values, &-value { + &-ts, &-time-window, &-values, &-value, &-func { color: #7214D0; } &-start-ts, &-end-ts, &-limit { diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/panel/calculated-field-argument-panel.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/components/panel/calculated-field-argument-panel.component.html index 72bc85a48f..566a433545 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/panel/calculated-field-argument-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/panel/calculated-field-argument-panel.component.html @@ -166,10 +166,19 @@ formControlName="timeWindow" /> -
-
{{ 'calculated-fields.limit' | translate }}
- -
+ @if (maxDataPointsPerRollingArg) { +
+
{{ 'calculated-fields.limit' | translate }}
+
+ + + + + + +
+
+ } } diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/panel/calculated-field-argument-panel.component.scss b/ui-ngx/src/app/modules/home/components/calculated-fields/components/panel/calculated-field-argument-panel.component.scss index 1f7c084d08..773489ee60 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/panel/calculated-field-argument-panel.component.scss +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/panel/calculated-field-argument-panel.component.scss @@ -28,9 +28,7 @@ $panel-width: 520px; min-width: 120px; } } -} -:host ::ng-deep { .limit-field-row { @media screen and (max-width: $panel-width) { display: flex; @@ -42,7 +40,9 @@ $panel-width: 520px; } } } +} +:host ::ng-deep { .time-interval-field { .advanced-input { flex-direction: column; diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/panel/calculated-field-argument-panel.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/panel/calculated-field-argument-panel.component.ts index 93b8f897ce..9d9614ba28 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/panel/calculated-field-argument-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/panel/calculated-field-argument-panel.component.ts @@ -38,7 +38,9 @@ import { EntityFilter } from '@shared/models/query/query.models'; import { AliasFilterType } from '@shared/models/alias.models'; import { merge } from 'rxjs'; import { MINUTE } from '@shared/models/time/time.models'; -import { TimeService } from '@core/services/time.service'; +import { getCurrentAuthState } from '@core/auth/auth.selectors'; +import { AppState } from '@core/core.state'; +import { Store } from '@ngrx/store'; @Component({ selector: 'tb-calculated-field-argument-panel', @@ -58,7 +60,8 @@ export class CalculatedFieldArgumentPanelComponent implements OnInit { argumentsDataApplied = output<{ value: CalculatedFieldArgumentValue, index: number }>(); - readonly defaultLimit = Math.max(this.timeService.getMinDatapointsLimit(), Math.floor(this.timeService.getMaxDatapointsLimit() / 10)); + readonly maxDataPointsPerRollingArg = getCurrentAuthState(this.store).maxDataPointsPerRollingArg; + readonly defaultLimit = Math.floor(this.maxDataPointsPerRollingArg / 10); argumentFormGroup = this.fb.group({ argumentName: ['', [Validators.required, this.uniqNameRequired(), Validators.pattern(charsWithNumRegex), Validators.maxLength(255)]], @@ -72,7 +75,7 @@ export class CalculatedFieldArgumentPanelComponent implements OnInit { scope: [{ value: AttributeScope.SERVER_SCOPE, disabled: true }, [Validators.required]], }), defaultValue: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], - limit: [this.defaultLimit], + limit: [{ value: this.defaultLimit, disabled: !this.maxDataPointsPerRollingArg }], timeWindow: [MINUTE * 15], }); @@ -96,7 +99,7 @@ export class CalculatedFieldArgumentPanelComponent implements OnInit { private fb: FormBuilder, private cd: ChangeDetectorRef, private popover: TbPopoverComponent, - private timeService: TimeService + private store: Store ) { this.observeEntityFilterChanges(); this.observeEntityTypeChanges() diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/test-dialog/calculated-field-script-test-dialog.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/components/test-dialog/calculated-field-script-test-dialog.component.html index 57234aa692..cda99f6c09 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/test-dialog/calculated-field-script-test-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/test-dialog/calculated-field-script-test-dialog.component.html @@ -45,7 +45,7 @@ [scriptLanguage]="ScriptLanguage.TBEL" [editorCompleter]="data.argumentsEditorCompleter" resultType="object" - helpId="calculated-field/test-expression_fn" + helpId="calculated-field/expression_fn" /> diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/test-dialog/calculated-field-script-test-dialog.component.scss b/ui-ngx/src/app/modules/home/components/calculated-fields/components/test-dialog/calculated-field-script-test-dialog.component.scss index 298ab64e26..2187b47e0d 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/test-dialog/calculated-field-script-test-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/test-dialog/calculated-field-script-test-dialog.component.scss @@ -78,7 +78,7 @@ &-key { color: #C52F00; } - &-ts, &-time-window, &-values, &-value { + &-ts, &-time-window, &-values, &-value, &-func { color: #7214D0; } &-start-ts, &-end-ts, &-limit { diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html index 9f1839afec..64d5039914 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html @@ -229,6 +229,92 @@ +
+ + {{ 'tenant-profile.calculated-fields' | translate }} tenant-profile.unlimited + +
+ + tenant-profile.max-calculated-fields + + + {{ 'tenant-profile.max-calculated-fields-required' | translate}} + + + {{ 'tenant-profile.max-calculated-fields-range' | translate}} + + + + + tenant-profile.max-data-points-per-rolling-arg + + + {{ 'tenant-profile.max-data-points-per-rolling-arg-required' | translate}} + + + {{ 'tenant-profile.max-data-points-per-rolling-arg-range' | translate}} + + + +
+
+ + tenant-profile.max-arguments-per-cf + + + {{ 'tenant-profile.max-arguments-per-cf-required' | translate}} + + + {{ 'tenant-profile.max-arguments-per-cf-range' | translate}} + + + +
+
+ + + + tenant-profile.advanced-settings + + + +
+ + tenant-profile.max-state-size + + + {{ 'tenant-profile.max-state-size-required' | translate}} + + + {{ 'tenant-profile.max-state-size-range' | translate}} + + + + + tenant-profile.max-value-argument-size + + + {{ 'tenant-profile.max-value-argument-size-required' | translate}} + + + {{ 'tenant-profile.max-value-argument-size-range' | translate}} + + + +
+
+
+
@@ -638,6 +724,12 @@ [type]="rateLimitsType.EDGE_UPLINK_MESSAGES_PER_EDGE_RATE_LIMIT"> +
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts index 4cce34c502..b1d6652e4a 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts @@ -118,7 +118,13 @@ export class DefaultTenantProfileConfigurationComponent implements ControlValueA edgeEventRateLimits: [null, []], edgeEventRateLimitsPerEdge: [null, []], edgeUplinkMessagesRateLimits: [null, []], - edgeUplinkMessagesRateLimitsPerEdge: [null, []] + edgeUplinkMessagesRateLimitsPerEdge: [null, []], + maxCalculatedFieldsPerEntity: [null, [Validators.required, Validators.min(0)]], + maxArgumentsPerCF: [null, [Validators.required, Validators.min(0)]], + maxDataPointsPerRollingArg: [null, [Validators.required, Validators.min(0)]], + maxStateSizeInKBytes: [null, [Validators.required, Validators.min(0)]], + calculatedFieldDebugEventsRateLimit: [null, []], + maxSingleValueArgumentSizeInKBytes: [null, [Validators.required, Validators.min(0)]], }); this.defaultTenantProfileConfigurationFormGroup.get('smsEnabled').valueChanges.pipe( diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.models.ts b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.models.ts index ab50c967bf..f09f950ee7 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.models.ts +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.models.ts @@ -45,7 +45,8 @@ export enum RateLimitsType { EDGE_EVENTS_RATE_LIMIT = 'EDGE_EVENTS_RATE_LIMIT', EDGE_EVENTS_PER_EDGE_RATE_LIMIT = 'EDGE_EVENTS_PER_EDGE_RATE_LIMIT', EDGE_UPLINK_MESSAGES_RATE_LIMIT = 'EDGE_UPLINK_MESSAGES_RATE_LIMIT', - EDGE_UPLINK_MESSAGES_PER_EDGE_RATE_LIMIT = 'EDGE_UPLINK_MESSAGES_PER_EDGE_RATE_LIMIT' + EDGE_UPLINK_MESSAGES_PER_EDGE_RATE_LIMIT = 'EDGE_UPLINK_MESSAGES_PER_EDGE_RATE_LIMIT', + CALCULATED_FIELD_DEBUG_EVENT_RATE_LIMIT = 'CALCULATED_FIELD_DEBUG_EVENT_RATE_LIMIT', } export const rateLimitsLabelTranslationMap = new Map( @@ -74,6 +75,7 @@ export const rateLimitsLabelTranslationMap = new Map( [RateLimitsType.EDGE_EVENTS_PER_EDGE_RATE_LIMIT, 'tenant-profile.rate-limits.edge-events-per-edge-rate-limit'], [RateLimitsType.EDGE_UPLINK_MESSAGES_RATE_LIMIT, 'tenant-profile.rate-limits.edge-uplink-messages-rate-limit'], [RateLimitsType.EDGE_UPLINK_MESSAGES_PER_EDGE_RATE_LIMIT, 'tenant-profile.rate-limits.edge-uplink-messages-per-edge-rate-limit'], + [RateLimitsType.CALCULATED_FIELD_DEBUG_EVENT_RATE_LIMIT, 'tenant-profile.rate-limits.calculated-field-debug-event-rate-limit'], ] ); @@ -103,6 +105,7 @@ export const rateLimitsDialogTitleTranslationMap = new Map = + ['max', 'min', 'mean', 'std', 'median', 'count', 'last', 'first', 'sum'].map(funcName => ({ + token: 'tb.calculated-field-func', + regex: `\\b${funcName}\\b`, + next: 'no_regex' + })); + const calculatedFieldRollingArgumentValueHighlightRules: AceHighlightRules = { calculatedFieldRollingArgumentValue: [ dotOperatorHighlightRule, @@ -368,6 +523,7 @@ const calculatedFieldRollingArgumentValueHighlightRules: AceHighlightRules = { regex: /timeWindow/, next: 'calculatedFieldRollingArgumentTimeWindow' }, + ...calculatedFieldRollingArgumentValueFunctionsHighlightRules, endGroupHighlightRule ], } diff --git a/ui-ngx/src/app/shared/models/tenant.model.ts b/ui-ngx/src/app/shared/models/tenant.model.ts index 23c2d95762..38ff63e3c6 100644 --- a/ui-ngx/src/app/shared/models/tenant.model.ts +++ b/ui-ngx/src/app/shared/models/tenant.model.ts @@ -95,6 +95,13 @@ export interface DefaultTenantProfileConfiguration { rpcTtlDays: number; queueStatsTtlDays: number; ruleEngineExceptionsTtlDays: number; + + maxCalculatedFieldsPerEntity: number; + maxArgumentsPerCF: number; + maxDataPointsPerRollingArg: number; + maxStateSizeInKBytes: number; + maxSingleValueArgumentSizeInKBytes: number; + calculatedFieldDebugEventsRateLimit: string; } export type TenantProfileConfigurations = DefaultTenantProfileConfiguration; @@ -148,7 +155,13 @@ export function createTenantProfileConfiguration(type: TenantProfileType): Tenan alarmsTtlDays: 0, rpcTtlDays: 0, queueStatsTtlDays: 0, - ruleEngineExceptionsTtlDays: 0 + ruleEngineExceptionsTtlDays: 0, + maxCalculatedFieldsPerEntity: 0, + maxArgumentsPerCF: 0, + maxDataPointsPerRollingArg: 0, + maxStateSizeInKBytes: 0, + maxSingleValueArgumentSizeInKBytes: 0, + calculatedFieldDebugEventsRateLimit: '' }; configuration = {...defaultConfiguration, type: TenantProfileType.DEFAULT}; break; diff --git a/ui-ngx/src/assets/help/en_US/calculated-field/test-expression_fn.md b/ui-ngx/src/assets/help/en_US/calculated-field/test-expression_fn.md deleted file mode 100644 index f8173dc528..0000000000 --- a/ui-ngx/src/assets/help/en_US/calculated-field/test-expression_fn.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 9aff79102b..1163024e90 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1068,7 +1068,9 @@ "argument-name-pattern": "Argument name is invalid.", "argument-name-duplicate": "Argument with such name already exists.", "argument-name-max-length": "Argument name should be less than 256 characters.", - "argument-type-required": "Argument type is required." + "argument-type-required": "Argument type is required.", + "max-args": "Maximum number of arguments reached.", + "expression": "Default expression demonstrates how to transform a temperature from Fahrenheit to Celsius." } }, "confirm-on-exit": { @@ -5474,6 +5476,7 @@ "entities": "Entities", "rule-engine": "Rule Engine", "time-to-live": "Time-to-live", + "calculated-fields": "Calculated fields", "alarms-and-notifications": "Alarms and notifications", "ota-files-in-bytes": "Files", "ws-title": "WS", @@ -5526,6 +5529,21 @@ "tenant-entity-import-rate-limit": "Entity version load", "tenant-notification-request-rate-limit": "Notification requests", "tenant-notification-requests-per-rule-rate-limit": "Notification requests per notification rule", + "max-calculated-fields": "Maximum number of calculated fields per entity", + "max-calculated-fields-range": "Maximum number of calculated fields per entity can't be negative", + "max-calculated-fields-required": "Maximum number of calculated fields per entity is required", + "max-data-points-per-rolling-arg": "Maximum number of data points in a time series rolling arguments", + "max-data-points-per-rolling-arg-range": "Maximum number of data points in a time series rolling arguments can't be negative", + "max-data-points-per-rolling-arg-required": "Maximum number of data points in a time series rolling arguments is required", + "max-arguments-per-cf": "Maximum number of arguments per calculated field", + "max-arguments-per-cf-range": "Maximum number of arguments per calculated field can't be negative", + "max-arguments-per-cf-required": "Maximum number of arguments per calculated field is required", + "max-state-size": "Maximum size of the state in KB", + "max-state-size-range": "Maximum size of the state in KB can't be negative", + "max-state-size-required": "Maximum size of the state in KB is required", + "max-value-argument-size": "Maximum size of the single value argument in KB", + "max-value-argument-size-range": "Maximum size of the single value argument in KB can't be negative", + "max-value-argument-size-required": "Maximum size of the single value argument in KB is required", "max-transport-messages": "Transport messages maximum number", "max-transport-messages-required": "Transport messages maximum number is required.", "max-transport-messages-range": "Transport messages maximum number can't be negative", @@ -5597,6 +5615,8 @@ "advanced-settings": "Advanced settings", "edit-limit": "Edit limit", "but-less-than": "but less than", + "calculated-field-debug-event-rate-limit": "Calculated field debug events", + "edit-calculated-field-debug-event-rate-limit": "Edit calculated field debug events rate limits", "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",