diff --git a/ui-ngx/src/app/modules/common/modules-map.ts b/ui-ngx/src/app/modules/common/modules-map.ts
index e49e9de335..f93a7172af 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 TimezonePanelComponent from '@shared/components/time/timezone-panel.
import * as DatapointsLimitComponent from '@shared/components/time/datapoints-limit.component';
import * as AggregationTypeSelectComponent from '@shared/components/aggregation/aggregation-type-select.component';
import * as AggregationOptionsConfigComponent from '@shared/components/aggregation/aggregation-options-config-panel.component';
+import * as IntervalOptionsConfigPanelComponent from '@shared/components/time/interval-options-config-panel.component';
import { IModulesMap } from '@modules/common/modules-map.models';
import { Observable, map, of } from 'rxjs';
@@ -476,6 +477,7 @@ class ModulesMap implements IModulesMap {
'@shared/components/time/datapoints-limit.component': DatapointsLimitComponent,
'@shared/components/aggregation/aggregation-type-select.component': AggregationTypeSelectComponent,
'@shared/components/aggregation/aggregation-options-config-panel.component': AggregationOptionsConfigComponent,
+ '@shared/components/time/interval-options-config-panel.component': IntervalOptionsConfigPanelComponent,
'@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/aggregation/aggregation-type-select.component.scss b/ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.scss
index 4e08e7fc73..ddc2385dfa 100644
--- a/ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.scss
+++ b/ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.scss
@@ -26,10 +26,4 @@
.mat-mdc-select-value {
min-width: 100px;
}
- .mat-mdc-form-field-has-icon-suffix .mat-mdc-text-field-wrapper:has(.mat-mdc-form-field-icon-suffix:empty) {
- padding-right: 12px;
- }
- .mat-mdc-form-field-icon-suffix:empty {
- padding: 0;
- }
}
diff --git a/ui-ngx/src/app/shared/components/time/interval-options-config-panel.component.html b/ui-ngx/src/app/shared/components/time/interval-options-config-panel.component.html
new file mode 100644
index 0000000000..ce8abdd7e5
--- /dev/null
+++ b/ui-ngx/src/app/shared/components/time/interval-options-config-panel.component.html
@@ -0,0 +1,47 @@
+
+
diff --git a/ui-ngx/src/app/shared/components/time/interval-options-config-panel.component.scss b/ui-ngx/src/app/shared/components/time/interval-options-config-panel.component.scss
new file mode 100644
index 0000000000..e6ffca11ee
--- /dev/null
+++ b/ui-ngx/src/app/shared/components/time/interval-options-config-panel.component.scss
@@ -0,0 +1,44 @@
+/**
+ * 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.
+ */
+:host {
+ .tb-interval-options-form {
+ height: 100%;
+ max-width: 600px;
+
+ .tb-form-table {
+ overflow: hidden;
+ }
+
+ .tb-form-table-body {
+ overflow-y: auto;
+ }
+
+ .tb-form-hint {
+ flex-shrink: 0;
+ }
+
+ .mdc-list {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+
+ .mat-mdc-list-item.mdc-list-item--with-leading-checkbox.mdc-list-item--with-one-line {
+ height: 40px;
+ }
+ }
+ }
+}
+
diff --git a/ui-ngx/src/app/shared/components/time/interval-options-config-panel.component.ts b/ui-ngx/src/app/shared/components/time/interval-options-config-panel.component.ts
new file mode 100644
index 0000000000..53de09e28f
--- /dev/null
+++ b/ui-ngx/src/app/shared/components/time/interval-options-config-panel.component.ts
@@ -0,0 +1,68 @@
+///
+/// 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, Input, OnInit } from '@angular/core';
+import { HistoryWindowType, RealtimeWindowType, TimewindowType } from '@shared/models/time/time.models';
+import { FormBuilder, FormGroup } from '@angular/forms';
+import { TbPopoverComponent } from '@shared/components/popover.component';
+
+@Component({
+ selector: 'tb-interval-options-config-panel',
+ templateUrl: './interval-options-config-panel.component.html',
+ styleUrls: ['./interval-options-config-panel.component.scss']
+})
+export class IntervalOptionsConfigPanelComponent implements OnInit {
+
+ @Input()
+ allowedIntervals: Array;
+
+ @Input()
+ intervalType: RealtimeWindowType | HistoryWindowType;
+
+ @Input()
+ timewindowType: TimewindowType;
+
+ @Input()
+ onClose: (result: Array | null) => void;
+
+ @Input()
+ popoverComponent: TbPopoverComponent;
+
+ intervalOptionsConfigForm: FormGroup;
+
+ intervals = [];
+
+ constructor(private fb: FormBuilder) {}
+
+ ngOnInit(): void {
+ this.intervalOptionsConfigForm = this.fb.group({
+ allowedIntervals: [this.allowedIntervals]
+ });
+ }
+
+ update() {
+ if (this.onClose) {
+ this.onClose([]);
+ }
+ }
+
+ cancel() {
+ if (this.onClose) {
+ this.onClose(null);
+ }
+ }
+
+}
diff --git a/ui-ngx/src/app/shared/components/time/quick-time-interval.component.html b/ui-ngx/src/app/shared/components/time/quick-time-interval.component.html
index 40f39118c8..8eb18ea34b 100644
--- a/ui-ngx/src/app/shared/components/time/quick-time-interval.component.html
+++ b/ui-ngx/src/app/shared/components/time/quick-time-interval.component.html
@@ -23,5 +23,8 @@
{{ timeIntervalTranslationMap.get(interval) | translate}}
+
+
+
diff --git a/ui-ngx/src/app/shared/components/time/timeinterval.component.html b/ui-ngx/src/app/shared/components/time/timeinterval.component.html
index 57e6ca0808..e9914c6068 100644
--- a/ui-ngx/src/app/shared/components/time/timeinterval.component.html
+++ b/ui-ngx/src/app/shared/components/time/timeinterval.component.html
@@ -25,6 +25,9 @@
{{ interval.name | translate:interval.translateParams }}
+
+
+
@@ -120,6 +132,12 @@
[disabledAdvanced]="timewindowForm.get('history.disableCustomInterval').value"
[required]="timewindowForm.get('selectedTab').value === timewindowTypes.HISTORY &&
timewindowForm.get('history.historyType').value === historyTypes.LAST_INTERVAL">
+
@@ -153,6 +171,12 @@
appearance="outline"
[required]="timewindowForm.get('selectedTab').value === timewindowTypes.HISTORY &&
timewindowForm.get('history.historyType').value === historyTypes.INTERVAL">
+
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 618d6daf8e..e85e5dbaa0 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
@@ -41,6 +41,7 @@ import { TbPopoverService } from '@shared/components/popover.service';
import {
AggregationOptionsConfigPanelComponent
} from '@shared/components/aggregation/aggregation-options-config-panel.component';
+import { IntervalOptionsConfigPanelComponent } from '@shared/components/time/interval-options-config-panel.component';
export interface TimewindowConfigDialogData {
quickIntervalOnly: boolean;
@@ -456,4 +457,50 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On
this.cd.detectChanges();
}
+ configureRealtimeLastIntervalOptions($event: Event) {
+ const resFn = (res) => {};
+ this.openIntervalOptionsConfig($event, [], resFn, RealtimeWindowType.LAST_INTERVAL);
+ }
+
+ configureRealtimeQuickIntervalOptions($event: Event) {
+ const resFn = (res) => {};
+ this.openIntervalOptionsConfig($event, [], resFn, RealtimeWindowType.INTERVAL, TimewindowType.REALTIME);
+ }
+
+ configureHistoryLastIntervalOptions($event: Event) {
+ const resFn = (res) => {};
+ this.openIntervalOptionsConfig($event, [], resFn, HistoryWindowType.LAST_INTERVAL);
+ }
+
+ configureHistoryQuickIntervalOptions($event: Event) {
+ const resFn = (res) => {};
+ this.openIntervalOptionsConfig($event, [], resFn, HistoryWindowType.INTERVAL, TimewindowType.HISTORY);
+ }
+
+ private openIntervalOptionsConfig($event: Event, allowedIntervals: Array, resFn: (res) => void,
+ intervalType: RealtimeWindowType | HistoryWindowType, timewindowType?: TimewindowType) {
+ if ($event) {
+ $event.stopPropagation();
+ }
+ const trigger = ($event.target || $event.srcElement || $event.currentTarget) as Element;
+ if (this.popoverService.hasPopover(trigger)) {
+ this.popoverService.hidePopover(trigger);
+ } else {
+ const intervalsConfigPopover = this.popoverService.displayPopover(trigger, this.renderer,
+ this.viewContainerRef, IntervalOptionsConfigPanelComponent, ['left', 'leftTop', 'leftBottom'], true, null,
+ {
+ allowedIntervals: deepClone(allowedIntervals),
+ intervalType: intervalType,
+ timewindowType: timewindowType,
+ onClose: (result: Array | null) => {
+ intervalsConfigPopover.hide();
+ resFn(result);
+ }
+ },
+ {maxHeight: '90vh', height: '100%'},
+ {}, {}, true, () => {}, {padding: 0});
+ intervalsConfigPopover.tbComponentRef.instance.popoverComponent = intervalsConfigPopover;
+ }
+ this.cd.detectChanges();
+ }
}
diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts
index 2968193d43..3200fad4cd 100644
--- a/ui-ngx/src/app/shared/shared.module.ts
+++ b/ui-ngx/src/app/shared/shared.module.ts
@@ -220,6 +220,7 @@ import { SvgXmlComponent } from '@shared/components/svg-xml.component';
import { DatapointsLimitComponent } from '@shared/components/time/datapoints-limit.component';
import { AggregationTypeSelectComponent } from '@shared/components/aggregation/aggregation-type-select.component';
import { AggregationOptionsConfigPanelComponent } from '@shared/components/aggregation/aggregation-options-config-panel.component';
+import { IntervalOptionsConfigPanelComponent } from '@shared/components/time/interval-options-config-panel.component';
export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) {
return markedOptionsService;
@@ -311,6 +312,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
DatapointsLimitComponent,
AggregationTypeSelectComponent,
AggregationOptionsConfigPanelComponent,
+ IntervalOptionsConfigPanelComponent,
DashboardSelectComponent,
DashboardSelectPanelComponent,
DatetimePeriodComponent,
@@ -520,6 +522,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
DatapointsLimitComponent,
AggregationTypeSelectComponent,
AggregationOptionsConfigPanelComponent,
+ IntervalOptionsConfigPanelComponent,
DashboardSelectComponent,
DatetimePeriodComponent,
DatetimeComponent,
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 b36ef5c293..e699f75c11 100644
--- a/ui-ngx/src/assets/locale/locale.constant-en_US.json
+++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json
@@ -4545,7 +4545,9 @@
"disable-custom-interval": "Disable custom interval selection",
"edit-aggregation-functions-list": "Edit aggregation functions list",
"edit-aggregation-functions-list-hint": "List of available options can be specified.",
- "allowed-aggregation-functions": "Allowed aggregation functions"
+ "allowed-aggregation-functions": "Allowed aggregation functions",
+ "edit-intervals-list": "Edit intervals list",
+ "edit-intervals-list-hint": "List of available interval options can be specified. It is possible to configure the grouping intervals list and default grouping interval."
},
"tooltip": {
"trigger": "Trigger",
diff --git a/ui-ngx/src/form.scss b/ui-ngx/src/form.scss
index 2a7ee6e7eb..bd3ba3c275 100644
--- a/ui-ngx/src/form.scss
+++ b/ui-ngx/src/form.scss
@@ -381,6 +381,16 @@
}
}
}
+ &.mat-mdc-form-field-has-icon-suffix {
+ .mat-mdc-form-field-icon-suffix:empty {
+ padding: 0;
+ }
+ .mat-mdc-text-field-wrapper:has(.mat-mdc-form-field-icon-suffix:empty) {
+ &.mdc-text-field--outlined, &:not(.mdc-text-field--outlined) {
+ padding-right: 12px;
+ }
+ }
+ }
.mat-mdc-text-field-wrapper {
&.mdc-text-field--outlined, &:not(.mdc-text-field--outlined) {
&:not(.mdc-text-field--focused):not(.mdc-text-field--disabled):not(.mdc-text-field--invalid):not(:hover) {