Browse Source

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

pull/14641/head
Mazurenko Nikita 10 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); Path resourcesDir = Path.of(getDataDir(), RESOURCES_DIR);
loadSystemResources(resourcesDir.resolve("images"), ResourceType.IMAGE, null); 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); 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") @Schema(description = "Alarm originator label", example = "Thermostat label")
private String originatorLabel; private String originatorLabel;
@Getter
@Setter @Setter
@Schema(description = "Originator display name", example = "Thermostat") @Schema(description = "Originator display name", example = "Thermostat")
private String originatorDisplayName; private String originatorDisplayName;
@ -59,10 +58,15 @@ public class AlarmInfo extends Alarm {
super(alarm); super(alarm);
} }
public String getOriginatorDisplayName() {
return originatorDisplayName != null ? originatorDisplayName : (originatorLabel != null ? originatorLabel : originatorName);
}
public AlarmInfo(AlarmInfo alarmInfo) { public AlarmInfo(AlarmInfo alarmInfo) {
super(alarmInfo); super(alarmInfo);
this.originatorName = alarmInfo.getOriginatorName(); this.originatorName = alarmInfo.getOriginatorName();
this.originatorLabel = alarmInfo.getOriginatorLabel(); this.originatorLabel = alarmInfo.getOriginatorLabel();
this.originatorDisplayName = alarmInfo.getOriginatorDisplayName();
this.assignee = alarmInfo.getAssignee(); 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_NAME_PROPERTY = "originator_name";
public static final String ALARM_ORIGINATOR_LABEL_PROPERTY = "originator_label"; 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_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_SEVERITY_PROPERTY = "severity";
public static final String ALARM_ASSIGNEE_ID_PROPERTY = "assignee_id"; public static final String ALARM_ASSIGNEE_ID_PROPERTY = "assignee_id";
public static final String ALARM_ASSIGNEE_FIRST_NAME_PROPERTY = "assignee_first_name"; 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; String originatorName = originatorNameObj != null ? originatorNameObj.toString() : null;
Object originatorLabelObj = row.get(ModelConstants.ALARM_ORIGINATOR_LABEL_PROPERTY); Object originatorLabelObj = row.get(ModelConstants.ALARM_ORIGINATOR_LABEL_PROPERTY);
String originatorLabel = originatorLabelObj != null ? originatorLabelObj.toString() : null; 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 alarmData = new AlarmData(alarm, entityId);
alarmData.setOriginatorName(originatorName); alarmData.setOriginatorName(originatorName);
alarmData.setOriginatorLabel(originatorLabel); alarmData.setOriginatorLabel(originatorLabel);
alarmData.setOriginatorDisplayName(StringUtils.isBlank(originatorLabel) ? originatorName : originatorLabel); alarmData.setOriginatorDisplayName(originatorDisplayName);
if (alarm.getAssigneeId() != null) { if (alarm.getAssigneeId() != null) {
alarmData.setAssignee(new AlarmAssignee(alarm.getAssigneeId(), assigneeFirstName, assigneeLastName, assigneeEmail)); 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_FIRST_NAME_KEY, ModelConstants.ALARM_ASSIGNEE_FIRST_NAME_PROPERTY);
alarmFieldColumnMap.put(ASSIGNEE_LAST_NAME_KEY, ModelConstants.ALARM_ASSIGNEE_LAST_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(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," + 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.type as type, " +
" a.originator_name as originator_name, " + " a.originator_name as originator_name, " +
" a.originator_label as originator_label, " + " 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_first_name as assignee_first_name, " +
" a.assignee_last_name as assignee_last_name, " + " a.assignee_last_name as assignee_last_name, " +
" a.assignee_email as assignee_email, " + " 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" class="flex-1"
showLabel showLabel
required required
label="{{ 'alarm-rule.target-entity-type' | translate }}" label="{{ 'entity.entity-type' | translate }}"
[filterAllowedEntityTypes]="false" [filterAllowedEntityTypes]="false"
[allowedEntityTypes]="alarmRuleEntityTypeList" [allowedEntityTypes]="alarmRuleEntityTypeList"
formControlName="entityType"> 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> </tb-entity-subtype-list>
</div> </div>
<div class="tb-form-row column-xs"> <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-form-field class="flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="entityType" placeholder="{{ 'alarm-rule.any-type' | translate }}"> <mat-select formControlName="entityType" placeholder="{{ 'alarm-rule.any-type' | translate }}">
<mat-option>{{ 'alarm-rule.any-type' | translate }}</mat-option> <mat-option>{{ 'alarm-rule.any-type' | translate }}</mat-option>
@ -78,7 +78,7 @@
</div> </div>
@if (alarmRuleFilterConfigForm.get('entityType').value) { @if (alarmRuleFilterConfigForm.get('entityType').value) {
<div class="tb-form-row column-xs"> <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" <tb-entity-list appearance="outline"
subscriptSizing="dynamic" subscriptSizing="dynamic"
class="flex flex-1" 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 { ::ng-deep {
.tb-alarm-filter-config-component { .tb-alarm-filter-config-component {
max-width: 100%; max-width: 100%;
width: 600px; 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() @Input()
buttonMode = true; buttonMode = true;
@coerceBoolean()
@Input()
userMode = false;
@coerceBoolean()
@Input()
propagatedFilter = true;
@Input() @Input()
initialAlarmRuleFilterConfig: AlarmRuleFilterConfig = { initialAlarmRuleFilterConfig: AlarmRuleFilterConfig = {
name: [], name: [],
@ -124,7 +116,6 @@ export class AlarmRuleFilterConfigComponent implements OnInit, ControlValueAcces
ngOnInit(): void { ngOnInit(): void {
if (this.data) { if (this.data) {
this.panelMode = this.data.panelMode; this.panelMode = this.data.panelMode;
this.userMode = this.data.userMode;
this.alarmRuleFilterConfig = this.data.alarmRuleFilterConfig; this.alarmRuleFilterConfig = this.data.alarmRuleFilterConfig;
this.initialAlarmRuleFilterConfig = this.data.initialAlarmRuleFilterConfig; this.initialAlarmRuleFilterConfig = this.data.initialAlarmRuleFilterConfig;
if (this.panelMode && !this.initialAlarmRuleFilterConfig) { if (this.panelMode && !this.initialAlarmRuleFilterConfig) {
@ -248,7 +239,7 @@ export class AlarmRuleFilterConfigComponent implements OnInit, ControlValueAcces
if (!isArraysEqualIgnoreUndefined(filter1.entities, filter2.entities)) { if (!isArraysEqualIgnoreUndefined(filter1.entities, filter2.entities)) {
return false; return false;
} }
return filter1.entityType !== filter2.entityType; return filter1.entityType === filter2.entityType;
} }
return false; return false;
}; };
@ -287,7 +278,7 @@ export class AlarmRuleFilterConfigComponent implements OnInit, ControlValueAcces
if (!filterTextParts.length) { if (!filterTextParts.length) {
this.buttonDisplayValue = this.translate.instant('alarm-rule.alarm-rule-filter-title'); this.buttonDisplayValue = this.translate.instant('alarm-rule.alarm-rule-filter-title');
} else { } 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.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC};
this.columns.push(new DateEntityTableColumn<CalculatedFieldAlarmRule>('createdTime', 'common.created-time', this.datePipe, '150px')); 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))); entity => this.utilsService.customTranslation(entity.name, entity.name)));
if (this.pageMode) { 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))); 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 => this.utilsService.customTranslation(entity['entityName'], entity['entityName']),
entity => getEntityDetailsPageURL(entity.entityId?.id, entity.entityId?.entityType as EntityType), false)); 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(', '), entity => Object.keys(entity.configuration.createRules).map((severity) => this.translate.instant(alarmSeverityTranslations.get(severity as AlarmSeverity))).join(', '),
() => ({}), false)); () => ({}), false));
this.columns.push(new EntityTableColumn<CalculatedFieldAlarmRule>('clearRule', 'alarm-rule.cleared', '90px', 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; alarmSeverityTranslationMap = alarmSeverityTranslations;
buttonDisplayValue = this.translate.instant('alarm.alarm-filter'); buttonDisplayValue = this.translate.instant('alarm.alarm-filter-title');
alarmFilterConfigForm: UntypedFormGroup; 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}} <mat-icon>filter_list</mat-icon>{{buttonDisplayValue}}
</button> </button>
<ng-template #auditLogFilterPanel> <ng-template #auditLogFilterPanel>
<form class="mat-content mat-padding flex flex-col" [formGroup]="auditLogFilterForm" (ngSubmit)="update()"> <form class="tb-audit-log-config-component 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-panel no-padding no-border flex-1">
<div class="tb-form-row flex-1 space-between column-xs"> <div class="tb-form-row space-between column-xs flex-1">
<div class="fixed-title-width" translate>audit-log.filter-types</div> <div class="fixed-title-width" translate>audit-log.filter-types</div>
<tb-string-items-list appearance="outline" <tb-string-items-list appearance="outline"
class="flex flex-1 gt-xs:w-64" class="flex flex-1 gt-xs:w-64"
subscriptSizing="dynamic" subscriptSizing="dynamic"
[placeholder]="'audit-log.filter-type-add' | translate" placeholder="{{ !auditLogFilterForm.get('actionTypes').value?.length ? ('audit-log.any-type' | translate) : '' }}"
[predefinedValues]="predefinedTypeValues" [predefinedValues]="predefinedTypeValues"
formControlName="actionTypes"> formControlName="actionTypes">
</tb-string-items-list> </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 { Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
import { TemplatePortal } from '@angular/cdk/portal'; 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 { 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 { isDefined, isDefinedAndNotNull } from '@app/core/utils';
import { StringItemsOption } from '@app/shared/components/string-items-list.component'; import { StringItemsOption } from '@app/shared/components/string-items-list.component';
import { import {
ActionType, ActionType,
actionTypeTranslations, actionTypeTranslations,
auditLogFilterEquals,
AuditLogFilter, AuditLogFilter,
auditLogFilterEquals,
POSITION_MAP, POSITION_MAP,
} from '@app/shared/public-api'; } from '@app/shared/public-api';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
@ -56,9 +67,9 @@ export class AuditLogFilterComponent implements OnInit, ControlValueAccessor {
actionTypeTranslations = actionTypeTranslations; 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; auditLogOverlayRef: OverlayRef;
@ -68,7 +79,7 @@ export class AuditLogFilterComponent implements OnInit, ControlValueAccessor {
private propagateChange = (_: any) => {}; private propagateChange = (_: any) => {};
constructor(private fb: UntypedFormBuilder, constructor(private fb: FormBuilder,
private translate: TranslateService, private translate: TranslateService,
private overlay: Overlay, private overlay: Overlay,
private nativeElement: ElementRef, private nativeElement: ElementRef,
@ -93,7 +104,7 @@ export class AuditLogFilterComponent implements OnInit, ControlValueAccessor {
this.propagateChange = fn; this.propagateChange = fn;
} }
registerOnTouched(fn: any): void { registerOnTouched(_fn: any): void {
} }
setDisabledState(isDisabled: boolean): 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%', this.columns.push(new EntityTableColumn<CalculatedField>('name', 'common.name', this.pageMode ? '33%' : '60%',
entity => this.utilsService.customTranslation(entity.name, entity.name))); entity => this.utilsService.customTranslation(entity.name, entity.name)));
if (this.pageMode) { 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 => this.utilsService.customTranslation(entity['entityName'], entity['entityName']),
entity => getEntityDetailsPageURL(entity.entityId?.id, entity.entityId?.entityType as EntityType), false)); 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( 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" class="flex-1"
showLabel showLabel
required required
label="{{ 'alarm-rule.target-entity-type' | translate }}" label="{{ 'entity.entity-type' | translate }}"
[filterAllowedEntityTypes]="false" [filterAllowedEntityTypes]="false"
[allowedEntityTypes]="calculatedFieldsEntityTypeList" [allowedEntityTypes]="calculatedFieldsEntityTypeList"
formControlName="entityType"> 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-template #componentMode>
<ng-container *ngIf="buttonMode; else calculatedFieldsFilter"> <ng-container *ngIf="buttonMode; else calculatedFieldsFilter">
<button color="primary" <button color="primary"
matTooltip="{{ 'calculated-fields.calculated-fields-filter' | translate }}" matTooltip="{{buttonDisplayValue}}"
matTooltipPosition="above" matTooltipPosition="above"
mat-stroked-button mat-stroked-button
(click)="toggleCfFilterPanel($event)"> (click)="toggleCfFilterPanel($event)">
<mat-icon>filter_list</mat-icon>{{ 'calculated-fields.calculated-fields-filter' | translate }} <mat-icon>filter_list</mat-icon>{{buttonDisplayValue}}
</button> </button>
</ng-container> </ng-container>
</ng-template> </ng-template>
<ng-template #calculatedFieldsFilterPanel> <ng-template #calculatedFieldsFilterPanel>
<form class="mat-content mat-padding flex flex-col" (ngSubmit)="update()"> <form class="mat-content mat-padding flex flex-col" (ngSubmit)="update()">
<ng-container *ngTemplateOutlet="calculatedFieldsFilter"></ng-container> <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" <button type="button"
mat-button mat-button
color="primary" color="primary"
@ -68,7 +68,7 @@
</tb-string-items-list> </tb-string-items-list>
</div> </div>
<div class="tb-form-row column-xs"> <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-form-field class="flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="entityType" placeholder="{{ 'alarm-rule.any-type' | translate }}"> <mat-select formControlName="entityType" placeholder="{{ 'alarm-rule.any-type' | translate }}">
<mat-option>{{ 'alarm-rule.any-type' | translate }}</mat-option> <mat-option>{{ 'alarm-rule.any-type' | translate }}</mat-option>
@ -80,7 +80,7 @@
</div> </div>
@if (cfFilterForm.get('entityType').value) { @if (cfFilterForm.get('entityType').value) {
<div class="tb-form-row column-xs"> <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" <tb-entity-list appearance="outline"
subscriptSizing="dynamic" subscriptSizing="dynamic"
class="flex flex-1" 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. * limitations under the License.
*/ */
@import '../scss/constants';
:host { :host {
display: flex; display: flex;
max-width: 100%; max-width: 100%;
@ -42,15 +40,5 @@
width: 600px; width: 600px;
min-width: 100%; min-width: 100%;
flex: 1; 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() @Input()
buttonMode = true; buttonMode = true;
@coerceBoolean()
@Input()
userMode = false;
@coerceBoolean()
@Input()
propagatedFilter = true;
@Input() @Input()
initialCfFilterConfig: CalculatedFieldsQuery = { initialCfFilterConfig: CalculatedFieldsQuery = {
types: [], types: [],
@ -96,6 +88,8 @@ export class CalculatedFieldsFilterConfigComponent implements OnInit, ControlVal
panelMode = false; panelMode = false;
buttonDisplayValue = this.translate.instant('calculated-fields.calculated-field-filter-title');
cfFilterForm: FormGroup; cfFilterForm: FormGroup;
panelResult: CalculatedFieldsQuery = null; panelResult: CalculatedFieldsQuery = null;
@ -130,7 +124,6 @@ export class CalculatedFieldsFilterConfigComponent implements OnInit, ControlVal
ngOnInit(): void { ngOnInit(): void {
if (this.data) { if (this.data) {
this.panelMode = this.data.panelMode; this.panelMode = this.data.panelMode;
this.userMode = this.data.userMode;
this.cfFilterConfig = this.data.filterConfig; this.cfFilterConfig = this.data.filterConfig;
this.initialCfFilterConfig = this.data.initialFilterConfig; this.initialCfFilterConfig = this.data.initialFilterConfig;
if (this.panelMode && !this.initialCfFilterConfig) { if (this.panelMode && !this.initialCfFilterConfig) {
@ -177,6 +170,7 @@ export class CalculatedFieldsFilterConfigComponent implements OnInit, ControlVal
if (!this.initialCfFilterConfig && cfFilterConfig) { if (!this.initialCfFilterConfig && cfFilterConfig) {
this.initialCfFilterConfig = deepClone(cfFilterConfig); this.initialCfFilterConfig = deepClone(cfFilterConfig);
} }
this.updateButtonDisplayValue();
this.updateCfConfigForm(cfFilterConfig); this.updateCfConfigForm(cfFilterConfig);
} }
@ -253,7 +247,7 @@ export class CalculatedFieldsFilterConfigComponent implements OnInit, ControlVal
if (!isArraysEqualIgnoreUndefined(filter1.entities, filter2.entities)) { if (!isArraysEqualIgnoreUndefined(filter1.entities, filter2.entities)) {
return false; return false;
} }
return filter1.entityType !== filter2.entityType; return filter1.entityType === filter2.entityType;
} }
return false; return false;
}; };
@ -268,9 +262,27 @@ export class CalculatedFieldsFilterConfigComponent implements OnInit, ControlVal
private cfConfigUpdated(formValue: any) { private cfConfigUpdated(formValue: any) {
this.cfFilterConfig = this.cfFilterFromFormValue(formValue); this.cfFilterConfig = this.cfFilterFromFormValue(formValue);
this.updateButtonDisplayValue();
this.propagateChange(this.cfFilterConfig); 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 { private cfFilterFromFormValue(formValue: any): CalculatedFieldsQuery {
return { return {
types: formValue?.types ?? [], 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()"> <form class="mat-content mat-padding flex flex-col" (ngSubmit)="update()">
<ng-container *ngTemplateOutlet="deviceFilter"></ng-container> <ng-container *ngTemplateOutlet="deviceFilter"></ng-container>
<div class="tb-panel-actions flex flex-row items-center justify-end"> <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" <button type="button"
mat-button mat-button
(click)="cancel()"> (click)="cancel()">
@ -48,28 +55,28 @@
</form> </form>
</ng-template> </ng-template>
<ng-template #deviceFilter> <ng-template #deviceFilter>
<div class="flex flex-col gap-4" style="min-width: 280px;" [formGroup]="deviceInfoFilterForm"> <div class="tb-device-filter-config-component tb-form-panel no-padding no-border" [formGroup]="deviceInfoFilterForm">
<tb-device-profile-autocomplete <div class="tb-form-row column-xs">
subscriptSizing="dynamic" <div class="fixed-title-width" translate>device-profile.device-profile</div>
formControlName="deviceProfileId" <tb-device-profile-autocomplete
[displayAllOnEmpty]="true" class="flex-1"
(deviceProfileChanged)="deviceProfileChanged($event)" formControlName="deviceProfileId"
[editProfileEnabled]="false" [inlineField]="true"
[addNewProfile]="false"> [displayAllOnEmpty]="true"
</tb-device-profile-autocomplete> (deviceProfileChanged)="deviceProfileChanged($event)"
<mat-form-field> [editProfileEnabled]="false"
<mat-label translate>device.device-state</mat-label> [addNewProfile]="false">
<mat-select formControlName="active"> </tb-device-profile-autocomplete>
<mat-option value=""> </div>
{{ 'device.any' | translate }} <div class="tb-form-row column-xs">
</mat-option> <div class="fixed-title-width" translate>device.device-state</div>
<mat-option [value]="true"> <mat-form-field class="flex-1" appearance="outline" subscriptSizing="dynamic">
{{ 'device.active' | translate }} <mat-select formControlName="active" placeholder="{{ 'device.any' | translate }}">
</mat-option> <mat-option>{{ 'device.any' | translate }}</mat-option>
<mat-option [value]="false"> <mat-option [value]="true">{{ 'device.active' | translate }}</mat-option>
{{ 'device.inactive' | translate }} <mat-option [value]="false">{{ 'device.inactive' | translate }}</mat-option>
</mat-option> </mat-select>
</mat-select> </mat-form-field>
</mat-form-field> </div>
</div> </div>
</ng-template> </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, Inject,
InjectionToken, InjectionToken,
Input, Input,
OnDestroy,
OnInit, OnInit,
Optional, Optional,
TemplateRef, TemplateRef,
ViewChild, ViewChild,
ViewContainerRef ViewContainerRef
} from '@angular/core'; } 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 { coerceBoolean } from '@shared/decorators/coercion';
import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
import { TemplatePortal } from '@angular/cdk/portal'; import { TemplatePortal } from '@angular/cdk/portal';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { DeviceInfoFilter } from '@shared/models/device.models'; 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 { EntityInfoData } from '@shared/models/entity.models';
import { DeviceProfileService } from '@core/http/device-profile.service'; import { DeviceProfileService } from '@core/http/device-profile.service';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { fromEvent, Subscription } from 'rxjs';
export const DEVICE_FILTER_CONFIG_DATA = new InjectionToken<any>('DeviceFilterConfigData'); export const DEVICE_FILTER_CONFIG_DATA = new InjectionToken<any>('DeviceFilterConfigData');
export interface DeviceFilterConfigData { export interface DeviceFilterConfigData {
panelMode: boolean; panelMode: boolean;
deviceInfoFilter: DeviceInfoFilter; deviceInfoFilter: DeviceInfoFilter;
initialFilterConfig?: DeviceInfoFilter;
} }
// @dynamic // @dynamic
@ -61,7 +62,7 @@ export interface DeviceFilterConfigData {
} }
] ]
}) })
export class DeviceInfoFilterComponent implements OnInit, OnDestroy, ControlValueAccessor { export class DeviceInfoFilterComponent implements OnInit, ControlValueAccessor {
@ViewChild('deviceFilterPanel') @ViewChild('deviceFilterPanel')
deviceFilterPanel: TemplateRef<any>; deviceFilterPanel: TemplateRef<any>;
@ -72,19 +73,25 @@ export class DeviceInfoFilterComponent implements OnInit, OnDestroy, ControlValu
@Input() @Input()
buttonMode = true; buttonMode = true;
@Input()
initialDeviceFilterConfig: DeviceInfoFilter = {
deviceProfileId: null,
active: null
};
panelMode = false; panelMode = false;
buttonDisplayValue = this.translate.instant('device.device-filter'); buttonDisplayValue = this.translate.instant('device.device-filter-title');
deviceInfoFilterForm: UntypedFormGroup; deviceInfoFilterForm: FormGroup;
deviceFilterOverlayRef: OverlayRef; deviceFilterOverlayRef: OverlayRef;
panelResult: DeviceInfoFilter = null; panelResult: DeviceInfoFilter = null;
private deviceProfileInfo: EntityInfoData; private deviceProfileInfo: EntityInfoData;
private deviceInfoFilter: DeviceInfoFilter; private deviceInfoFilter: DeviceInfoFilter;
private resizeWindows: Subscription;
private propagateChange = (_: any) => {}; private propagateChange = (_: any) => {};
@ -92,7 +99,7 @@ export class DeviceInfoFilterComponent implements OnInit, OnDestroy, ControlValu
private data: DeviceFilterConfigData | undefined, private data: DeviceFilterConfigData | undefined,
@Optional() @Optional()
private overlayRef: OverlayRef, private overlayRef: OverlayRef,
private fb: UntypedFormBuilder, private fb: FormBuilder,
private translate: TranslateService, private translate: TranslateService,
private overlay: Overlay, private overlay: Overlay,
private nativeElement: ElementRef, private nativeElement: ElementRef,
@ -106,6 +113,10 @@ export class DeviceInfoFilterComponent implements OnInit, OnDestroy, ControlValu
if (this.data) { if (this.data) {
this.panelMode = this.data.panelMode; this.panelMode = this.data.panelMode;
this.deviceInfoFilter = this.data.deviceInfoFilter; 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({ this.deviceInfoFilterForm = this.fb.group({
deviceProfileId: [null, []], deviceProfileId: [null, []],
@ -115,7 +126,6 @@ export class DeviceInfoFilterComponent implements OnInit, OnDestroy, ControlValu
takeUntilDestroyed(this.destroyRef) takeUntilDestroyed(this.destroyRef)
).subscribe( ).subscribe(
() => { () => {
this.updateValidators();
if (!this.buttonMode) { if (!this.buttonMode) {
this.deviceFilterUpdated(this.deviceInfoFilterForm.value); this.deviceFilterUpdated(this.deviceInfoFilterForm.value);
} }
@ -126,14 +136,11 @@ export class DeviceInfoFilterComponent implements OnInit, OnDestroy, ControlValu
} }
} }
ngOnDestroy(): void {
}
registerOnChange(fn: any): void { registerOnChange(fn: any): void {
this.propagateChange = fn; this.propagateChange = fn;
} }
registerOnTouched(fn: any): void { registerOnTouched(_fn: any): void {
} }
setDisabledState(isDisabled: boolean): void { setDisabledState(isDisabled: boolean): void {
@ -142,19 +149,18 @@ export class DeviceInfoFilterComponent implements OnInit, OnDestroy, ControlValu
this.deviceInfoFilterForm.disable({emitEvent: false}); this.deviceInfoFilterForm.disable({emitEvent: false});
} else { } else {
this.deviceInfoFilterForm.enable({emitEvent: false}); this.deviceInfoFilterForm.enable({emitEvent: false});
this.updateValidators();
} }
} }
writeValue(deviceInfoFilter?: DeviceInfoFilter): void { writeValue(deviceInfoFilter?: DeviceInfoFilter): void {
this.deviceInfoFilter = deviceInfoFilter; this.deviceInfoFilter = deviceInfoFilter;
if (!this.initialDeviceFilterConfig && deviceInfoFilter) {
this.initialDeviceFilterConfig = deepClone(deviceInfoFilter);
}
this.updateButtonDisplayValue(); this.updateButtonDisplayValue();
this.updateDeviceInfoFilterForm(deviceInfoFilter); this.updateDeviceInfoFilterForm(deviceInfoFilter);
} }
private updateValidators() {
}
toggleDeviceFilterPanel($event: Event) { toggleDeviceFilterPanel($event: Event) {
if ($event) { if ($event) {
$event.stopPropagation(); $event.stopPropagation();
@ -183,6 +189,9 @@ export class DeviceInfoFilterComponent implements OnInit, OnDestroy, ControlValu
}); });
this.deviceFilterOverlayRef.attach(new TemplatePortal(this.deviceFilterPanel, this.deviceFilterOverlayRef.attach(new TemplatePortal(this.deviceFilterPanel,
this.viewContainerRef)); this.viewContainerRef));
this.resizeWindows = fromEvent(window, 'resize').subscribe(() => {
this.deviceFilterOverlayRef.updatePosition();
});
} }
cancel() { cancel() {
@ -190,40 +199,62 @@ export class DeviceInfoFilterComponent implements OnInit, OnDestroy, ControlValu
if (this.overlayRef) { if (this.overlayRef) {
this.overlayRef.dispose(); this.overlayRef.dispose();
} else { } else {
this.resizeWindows.unsubscribe();
this.deviceFilterOverlayRef.dispose(); this.deviceFilterOverlayRef.dispose();
} }
} }
update() { update() {
this.deviceFilterUpdated(this.deviceInfoFilterForm.value); this.deviceFilterUpdated(this.deviceInfoFilterForm.value);
this.deviceInfoFilterForm.markAsPristine();
if (this.panelMode) { if (this.panelMode) {
this.panelResult = this.deviceInfoFilter; this.panelResult = this.deviceInfoFilter;
} }
if (this.overlayRef) { if (this.overlayRef) {
this.overlayRef.dispose(); this.overlayRef.dispose();
} else { } else {
this.resizeWindows.unsubscribe();
this.deviceFilterOverlayRef.dispose(); 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) { deviceProfileChanged(deviceProfileInfo: EntityInfoData) {
this.deviceProfileInfo = deviceProfileInfo; this.deviceProfileInfo = deviceProfileInfo;
this.updateButtonDisplayValue(); 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) { private updateDeviceInfoFilterForm(deviceInfoFilter?: DeviceInfoFilter) {
this.deviceInfoFilterForm.patchValue({ this.deviceInfoFilterForm.patchValue({
deviceProfileId: deviceInfoFilter?.deviceProfileId, deviceProfileId: deviceInfoFilter?.deviceProfileId ?? null,
active: isDefinedAndNotNull(deviceInfoFilter?.active) ? deviceInfoFilter?.active : '' active: deviceInfoFilter?.active ?? null
}, {emitEvent: false}); }, {emitEvent: false});
this.updateValidators();
} }
private deviceFilterUpdated(deviceInfoFilter: DeviceInfoFilter) { private deviceFilterUpdated(deviceInfoFilter: DeviceInfoFilter) {
this.deviceInfoFilter = deviceInfoFilter; this.deviceInfoFilter = this.deviceFilterFromFormValue(deviceInfoFilter);
if ((this.deviceInfoFilter.active as any) === '') {
this.deviceInfoFilter.active = null;
}
this.updateButtonDisplayValue(); this.updateButtonDisplayValue();
this.propagateChange(this.deviceInfoFilter); 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. limitations under the License.
--> -->
<mat-form-field [formGroup]="selectDeviceProfileFormGroup" class="mat-block" subscriptSizing="{{ subscriptSizing }}"> <mat-form-field [formGroup]="selectDeviceProfileFormGroup"
<mat-label>{{ 'device-profile.device-profile' | translate }}</mat-label> [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" <input matInput type="text"
#deviceProfileInput #deviceProfileInput
formControlName="deviceProfile" 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 { AddDeviceProfileDialogComponent, AddDeviceProfileDialogData } from './add-device-profile-dialog.component';
import { emptyPageData } from '@shared/models/page/page-data'; import { emptyPageData } from '@shared/models/page/page-data';
import { getEntityDetailsPageURL } from '@core/utils'; 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 { coerceBoolean } from '@shared/decorators/coercion';
import { AuthUser } from '@shared/models/user.model'; import { AuthUser } from '@shared/models/user.model';
import { getCurrentAuthUser } from '@core/auth/auth.selectors'; import { getCurrentAuthUser } from '@core/auth/auth.selectors';
@ -69,6 +69,9 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor,
modelValue: DeviceProfileId | null; modelValue: DeviceProfileId | null;
@Input()
appearance: MatFormFieldAppearance = 'fill';
@Input() @Input()
subscriptSizing: SubscriptSizing = 'fixed'; subscriptSizing: SubscriptSizing = 'fixed';
@ -106,6 +109,10 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor,
@Input() @Input()
hint: string; hint: string;
@Input()
@coerceBoolean()
inlineField: boolean;
@Output() @Output()
deviceProfileUpdated = new EventEmitter<DeviceProfileId>(); 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); yAxisSettingsList.sort((a1, a2) => a1.order - a2.order);
const axisLimitDatasources: Datasource[] = []; const axisLimitDatasources: Datasource[] = [];
for (const yAxisSettings of yAxisSettingsList) { for (const yAxisSettings of yAxisSettingsList) {
yAxisSettings.min = this.normalizeAxisLimit(yAxisSettings.min);
yAxisSettings.max = this.normalizeAxisLimit(yAxisSettings.max);
const axisSettings = mergeDeep<TimeSeriesChartYAxisSettings>({} as TimeSeriesChartYAxisSettings, const axisSettings = mergeDeep<TimeSeriesChartYAxisSettings>({} as TimeSeriesChartYAxisSettings,
defaultTimeSeriesChartYAxisSettings, yAxisSettings); defaultTimeSeriesChartYAxisSettings, yAxisSettings);
const units = isNotEmptyTbUnits(axisSettings.units) ? axisSettings.units : this.ctx.units; const units = isNotEmptyTbUnits(axisSettings.units) ? axisSettings.units : this.ctx.units;
@ -1078,4 +1080,21 @@ export class TbTimeSeriesChart {
this.timeSeriesChart.setOption(this.timeSeriesChartOptions); 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": "", "noDataDisplayMessage": "",
"configMode": "basic", "configMode": "basic",
"borderRadius": "12px", "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", "id": "a669cf86-e715-efa4-dd9a-b839abf499e9",
"typeFullFqn": "system.cards.timeseries_table" "typeFullFqn": "system.cards.timeseries_table"
@ -4519,7 +4543,7 @@
"name": "{i18n:api-usage.view-statistics}", "name": "{i18n:api-usage.view-statistics}",
"buttonType": "icon", "buttonType": "icon",
"icon": "show_chart", "icon": "show_chart",
"buttonColor": "rgba(0, 0, 0, 0.87)", "buttonColor": "rgba(0, 0, 0, 0.54)",
"customButtonStyle": {}, "customButtonStyle": {},
"useShowWidgetActionFunction": null, "useShowWidgetActionFunction": null,
"showWidgetActionFunction": "return true;", "showWidgetActionFunction": "return true;",
@ -4955,7 +4979,7 @@
"name": "{i18n:api-usage.view-statistics}", "name": "{i18n:api-usage.view-statistics}",
"buttonType": "icon", "buttonType": "icon",
"icon": "show_chart", "icon": "show_chart",
"buttonColor": "rgba(0, 0, 0, 0.87)", "buttonColor": "rgba(0, 0, 0, 0.54)",
"customButtonStyle": {}, "customButtonStyle": {},
"useShowWidgetActionFunction": null, "useShowWidgetActionFunction": null,
"showWidgetActionFunction": "return true;", "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} }?", "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", "selected-alarms-are-cleared": "Selected alarms are already cleared",
"alarm-status-filter": "Alarm Status Filter", "alarm-status-filter": "Alarm Status Filter",
"alarm-filter-title": "Alarm Filter", "alarm-filter-title": "Alarm filter",
"assigned": "Assigned", "assigned": "Assigned",
"filter-title": "Filter", "filter-title": "Filter",
"max-count-load": "Maximum number of alarms to load (0 - unlimited)", "max-count-load": "Maximum number of alarms to load (0 - unlimited)",
@ -1080,12 +1080,10 @@
"type-timeseries-updated": "Telemetry updated", "type-timeseries-updated": "Telemetry updated",
"type-timeseries-deleted": "Telemetry deleted", "type-timeseries-deleted": "Telemetry deleted",
"type-sms-sent": "SMS sent", "type-sms-sent": "SMS sent",
"any":"Any", "any-type":"Any type",
"filter":"Audit Log Filters", "audit-log-filter-title": "Audit log filter",
"audit-log-filter-title": "Audit Log Filter",
"filter-title": "Filter", "filter-title": "Filter",
"filter-type-add": "+ Add type", "filter-types": "Audit log types"
"filter-types": "Audit Log Types"
}, },
"debug-settings": { "debug-settings": {
"label": "Debug Configuration", "label": "Debug Configuration",
@ -1219,10 +1217,10 @@
"deduplication-interval": "Deduplication interval", "deduplication-interval": "Deduplication interval",
"deduplication-interval-min": "Deduplication interval should be at least {{ sec }} seconds.", "deduplication-interval-min": "Deduplication interval should be at least {{ sec }} seconds.",
"deduplication-interval-required": "Deduplication interval is required.", "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", "calculated-field-types": "Calculated field types",
"any-type": "Any type", "any-type": "Any type",
"target-entity": "Target entity",
"metrics": { "metrics": {
"metrics": "Metrics", "metrics": "Metrics",
"metrics-empty": "At least one metric must be configured.", "metrics-empty": "At least one metric must be configured.",
@ -1402,10 +1400,7 @@
"export": "Export alarm rule", "export": "Export alarm rule",
"export-failed-error": "Unable to export alarm rule: {{error}}", "export-failed-error": "Unable to export alarm rule: {{error}}",
"entity-type": "Entity type", "entity-type": "Entity type",
"target-entity-type": "Target entity type",
"entity-type-required": "Entity type is required.", "entity-type-required": "Entity type is required.",
"target-entity": "Target entity",
"target-entities": "Target entities",
"alarm-type": "Alarm type", "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-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.", "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.", "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-owner": "Propagate alarm to entity owner (Customer or Tenant)",
"propagate-alarm-to-tenant": "Propagate alarm to 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", "debugging": "Alarm rule debugging",
"any-type": "Any type", "any-type": "Any type",
"enter-alarm-rule-type": "Enter alarm 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}}", "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", "is-gateway": "Is gateway",
"overwrite-activity-time": "Overwrite activity time for connected device", "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", "filter-title": "Filter",
"device-state": "Device state", "device-state": "Device state",
"state": "State", "state": "State",

Loading…
Cancel
Save