Browse Source

Merge branch 'master' into fix-device-profile-edge-sync

pull/14641/head
Mazurenko Nikita 9 months ago
committed by GitHub
parent
commit
b96b9f9e6e
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 0
      application/src/main/data/resources/js_extensions/gateway-management-extension.js
  2. 3
      application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java
  3. 6
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java
  4. 1
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  5. 4
      dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmDataAdapter.java
  6. 2
      dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java
  7. 2
      ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.html
  8. 4
      ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-filter-config.component.html
  9. 1
      ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-filter-config.component.scss
  10. 13
      ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-filter-config.component.ts
  11. 8
      ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table-config.ts
  12. 2
      ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.ts
  13. 8
      ui-ngx/src/app/modules/home/components/audit-log/audit-log-filter.component.html
  14. 9
      ui-ngx/src/app/modules/home/components/audit-log/audit-log-filter.component.scss
  15. 25
      ui-ngx/src/app/modules/home/components/audit-log/audit-log-filter.component.ts
  16. 6
      ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts
  17. 2
      ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.html
  18. 10
      ui-ngx/src/app/modules/home/components/calculated-fields/table-header/calculated-fields-filter-config.component.html
  19. 12
      ui-ngx/src/app/modules/home/components/calculated-fields/table-header/calculated-fields-filter-config.component.scss
  20. 32
      ui-ngx/src/app/modules/home/components/calculated-fields/table-header/calculated-fields-filter-config.component.ts
  21. 53
      ui-ngx/src/app/modules/home/components/device/device-info-filter.component.html
  22. 10
      ui-ngx/src/app/modules/home/components/device/device-info-filter.component.scss
  23. 85
      ui-ngx/src/app/modules/home/components/device/device-info-filter.component.ts
  24. 9
      ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.html
  25. 9
      ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.ts
  26. 19
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts
  27. 30
      ui-ngx/src/assets/dashboard/api_usage.json
  28. 23
      ui-ngx/src/assets/locale/locale.constant-en_US.json

0
application/src/main/data/resources/js_modules/gateway-management-extension.js → application/src/main/data/resources/js_extensions/gateway-management-extension.js

3
application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java

@ -406,7 +406,8 @@ public class InstallScripts {
Path resourcesDir = Path.of(getDataDir(), RESOURCES_DIR);
loadSystemResources(resourcesDir.resolve("images"), ResourceType.IMAGE, null);
loadSystemResources(resourcesDir.resolve("js_modules"), ResourceType.JS_MODULE, ResourceSubType.EXTENSION);
loadSystemResources(resourcesDir.resolve("js_extensions"), ResourceType.JS_MODULE, ResourceSubType.EXTENSION);
loadSystemResources(resourcesDir.resolve("js_modules"), ResourceType.JS_MODULE, ResourceSubType.MODULE);
loadSystemResources(resourcesDir.resolve("dashboards"), ResourceType.DASHBOARD, null);
}

6
common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java

