diff --git a/ui-ngx/src/app/modules/common/modules-map.ts b/ui-ngx/src/app/modules/common/modules-map.ts
index a7d2e9ffae..512719c513 100644
--- a/ui-ngx/src/app/modules/common/modules-map.ts
+++ b/ui-ngx/src/app/modules/common/modules-map.ts
@@ -337,6 +337,7 @@ import * as RuleChainSelectComponent from '@shared/components/rule-chain/rule-ch
import { IModulesMap } from '@modules/common/modules-map.models';
import { TimezoneComponent } from '@shared/components/time/timezone.component';
import { TimezonePanelComponent } from '@shared/components/time/timezone-panel.component';
+import { DatapointsLimitComponent } from '@shared/components/time/datapoints-limit.component';
declare const System;
@@ -470,6 +471,7 @@ class ModulesMap implements IModulesMap {
'@shared/components/time/timezone-select.component': TimezoneSelectComponent,
'@shared/components/time/timezone.component': TimezoneComponent,
'@shared/components/time/timezone-panel.component': TimezonePanelComponent,
+ '@shared/components/time/datapoints-limit': DatapointsLimitComponent,
'@shared/components/value-input.component': ValueInputComponent,
'@shared/components/dashboard-autocomplete.component': DashboardAutocompleteComponent,
'@shared/components/entity/entity-subtype-autocomplete.component': EntitySubTypeAutocompleteComponent,
diff --git a/ui-ngx/src/app/shared/components/time/datapoints-limit.component.html b/ui-ngx/src/app/shared/components/time/datapoints-limit.component.html
new file mode 100644
index 0000000000..8e2a5ac459
--- /dev/null
+++ b/ui-ngx/src/app/shared/components/time/datapoints-limit.component.html
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
diff --git a/ui-ngx/src/app/shared/components/time/datapoints-limit.component.scss b/ui-ngx/src/app/shared/components/time/datapoints-limit.component.scss
new file mode 100644
index 0000000000..c2ffa0bb34
--- /dev/null
+++ b/ui-ngx/src/app/shared/components/time/datapoints-limit.component.scss
@@ -0,0 +1,38 @@
+/**
+ * Copyright © 2016-2024 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";
+
+.limit-slider-container {
+ width: 100%;
+ .limit-slider-value {
+ margin-left: 16px;
+ min-width: 25px;
+ max-width: 106px;
+ }
+ mat-form-field input[type=number] {
+ text-align: center;
+ }
+}
+
+@media #{$mat-gt-sm} {
+ .limit-slider-container {
+ > label {
+ margin-right: 16px;
+ width: min-content;
+ max-width: 40%;
+ }
+ }
+}
diff --git a/ui-ngx/src/app/shared/components/time/datapoints-limit.component.ts b/ui-ngx/src/app/shared/components/time/datapoints-limit.component.ts
new file mode 100644
index 0000000000..21aff5a647
--- /dev/null
+++ b/ui-ngx/src/app/shared/components/time/datapoints-limit.component.ts
@@ -0,0 +1,138 @@
+///
+/// Copyright © 2016-2024 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, FormBuilder, NG_VALUE_ACCESSOR, UntypedFormGroup, Validators } from '@angular/forms';
+import { coerceBooleanProperty } from '@angular/cdk/coercion';
+import { getTimezoneInfo } from '@shared/models/time/time.models';
+import { TimeService } from '@core/services/time.service';
+import { takeUntil } from 'rxjs/operators';
+import { Subject } from 'rxjs';
+
+@Component({
+ selector: 'tb-datapoints-limit',
+ templateUrl: './datapoints-limit.component.html',
+ styleUrls: ['./datapoints-limit.component.scss'],
+ providers: [{
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => DatapointsLimitComponent),
+ multi: true
+ }]
+})
+export class DatapointsLimitComponent implements ControlValueAccessor, OnInit, OnDestroy {
+
+ datapointsLimitFormGroup: UntypedFormGroup;
+
+ modelValue: number | null;
+
+ private requiredValue: boolean;
+ get required(): boolean {
+ return this.requiredValue;
+ }
+ @Input()
+ set required(value: boolean) {
+ const newVal = coerceBooleanProperty(value);
+ if (this.requiredValue !== newVal) {
+ this.requiredValue = newVal;
+ this.updateValidators();
+ }
+ }
+
+ @Input()
+ disabled: boolean;
+
+ private propagateChange = (v: any) => { };
+
+ private destroy$ = new Subject();
+
+ constructor(private fb: FormBuilder,
+ private timeService: TimeService) {
+ }
+
+ registerOnChange(fn: any): void {
+ this.propagateChange = fn;
+ }
+
+ registerOnTouched(fn: any): void {
+ }
+
+ ngOnInit() {
+ this.datapointsLimitFormGroup = this.fb.group({
+ limit: [null]
+ });
+ this.datapointsLimitFormGroup.get('limit').valueChanges.pipe(
+ takeUntil(this.destroy$)
+ ).subscribe((value) => {
+ this.updateView(value);
+ });
+ }
+
+ updateValidators() {
+ if (this.datapointsLimitFormGroup) {
+ this.datapointsLimitFormGroup.get('limit').setValidators(this.required
+ ? [Validators.required, Validators.min(this.minDatapointsLimit()),
+ Validators.max(this.maxDatapointsLimit())]
+ : []);
+ this.datapointsLimitFormGroup.get('limit').updateValueAndValidity();
+ }
+ }
+
+ setDisabledState(isDisabled: boolean): void {
+ this.disabled = isDisabled;
+ if (this.disabled) {
+ this.datapointsLimitFormGroup.disable({emitEvent: false});
+ } else {
+ this.datapointsLimitFormGroup.enable({emitEvent: false});
+ }
+ }
+
+ private checkLimit(limit?: number): number {
+ if (!limit || limit < this.minDatapointsLimit()) {
+ return this.minDatapointsLimit();
+ } else if (limit > this.maxDatapointsLimit()) {
+ return this.maxDatapointsLimit();
+ }
+ return limit;
+ }
+
+ writeValue(value: number | null): void {
+ this.modelValue = this.checkLimit(value);
+ this.datapointsLimitFormGroup.patchValue(
+ { limit: this.modelValue }, {emitEvent: false}
+ );
+ }
+
+ updateView(value: number | null) {
+ if (this.modelValue !== value) {
+ this.modelValue = value;
+ this.propagateChange(this.modelValue);
+ }
+ }
+
+ minDatapointsLimit() {
+ return this.timeService.getMinDatapointsLimit();
+ }
+
+ maxDatapointsLimit() {
+ return this.timeService.getMaxDatapointsLimit();
+ }
+
+ ngOnDestroy() {
+ this.destroy$.next();
+ this.destroy$.complete();
+ }
+
+}
diff --git a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html
index e36996197c..333c70f24f 100644
--- a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html
+++ b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html
@@ -173,19 +173,11 @@
{{ 'timewindow.hide' | translate }}
-
-
-
-
-
-
-
-
+
+
+
+
diff --git a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts
index ba7ee3e58c..cb16d7694a 100644
--- a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts
+++ b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts
@@ -191,7 +191,7 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On
}),
aggregation: this.fb.group({
type: [ isDefined(aggregation?.type) ? this.timewindow.aggregation.type : null ],
- limit: [ isDefined(aggregation?.limit) ? this.checkLimit(this.timewindow.aggregation.limit) : null ]
+ limit: [ isDefined(aggregation?.limit) ? this.timewindow.aggregation.limit : null ]
}),
timezone: [ isDefined(this.timewindow.timezone) ? this.timewindow.timezone : null ],
hideAggregation: [ isDefinedAndNotNull(this.timewindow.hideAggregation)
@@ -292,15 +292,6 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On
this.destroy$.complete();
}
- private checkLimit(limit?: number): number {
- if (!limit || limit < this.minDatapointsLimit()) {
- return this.minDatapointsLimit();
- } else if (limit > this.maxDatapointsLimit()) {
- return this.maxDatapointsLimit();
- }
- return limit;
- }
-
private updateValidators(aggType: AggregationType) {
if (aggType !== AggregationType.NONE) {
this.timewindowForm.get('aggregation.limit').clearValidators();
diff --git a/ui-ngx/src/app/shared/components/time/timewindow-form.scss b/ui-ngx/src/app/shared/components/time/timewindow-form.scss
index 0aca6157a6..2379910a79 100644
--- a/ui-ngx/src/app/shared/components/time/timewindow-form.scss
+++ b/ui-ngx/src/app/shared/components/time/timewindow-form.scss
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-@import "../../../../scss/constants";
:host {
background-color: #fff;
@@ -32,32 +31,10 @@
tb-timeinterval,
tb-quick-time-interval,
- tb-datetime-period {
+ tb-datetime-period,
+ tb-datapoints-limit {
flex: 1;
}
}
}
-
- .limit-slider-container {
- width: 100%;
- .limit-slider-value {
- margin-left: 16px;
- min-width: 25px;
- max-width: 106px;
- }
- mat-form-field input[type=number] {
- text-align: center;
- }
- }
-
- @media #{$mat-gt-sm} {
- .limit-slider-container {
- > label {
- margin-right: 16px;
- width: min-content;
- max-width: 40%;
- }
- }
- }
-
}
diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html
index 0b93857a81..e631eff949 100644
--- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html
+++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html
@@ -137,19 +137,9 @@
diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts
index 8d51d2ec00..7b0286383a 100644
--- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts
+++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts
@@ -275,7 +275,7 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit, O
disabled: hideAggregation
}],
limit: [{
- value: isDefined(aggregation?.limit) ? this.checkLimit(aggregation.limit) : null,
+ value: isDefined(aggregation?.limit) ? aggregation.limit : null,
disabled: hideAggInterval
}, []]
}),
@@ -303,15 +303,6 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit, O
this.destroy$.complete();
}
- private checkLimit(limit?: number): number {
- if (!limit || limit < this.minDatapointsLimit()) {
- return this.minDatapointsLimit();
- } else if (limit > this.maxDatapointsLimit()) {
- return this.maxDatapointsLimit();
- }
- return limit;
- }
-
private updateValidators(aggType: AggregationType) {
if (aggType !== AggregationType.NONE) {
this.timewindowForm.get('aggregation.limit').clearValidators();
diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts
index 5afaf80fdb..130e272c21 100644
--- a/ui-ngx/src/app/shared/shared.module.ts
+++ b/ui-ngx/src/app/shared/shared.module.ts
@@ -228,6 +228,7 @@ import { ScadaSymbolInputComponent } from '@shared/components/image/scada-symbol
import { CountryAutocompleteComponent } from '@shared/components/country-autocomplete.component';
import { CountryData } from '@shared/models/country.models';
import { SvgXmlComponent } from '@shared/components/svg-xml.component';
+import { DatapointsLimitComponent } from '@shared/components/time/datapoints-limit.component';
export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) {
return markedOptionsService;
@@ -317,6 +318,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
TimezoneComponent,
TimezonePanelComponent,
QuickTimeIntervalComponent,
+ DatapointsLimitComponent,
DashboardSelectComponent,
DashboardSelectPanelComponent,
DatetimePeriodComponent,
@@ -529,6 +531,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
TimezoneComponent,
TimezonePanelComponent,
QuickTimeIntervalComponent,
+ DatapointsLimitComponent,
DashboardSelectComponent,
DatetimePeriodComponent,
DatetimeComponent,