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 @@ + +
+
{{ 'timewindow.edit-intervals-list' | translate }}
+
{{ 'timewindow.edit-intervals-list-hint' | translate }}
+
+
+
{{"timewindow.interval" | translate }}
+
+
+ + + {{ interval.name | translate:interval.translateParams }} + + +
+
+
+ + +
+
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 }} + + +
+ @@ -85,6 +91,12 @@ appearance="outline" [required]="timewindowForm.get('selectedTab').value === timewindowTypes.REALTIME && timewindowForm.get('realtime.realtimeType').value === realtimeTypes.INTERVAL"> +
@@ -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) {