@ -41,7 +41,6 @@ public class AlarmInfo extends Alarm {
@Schema(description = "Alarm originator label", example = "Thermostat label")
private String originatorLabel;
@Getter
@Setter
@Schema(description = "Originator display name", example = "Thermostat")
private String originatorDisplayName;
@ -59,10 +58,15 @@ public class AlarmInfo extends Alarm {
super(alarm);
}
public String getOriginatorDisplayName() {
return originatorDisplayName != null ? originatorDisplayName : (originatorLabel != null ? originatorLabel : originatorName);
}
public AlarmInfo(AlarmInfo alarmInfo) {
super(alarmInfo);
this.originatorName = alarmInfo.getOriginatorName();
this.originatorLabel = alarmInfo.getOriginatorLabel();
this.originatorDisplayName = alarmInfo.getOriginatorDisplayName();
this.assignee = alarmInfo.getAssignee();
}

1
dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java

@ -257,6 +257,7 @@ public class ModelConstants {
public static final String ALARM_ORIGINATOR_NAME_PROPERTY = "originator_name";
public static final String ALARM_ORIGINATOR_LABEL_PROPERTY = "originator_label";
public static final String ALARM_ORIGINATOR_TYPE_PROPERTY = "originator_type";
public static final String ALARM_ORIGINATOR_DISPLAY_NAME_PROPERTY = "originator_display_name";
public static final String ALARM_SEVERITY_PROPERTY = "severity";
public static final String ALARM_ASSIGNEE_ID_PROPERTY = "assignee_id";
public static final String ALARM_ASSIGNEE_FIRST_NAME_PROPERTY = "assignee_first_name";

4
dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmDataAdapter.java

@ -117,11 +117,13 @@ public class AlarmDataAdapter {
String originatorName = originatorNameObj != null ? originatorNameObj.toString() : null;
Object originatorLabelObj = row.get(ModelConstants.ALARM_ORIGINATOR_LABEL_PROPERTY);
String originatorLabel = originatorLabelObj != null ? originatorLabelObj.toString() : null;
Object originatorDisplayNameObj = row.get(ModelConstants.ALARM_ORIGINATOR_DISPLAY_NAME_PROPERTY);
String originatorDisplayName = originatorDisplayNameObj != null ? originatorDisplayNameObj.toString() : null;
AlarmData alarmData = new AlarmData(alarm, entityId);
alarmData.setOriginatorName(originatorName);
alarmData.setOriginatorLabel(originatorLabel);
alarmData.setOriginatorDisplayName(StringUtils.isBlank(originatorLabel) ? originatorName : originatorLabel);
alarmData.setOriginatorDisplayName(originatorDisplayName);
if (alarm.getAssigneeId() != null) {
alarmData.setAssignee(new AlarmAssignee(alarm.getAssigneeId(), assigneeFirstName, assigneeLastName, assigneeEmail));
}

2
dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java

@ -83,6 +83,7 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
alarmFieldColumnMap.put(ASSIGNEE_FIRST_NAME_KEY, ModelConstants.ALARM_ASSIGNEE_FIRST_NAME_PROPERTY);
alarmFieldColumnMap.put(ASSIGNEE_LAST_NAME_KEY, ModelConstants.ALARM_ASSIGNEE_LAST_NAME_PROPERTY);
alarmFieldColumnMap.put(ASSIGNEE_EMAIL_KEY, ModelConstants.ALARM_ASSIGNEE_EMAIL_PROPERTY);
alarmFieldColumnMap.put("originatorDisplayName", ModelConstants.ALARM_ORIGINATOR_DISPLAY_NAME_PROPERTY);
}
private static final String FIELDS_SELECTION = "select a.id as id," +
@ -106,6 +107,7 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
" a.type as type, " +
" a.originator_name as originator_name, " +
" a.originator_label as originator_label, " +
" coalesce(a.originator_label, a.originator_name) as originator_display_name, " +
" a.assignee_first_name as assignee_first_name, " +
" a.assignee_last_name as assignee_last_name, " +
" a.assignee_email as assignee_email, " +

2
ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.html

@ -65,7 +65,7 @@
class="flex-1"
showLabel
required
label="{{ 'alarm-rule.target-entity-type' | translate }}"
label="{{ 'entity.entity-type' | translate }}"
[filterAllowedEntityTypes]="false"
[allowedEntityTypes]="alarmRuleEntityTypeList"
formControlName="entityType">

4
ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-filter-config.component.html

@ -66,7 +66,7 @@
</tb-entity-subtype-list>
</div>
<div class="tb-form-row column-xs">
<div class="fixed-title-width" translate>alarm-rule.target-entity-type</div>
<div class="fixed-title-width" translate>entity.entity-type</div>
<mat-form-field class="flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="entityType" placeholder="{{ 'alarm-rule.any-type' | translate }}">
<mat-option>{{ 'alarm-rule.any-type' | translate }}</mat-option>
@ -78,7 +78,7 @@
</div>
@if (alarmRuleFilterConfigForm.get('entityType').value) {
<div class="tb-form-row column-xs">
<div class="fixed-title-width" translate>alarm-rule.target-entities</div>
<div class="fixed-title-width" translate>entity.entities</div>
<tb-entity-list appearance="outline"
subscriptSizing="dynamic"
class="flex flex-1"

1
ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-filter-config.component.scss

@ -37,7 +37,6 @@
}
::ng-deep {
.tb-alarm-filter-config-component {
max-width: 100%;
width: 600px;

13
ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-filter-config.component.ts

@ -73,14 +73,6 @@ export class AlarmRuleFilterConfigComponent implements OnInit, ControlValueAcces
@Input()
buttonMode = true;
@coerceBoolean()
@Input()
userMode = false;
@coerceBoolean()
@Input()
propagatedFilter = true;
@Input()
initialAlarmRuleFilterConfig: AlarmRuleFilterConfig = {
name: [],
@ -124,7 +116,6 @@ export class AlarmRuleFilterConfigComponent implements OnInit, ControlValueAcces
ngOnInit(): void {
if (this.data) {
this.panelMode = this.data.panelMode;
this.userMode = this.data.userMode;
this.alarmRuleFilterConfig = this.data.alarmRuleFilterConfig;
this.initialAlarmRuleFilterConfig = this.data.initialAlarmRuleFilterConfig;
if (this.panelMode && !this.initialAlarmRuleFilterConfig) {
@ -248,7 +239,7 @@ export class AlarmRuleFilterConfigComponent implements OnInit, ControlValueAcces
if (!isArraysEqualIgnoreUndefined(filter1.entities, filter2.entities)) {
return false;
}
return filter1.entityType !== filter2.entityType;
return filter1.entityType === filter2.entityType;
}
return false;
};
@ -287,7 +278,7 @@ export class AlarmRuleFilterConfigComponent implements OnInit, ControlValueAcces
if (!filterTextParts.length) {
this.buttonDisplayValue = this.translate.instant('alarm-rule.alarm-rule-filter-title');
} else {
this.buttonDisplayValue = this.translate.instant('alarm-rule.alarm-rule-filter-title') + `: ${filterTextParts.join(', ')}`;
this.buttonDisplayValue = this.translate.instant('alarm-rule.filter-title') + `: ${filterTextParts.join(', ')}`;
}
}
}

8
ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table-config.ts

@ -140,16 +140,16 @@ export class AlarmRulesTableConfig extends EntityTableConfig<any> {
this.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC};
this.columns.push(new DateEntityTableColumn<CalculatedFieldAlarmRule>('createdTime', 'common.created-time', this.datePipe, '150px'));
this.columns.push(new EntityTableColumn<CalculatedFieldAlarmRule>('name', 'alarm-rule.alarm-type', this.pageMode ? '30%' :'33%',
this.columns.push(new EntityTableColumn<CalculatedFieldAlarmRule>('name', 'alarm-rule.alarm-type', '33%',
entity => this.utilsService.customTranslation(entity.name, entity.name)));
if (this.pageMode) {
this.columns.push(new EntityTableColumn<CalculatedFieldAlarmRule>('entityType', 'alarm-rule.target-entity-type', '15%',
this.columns.push(new EntityTableColumn<CalculatedFieldAlarmRule>('entityType', 'entity.entity-type', '10%',
entity => this.translate.instant(entityTypeTranslations.get(entity.entityId.entityType).type)));
this.columns.push(new EntityLinkTableColumn<CalculatedFieldAlarmRule>('entityName', 'alarm-rule.target-entity', '30%',
this.columns.push(new EntityLinkTableColumn<CalculatedFieldAlarmRule>('entityName', 'entity.entity', '33%',
entity => this.utilsService.customTranslation(entity['entityName'], entity['entityName']),
entity => getEntityDetailsPageURL(entity.entityId?.id, entity.entityId?.entityType as EntityType), false));
}
this.columns.push(new EntityTableColumn<CalculatedFieldAlarmRule>('createRule', 'alarm-rule.severities', this.pageMode ? '15%' :'67%',
this.columns.push(new EntityTableColumn<CalculatedFieldAlarmRule>('createRule', 'alarm-rule.severities', this.pageMode ? '23%' : '67%',
entity => Object.keys(entity.configuration.createRules).map((severity) => this.translate.instant(alarmSeverityTranslations.get(severity as AlarmSeverity))).join(', '),
() => ({}), false));
this.columns.push(new EntityTableColumn<CalculatedFieldAlarmRule>('clearRule', 'alarm-rule.cleared', '90px',

2
ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.ts

@ -107,7 +107,7 @@ export class AlarmFilterConfigComponent implements OnInit, OnDestroy, ControlVal
alarmSeverityTranslationMap = alarmSeverityTranslations;
buttonDisplayValue = this.translate.instant('alarm.alarm-filter');
buttonDisplayValue = this.translate.instant('alarm.alarm-filter-title');
alarmFilterConfigForm: UntypedFormGroup;

8
ui-ngx/src/app/modules/home/components/audit-log/audit-log-filter.component.html

@ -24,14 +24,14 @@
<mat-icon>filter_list</mat-icon>{{buttonDisplayValue}}
</button>
<ng-template #auditLogFilterPanel>
<form class="mat-content mat-padding flex flex-col" [formGroup]="auditLogFilterForm" (ngSubmit)="update()">
<div class="tb-form-panel flex-1 no-padding no-border">
<div class="tb-form-row flex-1 space-between column-xs">
<form class="tb-audit-log-config-component mat-content mat-padding flex flex-col" [formGroup]="auditLogFilterForm" (ngSubmit)="update()">
<div class="tb-form-panel no-padding no-border flex-1">
<div class="tb-form-row space-between column-xs flex-1">
<div class="fixed-title-width" translate>audit-log.filter-types</div>
<tb-string-items-list appearance="outline"
class="flex flex-1 gt-xs:w-64"
subscriptSizing="dynamic"
[placeholder]="'audit-log.filter-type-add' | translate"
placeholder="{{ !auditLogFilterForm.get('actionTypes').value?.length ? ('audit-log.any-type' | translate) : '' }}"
[predefinedValues]="predefinedTypeValues"
formControlName="actionTypes">
</tb-string-items-list>

9
ui-ngx/src/app/modules/home/components/audit-log/audit-log-filter.component.scss

@ -33,3 +33,12 @@
}
}
}
::ng-deep {
.tb-audit-log-config-component {
max-width: 100%;
width: 600px;
min-width: 100%;
flex: 1;
}
}

25
ui-ngx/src/app/modules/home/components/audit-log/audit-log-filter.component.ts

@ -16,16 +16,27 @@
import { Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
import { TemplatePortal } from '@angular/cdk/portal';
import { ChangeDetectorRef, Component, DestroyRef, ElementRef, forwardRef, Input, OnInit, TemplateRef, ViewChild, ViewContainerRef } from '@angular/core';
import {
ChangeDetectorRef,
Component,
DestroyRef,
ElementRef,
forwardRef,
Input,
OnInit,
TemplateRef,
ViewChild,
ViewContainerRef
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
import { isDefined, isDefinedAndNotNull } from '@app/core/utils';
import { StringItemsOption } from '@app/shared/components/string-items-list.component';
import {
ActionType,
actionTypeTranslations,
auditLogFilterEquals,
AuditLogFilter,
auditLogFilterEquals,
POSITION_MAP,
} from '@app/shared/public-api';
import { TranslateService } from '@ngx-translate/core';
@ -56,9 +67,9 @@ export class AuditLogFilterComponent implements OnInit, ControlValueAccessor {
actionTypeTranslations = actionTypeTranslations;
buttonDisplayValue = this.translate.instant('audit-log.filter');
buttonDisplayValue = this.translate.instant('audit-log.audit-log-filter-title');
auditLogFilterForm: UntypedFormGroup;
auditLogFilterForm: FormGroup;
auditLogOverlayRef: OverlayRef;
@ -68,7 +79,7 @@ export class AuditLogFilterComponent implements OnInit, ControlValueAccessor {
private propagateChange = (_: any) => {};
constructor(private fb: UntypedFormBuilder,
constructor(private fb: FormBuilder,
private translate: TranslateService,
private overlay: Overlay,
private nativeElement: ElementRef,
@ -93,7 +104,7 @@ export class AuditLogFilterComponent implements OnInit, ControlValueAccessor {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
registerOnTouched(_fn: any): void {
}
setDisabledState(isDisabled: boolean): void {

6
ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts

@ -136,11 +136,13 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedFie
this.columns.push(new EntityTableColumn<CalculatedField>('name', 'common.name', this.pageMode ? '33%' : '60%',
entity => this.utilsService.customTranslation(entity.name, entity.name)));
if (this.pageMode) {
this.columns.push(new EntityLinkTableColumn<CalculatedFieldAlarmRule>('entityName', 'calculated-fields.target-entity', '33%',
this.columns.push(new EntityTableColumn<CalculatedFieldAlarmRule>('entityType', 'entity.entity-type', '10%',
entity => this.translate.instant(entityTypeTranslations.get(entity.entityId.entityType).type)));
this.columns.push(new EntityLinkTableColumn<CalculatedFieldAlarmRule>('entityName', 'entity.entity', '33%',
entity => this.utilsService.customTranslation(entity['entityName'], entity['entityName']),
entity => getEntityDetailsPageURL(entity.entityId?.id, entity.entityId?.entityType as EntityType), false));
}
this.columns.push(new EntityTableColumn<CalculatedField>('type', 'common.type', this.pageMode ? '33%' : '40%', entity => this.translate.instant(CalculatedFieldTypeTranslations.get(entity.type).name), () => ({whiteSpace: 'nowrap' })));
this.columns.push(new EntityTableColumn<CalculatedField>('type', 'common.type', this.pageMode ? '23%' : '40%', entity => this.translate.instant(CalculatedFieldTypeTranslations.get(entity.type).name), () => ({whiteSpace: 'nowrap' })));
this.cellActionDescriptors.push(
{

2
ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.html

@ -62,7 +62,7 @@
class="flex-1"
showLabel
required
label="{{ 'alarm-rule.target-entity-type' | translate }}"
label="{{ 'entity.entity-type' | translate }}"
[filterAllowedEntityTypes]="false"
[allowedEntityTypes]="calculatedFieldsEntityTypeList"
formControlName="entityType">

10
ui-ngx/src/app/modules/home/components/calculated-fields/table-header/calculated-fields-filter-config.component.html

@ -21,18 +21,18 @@
<ng-template #componentMode>
<ng-container *ngIf="buttonMode; else calculatedFieldsFilter">
<button color="primary"
matTooltip="{{ 'calculated-fields.calculated-fields-filter' | translate }}"
matTooltip="{{buttonDisplayValue}}"
matTooltipPosition="above"
mat-stroked-button
(click)="toggleCfFilterPanel($event)">
<mat-icon>filter_list</mat-icon>{{ 'calculated-fields.calculated-fields-filter' | translate }}
<mat-icon>filter_list</mat-icon>{{buttonDisplayValue}}
</button>
</ng-container>
</ng-template>
<ng-template #calculatedFieldsFilterPanel>
<form class="mat-content mat-padding flex flex-col" (ngSubmit)="update()">
<ng-container *ngTemplateOutlet="calculatedFieldsFilter"></ng-container>
<div class="tb-panel-actions flex flex-row items-center justify-end">
<div class="tb-panel-actions flex flex-row items-center">
<button type="button"
mat-button
color="primary"
@ -68,7 +68,7 @@
</tb-string-items-list>
</div>
<div class="tb-form-row column-xs">
<div class="fixed-title-width" translate>alarm-rule.target-entity-type</div>
<div class="fixed-title-width" translate>entity.entity-type</div>
<mat-form-field class="flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="entityType" placeholder="{{ 'alarm-rule.any-type' | translate }}">
<mat-option>{{ 'alarm-rule.any-type' | translate }}</mat-option>
@ -80,7 +80,7 @@
</div>
@if (cfFilterForm.get('entityType').value) {
<div class="tb-form-row column-xs">
<div class="fixed-title-width" translate>alarm-rule.target-entities</div>
<div class="fixed-title-width" translate>entity.entities</div>
<tb-entity-list appearance="outline"
subscriptSizing="dynamic"
class="flex flex-1"

12
ui-ngx/src/app/modules/home/components/calculated-fields/table-header/calculated-fields-filter-config.component.scss

@ -14,8 +14,6 @@
* limitations under the License.
*/
@import '../scss/constants';
:host {
display: flex;
max-width: 100%;
@ -42,15 +40,5 @@
width: 600px;
min-width: 100%;
flex: 1;
tb-entity-subtype-list {
flex: 1;
@media #{$mat-gt-xs} {
width: 180px;
}
.mdc-evolution-chip-set__chips {
width: 100%;
}
}
}
}

32
ui-ngx/src/app/modules/home/components/calculated-fields/table-header/calculated-fields-filter-config.component.ts

@ -79,14 +79,6 @@ export class CalculatedFieldsFilterConfigComponent implements OnInit, ControlVal
@Input()
buttonMode = true;
@coerceBoolean()
@Input()
userMode = false;
@coerceBoolean()
@Input()
propagatedFilter = true;
@Input()
initialCfFilterConfig: CalculatedFieldsQuery = {
types: [],
@ -96,6 +88,8 @@ export class CalculatedFieldsFilterConfigComponent implements OnInit, ControlVal
panelMode = false;
buttonDisplayValue = this.translate.instant('calculated-fields.calculated-field-filter-title');
cfFilterForm: FormGroup;
panelResult: CalculatedFieldsQuery = null;
@ -130,7 +124,6 @@ export class CalculatedFieldsFilterConfigComponent implements OnInit, ControlVal
ngOnInit(): void {
if (this.data) {
this.panelMode = this.data.panelMode;
this.userMode = this.data.userMode;
this.cfFilterConfig = this.data.filterConfig;
this.initialCfFilterConfig = this.data.initialFilterConfig;
if (this.panelMode && !this.initialCfFilterConfig) {
@ -177,6 +170,7 @@ export class CalculatedFieldsFilterConfigComponent implements OnInit, ControlVal
if (!this.initialCfFilterConfig && cfFilterConfig) {
this.initialCfFilterConfig = deepClone(cfFilterConfig);
}
this.updateButtonDisplayValue();
this.updateCfConfigForm(cfFilterConfig);
}
@ -253,7 +247,7 @@ export class CalculatedFieldsFilterConfigComponent implements OnInit, ControlVal
if (!isArraysEqualIgnoreUndefined(filter1.entities, filter2.entities)) {
return false;
}
return filter1.entityType !== filter2.entityType;
return filter1.entityType === filter2.entityType;
}
return false;
};
@ -268,9 +262,27 @@ export class CalculatedFieldsFilterConfigComponent implements OnInit, ControlVal
private cfConfigUpdated(formValue: any) {
this.cfFilterConfig = this.cfFilterFromFormValue(formValue);
this.updateButtonDisplayValue();
this.propagateChange(this.cfFilterConfig);
}
private updateButtonDisplayValue() {
if (this.buttonMode) {
const filterTextParts: string[] = [];
if (this.cfFilterConfig?.types?.length) {
filterTextParts.push(this.cfFilterConfig.types.map((type) => this.translate.instant(CalculatedFieldTypeTranslations.get(type).name)).join(', '));
}
if (this.cfFilterConfig?.entityType) {
filterTextParts.push(this.translate.instant( entityTypeTranslations.get(this.cfFilterConfig.entityType).type));
}
if (!filterTextParts.length) {
this.buttonDisplayValue = this.translate.instant('calculated-fields.calculated-field-filter-title');
} else {
this.buttonDisplayValue = this.translate.instant('calculated-fields.filter-title') + `: ${filterTextParts.join(', ')}`;
}
}
}
private cfFilterFromFormValue(formValue: any): CalculatedFieldsQuery {
return {
types: formValue?.types ?? [],

53
ui-ngx/src/app/modules/home/components/device/device-info-filter.component.html

@ -33,6 +33,13 @@
<form class="mat-content mat-padding flex flex-col" (ngSubmit)="update()">
<ng-container *ngTemplateOutlet="deviceFilter"></ng-container>
<div class="tb-panel-actions flex flex-row items-center justify-end">
<button type="button"
mat-button
color="primary"
(click)="reset()">
{{ 'action.reset' | translate }}
</button>
<span class="flex-1"></span>
<button type="button"
mat-button
(click)="cancel()">
@ -48,28 +55,28 @@
</form>
</ng-template>
<ng-template #deviceFilter>
<div class="flex flex-col gap-4" style="min-width: 280px;" [formGroup]="deviceInfoFilterForm">
<tb-device-profile-autocomplete
subscriptSizing="dynamic"
formControlName="deviceProfileId"
[displayAllOnEmpty]="true"
(deviceProfileChanged)="deviceProfileChanged($event)"
[editProfileEnabled]="false"
[addNewProfile]="false">
</tb-device-profile-autocomplete>
<mat-form-field>
<mat-label translate>device.device-state</mat-label>
<mat-select formControlName="active">
<mat-option value="">
{{ 'device.any' | translate }}
</mat-option>
<mat-option [value]="true">
{{ 'device.active' | translate }}
</mat-option>
<mat-option [value]="false">
{{ 'device.inactive' | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<div class="tb-device-filter-config-component tb-form-panel no-padding no-border" [formGroup]="deviceInfoFilterForm">
<div class="tb-form-row column-xs">
<div class="fixed-title-width" translate>device-profile.device-profile</div>
<tb-device-profile-autocomplete
class="flex-1"
formControlName="deviceProfileId"
[inlineField]="true"
[displayAllOnEmpty]="true"
(deviceProfileChanged)="deviceProfileChanged($event)"
[editProfileEnabled]="false"
[addNewProfile]="false">
</tb-device-profile-autocomplete>
</div>
<div class="tb-form-row column-xs">
<div class="fixed-title-width" translate>device.device-state</div>
<mat-form-field class="flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="active" placeholder="{{ 'device.any' | translate }}">
<mat-option>{{ 'device.any' | translate }}</mat-option>
<mat-option [value]="true">{{ 'device.active' | translate }}</mat-option>
<mat-option [value]="false">{{ 'device.inactive' | translate }}</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
</ng-template>

10
ui-ngx/src/app/modules/home/components/device/device-info-filter.component.scss

@ -33,3 +33,13 @@
}
}
}
::ng-deep {
.tb-device-filter-config-component {
max-width: 100%;
width: 500px;
min-width: 100%;
flex: 1;
}
}

85
ui-ngx/src/app/modules/home/components/device/device-info-filter.component.ts

@ -23,29 +23,30 @@ import {
Inject,
InjectionToken,
Input,
OnDestroy,
OnInit,
Optional,
TemplateRef,
ViewChild,
ViewContainerRef
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
import { coerceBoolean } from '@shared/decorators/coercion';
import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
import { TemplatePortal } from '@angular/cdk/portal';
import { TranslateService } from '@ngx-translate/core';
import { DeviceInfoFilter } from '@shared/models/device.models';
import { isDefinedAndNotNull } from '@core/utils';
import { deepClone, isDefinedAndNotNull, isEmpty, isUndefinedOrNull } from '@core/utils';
import { EntityInfoData } from '@shared/models/entity.models';
import { DeviceProfileService } from '@core/http/device-profile.service';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { fromEvent, Subscription } from 'rxjs';
export const DEVICE_FILTER_CONFIG_DATA = new InjectionToken<any>('DeviceFilterConfigData');
export interface DeviceFilterConfigData {
panelMode: boolean;
deviceInfoFilter: DeviceInfoFilter;
initialFilterConfig?: DeviceInfoFilter;
}
// @dynamic
@ -61,7 +62,7 @@ export interface DeviceFilterConfigData {
}
]
})
export class DeviceInfoFilterComponent implements OnInit, OnDestroy, ControlValueAccessor {
export class DeviceInfoFilterComponent implements OnInit, ControlValueAccessor {
@ViewChild('deviceFilterPanel')
deviceFilterPanel: TemplateRef<any>;
@ -72,19 +73,25 @@ export class DeviceInfoFilterComponent implements OnInit, OnDestroy, ControlValu
@Input()
buttonMode = true;
@Input()
initialDeviceFilterConfig: DeviceInfoFilter = {
deviceProfileId: null,
active: null
};
panelMode = false;
buttonDisplayValue = this.translate.instant('device.device-filter');
buttonDisplayValue = this.translate.instant('device.device-filter-title');
deviceInfoFilterForm: UntypedFormGroup;
deviceInfoFilterForm: FormGroup;
deviceFilterOverlayRef: OverlayRef;
panelResult: DeviceInfoFilter = null;
private deviceProfileInfo: EntityInfoData;
private deviceInfoFilter: DeviceInfoFilter;
private resizeWindows: Subscription;
private propagateChange = (_: any) => {};
@ -92,7 +99,7 @@ export class DeviceInfoFilterComponent implements OnInit, OnDestroy, ControlValu
private data: DeviceFilterConfigData | undefined,
@Optional()
private overlayRef: OverlayRef,
private fb: UntypedFormBuilder,
private fb: FormBuilder,
private translate: TranslateService,
private overlay: Overlay,
private nativeElement: ElementRef,
@ -106,6 +113,10 @@ export class DeviceInfoFilterComponent implements OnInit, OnDestroy, ControlValu
if (this.data) {
this.panelMode = this.data.panelMode;
this.deviceInfoFilter = this.data.deviceInfoFilter;
this.initialDeviceFilterConfig = this.data.initialFilterConfig;
if (this.panelMode && !this.initialDeviceFilterConfig) {
this.initialDeviceFilterConfig = deepClone(this.deviceInfoFilter);
}
}
this.deviceInfoFilterForm = this.fb.group({
deviceProfileId: [null, []],
@ -115,7 +126,6 @@ export class DeviceInfoFilterComponent implements OnInit, OnDestroy, ControlValu
takeUntilDestroyed(this.destroyRef)
).subscribe(
() => {
this.updateValidators();
if (!this.buttonMode) {
this.deviceFilterUpdated(this.deviceInfoFilterForm.value);
}
@ -126,14 +136,11 @@ export class DeviceInfoFilterComponent implements OnInit, OnDestroy, ControlValu
}
}
ngOnDestroy(): void {
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
registerOnTouched(_fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
@ -142,19 +149,18 @@ export class DeviceInfoFilterComponent implements OnInit, OnDestroy, ControlValu
this.deviceInfoFilterForm.disable({emitEvent: false});
} else {
this.deviceInfoFilterForm.enable({emitEvent: false});
this.updateValidators();
}
}
writeValue(deviceInfoFilter?: DeviceInfoFilter): void {
this.deviceInfoFilter = deviceInfoFilter;
if (!this.initialDeviceFilterConfig && deviceInfoFilter) {
this.initialDeviceFilterConfig = deepClone(deviceInfoFilter);
}
this.updateButtonDisplayValue();
this.updateDeviceInfoFilterForm(deviceInfoFilter);
}
private updateValidators() {
}
toggleDeviceFilterPanel($event: Event) {
if ($event) {
$event.stopPropagation();
@ -183,6 +189,9 @@ export class DeviceInfoFilterComponent implements OnInit, OnDestroy, ControlValu
});
this.deviceFilterOverlayRef.attach(new TemplatePortal(this.deviceFilterPanel,
this.viewContainerRef));
this.resizeWindows = fromEvent(window, 'resize').subscribe(() => {
this.deviceFilterOverlayRef.updatePosition();
});
}
cancel() {
@ -190,40 +199,62 @@ export class DeviceInfoFilterComponent implements OnInit, OnDestroy, ControlValu
if (this.overlayRef) {
this.overlayRef.dispose();
} else {
this.resizeWindows.unsubscribe();
this.deviceFilterOverlayRef.dispose();
}
}
update() {
this.deviceFilterUpdated(this.deviceInfoFilterForm.value);
this.deviceInfoFilterForm.markAsPristine();
if (this.panelMode) {
this.panelResult = this.deviceInfoFilter;
}
if (this.overlayRef) {
this.overlayRef.dispose();
} else {
this.resizeWindows.unsubscribe();
this.deviceFilterOverlayRef.dispose();
}
}
reset() {
const deviceInfoFilter = this.deviceFilterFromFormValue(this.deviceInfoFilterForm.value);
if (!this.deviceInfoFilterConfigEquals(deviceInfoFilter, this.initialDeviceFilterConfig)) {
this.updateDeviceInfoFilterForm(this.initialDeviceFilterConfig);
this.deviceInfoFilterForm.markAsDirty();
}
}
deviceProfileChanged(deviceProfileInfo: EntityInfoData) {
this.deviceProfileInfo = deviceProfileInfo;
this.updateButtonDisplayValue();
}
private deviceInfoFilterConfigEquals = (filter1?: DeviceInfoFilter, filter2?: DeviceInfoFilter): boolean => {
if (filter1 === filter2) {
return true;
}
if ((isUndefinedOrNull(filter1) || isEmpty(filter1)) && (isUndefinedOrNull(filter2) || isEmpty(filter2))) {
return true;
} else if (isDefinedAndNotNull(filter1) && isDefinedAndNotNull(filter2)) {
if (filter1.active !== filter2.active) {
return false;
}
return filter1.deviceProfileId === filter2.deviceProfileId;
}
return false;
};
private updateDeviceInfoFilterForm(deviceInfoFilter?: DeviceInfoFilter) {
this.deviceInfoFilterForm.patchValue({
deviceProfileId: deviceInfoFilter?.deviceProfileId,
active: isDefinedAndNotNull(deviceInfoFilter?.active) ? deviceInfoFilter?.active : ''
deviceProfileId: deviceInfoFilter?.deviceProfileId ?? null,
active: deviceInfoFilter?.active ?? null
}, {emitEvent: false});
this.updateValidators();
}
private deviceFilterUpdated(deviceInfoFilter: DeviceInfoFilter) {
this.deviceInfoFilter = deviceInfoFilter;
if ((this.deviceInfoFilter.active as any) === '') {
this.deviceInfoFilter.active = null;
}
this.deviceInfoFilter = this.deviceFilterFromFormValue(deviceInfoFilter);
this.updateButtonDisplayValue();
this.propagateChange(this.deviceInfoFilter);
}
@ -256,4 +287,10 @@ export class DeviceInfoFilterComponent implements OnInit, OnDestroy, ControlValu
}
}
private deviceFilterFromFormValue(formValue: any): DeviceInfoFilter {
return {
deviceProfileId: formValue?.deviceProfileId ?? null,
active: formValue?.active ?? null
};
}
}

9
ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.html

@ -15,8 +15,13 @@
limitations under the License.
-->
<mat-form-field [formGroup]="selectDeviceProfileFormGroup" class="mat-block" subscriptSizing="{{ subscriptSizing }}">
<mat-label>{{ 'device-profile.device-profile' | translate }}</mat-label>
<mat-form-field [formGroup]="selectDeviceProfileFormGroup"
[class.tb-inline-field]="inlineField"
[class.flex]="inlineField"
class="mat-block"
[appearance]="inlineField ? 'outline' : appearance"
[subscriptSizing]="inlineField ? 'dynamic' : subscriptSizing">
<mat-label *ngIf="!inlineField">{{ 'device-profile.device-profile' | translate }}</mat-label>
<input matInput type="text"
#deviceProfileInput
formControlName="deviceProfile"

9
ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.ts

@ -47,7 +47,7 @@ import { MatAutocomplete } from '@angular/material/autocomplete';
import { AddDeviceProfileDialogComponent, AddDeviceProfileDialogData } from './add-device-profile-dialog.component';
import { emptyPageData } from '@shared/models/page/page-data';
import { getEntityDetailsPageURL } from '@core/utils';
import { SubscriptSizing } from '@angular/material/form-field';
import { MatFormFieldAppearance, SubscriptSizing } from '@angular/material/form-field';
import { coerceBoolean } from '@shared/decorators/coercion';
import { AuthUser } from '@shared/models/user.model';
import { getCurrentAuthUser } from '@core/auth/auth.selectors';
@ -69,6 +69,9 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor,
modelValue: DeviceProfileId | null;
@Input()
appearance: MatFormFieldAppearance = 'fill';
@Input()
subscriptSizing: SubscriptSizing = 'fixed';
@ -106,6 +109,10 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor,
@Input()
hint: string;
@Input()
@coerceBoolean()
inlineField: boolean;
@Output()
deviceProfileUpdated = new EventEmitter<DeviceProfileId>();

19
ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts

@ -581,6 +581,8 @@ export class TbTimeSeriesChart {
yAxisSettingsList.sort((a1, a2) => a1.order - a2.order);
const axisLimitDatasources: Datasource[] = [];
for (const yAxisSettings of yAxisSettingsList) {
yAxisSettings.min = this.normalizeAxisLimit(yAxisSettings.min);
yAxisSettings.max = this.normalizeAxisLimit(yAxisSettings.max);
const axisSettings = mergeDeep<TimeSeriesChartYAxisSettings>({} as TimeSeriesChartYAxisSettings,
defaultTimeSeriesChartYAxisSettings, yAxisSettings);
const units = isNotEmptyTbUnits(axisSettings.units) ? axisSettings.units : this.ctx.units;
@ -1078,4 +1080,21 @@ export class TbTimeSeriesChart {
this.timeSeriesChart.setOption(this.timeSeriesChartOptions);
}
}
private normalizeAxisLimit(limit: string | number | ValueSourceConfig): string | number | ValueSourceConfig {
if (!limit) {
return {
type: ValueSourceType.constant,
value: null,
entityAlias: null
};
} else if (typeof limit === 'number' || typeof limit === 'string') {
return {
type: ValueSourceType.constant,
value: Number(limit),
entityAlias: null
};
}
return limit;
}
}

30
ui-ngx/src/assets/dashboard/api_usage.json

@ -121,7 +121,31 @@
"noDataDisplayMessage": "",
"configMode": "basic",
"borderRadius": "12px",
"titleColor": "rgba(0, 0, 0, 0.54)"
"titleColor": "rgba(0, 0, 0, 0.54)",
"timewindowStyle": {
"showIcon": false,
"iconSize": "24px",
"icon": "query_builder",
"iconPosition": "left",
"font": {
"size": 12,
"sizeUnit": "px",
"family": "Roboto",
"weight": "400",
"style": "normal",
"lineHeight": "16px"
},
"color": "rgba(0, 0, 0, 0.38)",
"displayTypePrefix": true
},
"titleFont": {
"size": 14,
"sizeUnit": "px",
"family": "Roboto",
"weight": "500",
"style": "normal",
"lineHeight": "20px"
}
},
"id": "a669cf86-e715-efa4-dd9a-b839abf499e9",
"typeFullFqn": "system.cards.timeseries_table"
@ -4519,7 +4543,7 @@
"name": "{i18n:api-usage.view-statistics}",
"buttonType": "icon",
"icon": "show_chart",
"buttonColor": "rgba(0, 0, 0, 0.87)",
"buttonColor": "rgba(0, 0, 0, 0.54)",
"customButtonStyle": {},
"useShowWidgetActionFunction": null,
"showWidgetActionFunction": "return true;",
@ -4955,7 +4979,7 @@
"name": "{i18n:api-usage.view-statistics}",
"buttonType": "icon",
"icon": "show_chart",
"buttonColor": "rgba(0, 0, 0, 0.87)",
"buttonColor": "rgba(0, 0, 0, 0.54)",
"customButtonStyle": {},
"useShowWidgetActionFunction": null,
"showWidgetActionFunction": "return true;",

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

@ -639,7 +639,7 @@
"delete-alarms-text": "Are you sure you want to delete { count, plural, =1 {1 alarm} other {# alarms} }?",
"selected-alarms-are-cleared": "Selected alarms are already cleared",
"alarm-status-filter": "Alarm Status Filter",
"alarm-filter-title": "Alarm Filter",
"alarm-filter-title": "Alarm filter",
"assigned": "Assigned",
"filter-title": "Filter",
"max-count-load": "Maximum number of alarms to load (0 - unlimited)",
@ -1080,12 +1080,10 @@
"type-timeseries-updated": "Telemetry updated",
"type-timeseries-deleted": "Telemetry deleted",
"type-sms-sent": "SMS sent",
"any":"Any",
"filter":"Audit Log Filters",
"audit-log-filter-title": "Audit Log Filter",
"any-type":"Any type",
"audit-log-filter-title": "Audit log filter",
"filter-title": "Filter",
"filter-type-add": "+ Add type",
"filter-types": "Audit Log Types"
"filter-types": "Audit log types"
},
"debug-settings": {
"label": "Debug Configuration",
@ -1219,10 +1217,10 @@
"deduplication-interval": "Deduplication interval",
"deduplication-interval-min": "Deduplication interval should be at least {{ sec }} seconds.",
"deduplication-interval-required": "Deduplication interval is required.",
"calculated-fields-filter": "Calculated fields filter",
"calculated-field-filter-title": "Calculated field filter",
"filter-title": "Filter",
"calculated-field-types": "Calculated field types",
"any-type": "Any type",
"target-entity": "Target entity",
"metrics": {
"metrics": "Metrics",
"metrics-empty": "At least one metric must be configured.",
@ -1402,10 +1400,7 @@
"export": "Export alarm rule",
"export-failed-error": "Unable to export alarm rule: {{error}}",
"entity-type": "Entity type",
"target-entity-type": "Target entity type",
"entity-type-required": "Entity type is required.",
"target-entity": "Target entity",
"target-entities": "Target entities",
"alarm-type": "Alarm type",
"alarm-type-hint": "Unique identifier (e.g., HighTempAlarm) across the scope of the alarm originator (Device, Asset, etc.) to prevent conflicts.",
"alarm-type-required": "Alarm type is required.",
@ -1526,7 +1521,8 @@
"alarm-rule-relation-types-list-hint": "Defines relation types to filter the related entities. If not set, the alarm will be propagated to all related entities.",
"propagate-alarm-to-owner": "Propagate alarm to entity owner (Customer or Tenant)",
"propagate-alarm-to-tenant": "Propagate alarm to Tenant",
"alarm-rule-filter-title": "Filter",
"alarm-rule-filter-title": "Alarm rule filter",
"filter-title": "Filter",
"debugging": "Alarm rule debugging",
"any-type": "Any type",
"enter-alarm-rule-type": "Enter alarm type",
@ -2247,8 +2243,7 @@
"unable-delete-device-alias-text": "Device alias '{{deviceAlias}}' can't be deleted as it used by the following widget(s):<br/>{{widgetsList}}",
"is-gateway": "Is gateway",
"overwrite-activity-time": "Overwrite activity time for connected device",
"device-filter": "Device filter",
"device-filter-title": "Device Filter",
"device-filter-title": "Device filter",
"filter-title": "Filter",
"device-state": "Device state",
"state": "State",

Loading…
Cancel
Save