Commit d9321b48169f8dd22e310946b648a7aa1bbd3ca0

Authored by Igor Kulikov
1 parent 3b55ebe5

UI: Device profile alarm rules

Showing 44 changed files with 710 additions and 336 deletions
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.query;
  17 +
  18 +public enum EntityKeyValueType {
  19 + STRING,
  20 + NUMERIC,
  21 + BOOLEAN,
  22 + DATE_TIME
  23 +}
... ...
... ... @@ -21,6 +21,7 @@ import lombok.Data;
21 21 public class KeyFilter {
22 22
23 23 private EntityKey key;
  24 + private EntityKeyValueType valueType;
24 25 private KeyFilterPredicate predicate;
25 26
26 27 }
... ...
... ... @@ -62,6 +62,7 @@ import {
62 62 entityInfoFields,
63 63 EntityKey,
64 64 EntityKeyType,
  65 + EntityKeyValueType,
65 66 FilterPredicateType,
66 67 singleEntityDataPageLink,
67 68 StringOperation
... ... @@ -399,6 +400,7 @@ export class EntityService {
399 400 keyFilters: searchText && searchText.length ? [
400 401 {
401 402 key: nameField,
  403 + valueType: EntityKeyValueType.STRING,
402 404 predicate: {
403 405 type: FilterPredicateType.STRING,
404 406 operation: StringOperation.STARTS_WITH,
... ... @@ -593,10 +595,10 @@ export class EntityService {
593 595 return entityTypes;
594 596 }
595 597
596   - private getEntityFieldKeys (entityType: EntityType, searchText: string): Array<string> {
  598 + private getEntityFieldKeys(entityType: EntityType, searchText: string): Array<string> {
597 599 const entityFieldKeys: string[] = [entityFields.createdTime.keyName];
598 600 const query = searchText.toLowerCase();
599   - switch(entityType) {
  601 + switch (entityType) {
600 602 case EntityType.USER:
601 603 entityFieldKeys.push(entityFields.name.keyName);
602 604 entityFieldKeys.push(entityFields.email.keyName);
... ... @@ -863,7 +865,7 @@ export class EntityService {
863 865 const tasks: Observable<any>[] = [];
864 866 const result: Device | Asset = entity as (Device | Asset);
865 867 const additionalInfo = result.additionalInfo || {};
866   - if(result.label !== entityData.label ||
  868 + if (result.label !== entityData.label ||
867 869 result.type !== entityData.type ||
868 870 additionalInfo.description !== entityData.description ||
869 871 (result.id.entityType === EntityType.DEVICE && (additionalInfo.gateway !== entityData.gateway)) ) {
... ...
... ... @@ -251,13 +251,14 @@ export class EntityDetailsPanelComponent extends PageComponent implements OnInit
251 251 }
252 252
253 253 onToggleEditMode(isEdit: boolean) {
254   - this.isEdit = isEdit;
255   - if (!this.isEdit) {
  254 + if (!isEdit) {
256 255 this.entityComponent.entity = this.entity;
257 256 if (this.entityTabsComponent) {
258 257 this.entityTabsComponent.entity = this.entity;
259 258 }
  259 + this.isEdit = isEdit;
260 260 } else {
  261 + this.isEdit = isEdit;
261 262 this.editingEntity = deepClone(this.entity);
262 263 this.entityComponent.entity = this.editingEntity;
263 264 if (this.entityTabsComponent) {
... ...
... ... @@ -65,7 +65,6 @@ export abstract class EntityComponent<T extends BaseData<HasId>,
65 65 set entity(entity: T) {
66 66 this.entityValue = entity;
67 67 if (this.entityForm) {
68   - this.entityForm.reset(undefined, {emitEvent: false});
69 68 this.entityForm.markAsPristine();
70 69 this.updateForm(entity);
71 70 }
... ...
... ... @@ -37,6 +37,7 @@
37 37 </mat-form-field>
38 38 <tb-filter-predicate-list
39 39 [valueType]="data.valueType"
  40 + [displayUserParameters]="data.displayUserParameters"
40 41 [operation]="complexFilterFormGroup.get('operation').value"
41 42 [key]="data.key"
42 43 formControlName="predicates">
... ... @@ -45,6 +46,7 @@
45 46 </div>
46 47 <div mat-dialog-actions fxLayoutAlign="end center">
47 48 <button mat-raised-button color="primary"
  49 + *ngIf="!data.readonly"
48 50 type="submit"
49 51 [disabled]="(isLoading$ | async) || complexFilterFormGroup.invalid || !complexFilterFormGroup.dirty">
50 52 {{ (isAdd ? 'action.add' : 'action.update') | translate }}
... ... @@ -54,7 +56,7 @@
54 56 [disabled]="(isLoading$ | async)"
55 57 (click)="cancel()"
56 58 cdkFocusInitial>
57   - {{ 'action.cancel' | translate }}
  59 + {{ (data.readonly ? 'action.close' : 'action.cancel') | translate }}
58 60 </button>
59 61 </div>
60 62 </form>
... ...
... ... @@ -32,9 +32,10 @@ import {
32 32 export interface ComplexFilterPredicateDialogData {
33 33 complexPredicate: ComplexFilterPredicateInfo;
34 34 key: string;
35   - disabled: boolean;
  35 + readonly: boolean;
36 36 isAdd: boolean;
37 37 valueType: EntityKeyValueType;
  38 + displayUserParameters: boolean;
38 39 }
39 40
40 41 @Component({
... ... @@ -73,6 +74,9 @@ export class ComplexFilterPredicateDialogComponent extends
73 74 predicates: [this.data.complexPredicate.predicates, [Validators.required]]
74 75 }
75 76 );
  77 + if (this.data.readonly) {
  78 + this.complexFilterFormGroup.disable({emitEvent: false});
  79 + }
76 80 }
77 81
78 82 ngOnInit(): void {
... ...
... ... @@ -19,11 +19,10 @@
19 19 <mat-label translate>filter.complex-filter</mat-label>
20 20 <button mat-icon-button color="primary"
21 21 class="tb-mat-32"
22   - [fxShow]="!disabled"
23 22 type="button"
24 23 (click)="openComplexFilterDialog()"
25   - matTooltip="{{ 'filter.edit-complex-filter' | translate }}"
  24 + matTooltip="{{ (disabled ? 'filter.complex-filter' : 'filter.edit-complex-filter') | translate }}"
26 25 matTooltipPosition="above">
27   - <mat-icon>edit</mat-icon>
  26 + <mat-icon>{{ disabled ? 'more_vert' : 'edit' }}</mat-icon>
28 27 </button>
29 28 </div>
... ...
... ... @@ -48,6 +48,8 @@ export class ComplexFilterPredicateComponent implements ControlValueAccessor, On
48 48
49 49 @Input() key: string;
50 50
  51 + @Input() displayUserParameters = true;
  52 +
51 53 private propagateChange = null;
52 54
53 55 private complexFilterPredicate: ComplexFilterPredicateInfo;
... ... @@ -79,11 +81,12 @@ export class ComplexFilterPredicateComponent implements ControlValueAccessor, On
79 81 disableClose: true,
80 82 panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
81 83 data: {
82   - complexPredicate: deepClone(this.complexFilterPredicate),
83   - disabled: this.disabled,
  84 + complexPredicate: this.disabled ? this.complexFilterPredicate : deepClone(this.complexFilterPredicate),
  85 + readonly: this.disabled,
84 86 valueType: this.valueType,
85 87 isAdd: false,
86   - key: this.key
  88 + key: this.key,
  89 + displayUserParameters: this.displayUserParameters
87 90 }
88 91 }).afterClosed().subscribe(
89 92 (result) => {
... ...
... ... @@ -33,7 +33,8 @@
33 33 </div>
34 34 <label fxFlex="60" translate class="tb-title no-padding">filter.value</label>
35 35 </div>
36   - <label translate class="tb-title no-padding" style="width: 60px;">filter.user-parameters</label>
  36 + <label *ngIf="displayUserParameters"
  37 + translate class="tb-title no-padding" style="width: 60px;">filter.user-parameters</label>
37 38 <span [fxShow]="!disabled" style="min-width: 40px;">&nbsp;</span>
38 39 </div>
39 40 </div>
... ... @@ -50,6 +51,7 @@
50 51 <tb-filter-predicate
51 52 fxFlex
52 53 [valueType]="valueType"
  54 + [displayUserParameters]="displayUserParameters"
53 55 [key]="key"
54 56 [formControl]="predicateControl">
55 57 </tb-filter-predicate>
... ...
... ... @@ -62,6 +62,8 @@ export class FilterPredicateListComponent implements ControlValueAccessor, OnIni
62 62
63 63 @Input() operation: ComplexOperation = ComplexOperation.AND;
64 64
  65 + @Input() displayUserParameters = true;
  66 +
65 67 filterListFormGroup: FormGroup;
66 68
67 69 valueTypeEnum = EntityKeyValueType;
... ... @@ -150,10 +152,11 @@ export class FilterPredicateListComponent implements ControlValueAccessor, OnIni
150 152 panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
151 153 data: {
152 154 complexPredicate: predicate.keyFilterPredicate as ComplexFilterPredicateInfo,
153   - disabled: this.disabled,
  155 + readonly: this.disabled,
154 156 valueType: this.valueType,
155 157 key: this.key,
156   - isAdd: true
  158 + isAdd: true,
  159 + displayUserParameters: this.displayUserParameters
157 160 }
158 161 }).afterClosed().pipe(
159 162 map((result) => {
... ...
... ... @@ -35,11 +35,12 @@
35 35 <tb-complex-filter-predicate
36 36 [key]="key"
37 37 [valueType]="valueType"
  38 + [displayUserParameters]="displayUserParameters"
38 39 formControlName="predicate">
39 40 </tb-complex-filter-predicate>
40 41 </ng-template>
41 42 </div>
42   - <tb-filter-user-info *ngIf="type !== filterPredicateType.COMPLEX"
  43 + <tb-filter-user-info *ngIf="type !== filterPredicateType.COMPLEX && displayUserParameters"
43 44 style="width: 60px;"
44 45 fxLayout="row" fxLayoutAlign="center"
45 46 [valueType]="valueType"
... ...
... ... @@ -41,6 +41,8 @@ export class FilterPredicateComponent implements ControlValueAccessor, OnInit {
41 41
42 42 @Input() key: string;
43 43
  44 + @Input() displayUserParameters = true;
  45 +
44 46 filterPredicateFormGroup: FormGroup;
45 47
46 48 type: FilterPredicateType;
... ...
... ... @@ -17,7 +17,7 @@
17 17 -->
18 18 <form [formGroup]="filterUserInfoFormGroup" (ngSubmit)="save()" style="width: 500px;">
19 19 <mat-toolbar color="primary">
20   - <h2 translate>filter.edit-filter-user-params</h2>
  20 + <h2>{{(data.readonly ? 'filter.filter-user-params' : 'filter.edit-filter-user-params') | translate}}</h2>
21 21 <span fxFlex></span>
22 22 <button mat-icon-button
23 23 (click)="cancel()"
... ... @@ -47,6 +47,7 @@
47 47 </div>
48 48 <div mat-dialog-actions fxLayoutAlign="end center">
49 49 <button mat-raised-button color="primary"
  50 + *ngIf="!data.readonly"
50 51 type="submit"
51 52 [disabled]="(isLoading$ | async) || filterUserInfoFormGroup.invalid || !filterUserInfoFormGroup.dirty">
52 53 {{ 'action.update' | translate }}
... ... @@ -56,7 +57,7 @@
56 57 [disabled]="(isLoading$ | async)"
57 58 (click)="cancel()"
58 59 cdkFocusInitial>
59   - {{ 'action.cancel' | translate }}
  60 + {{ (data.readonly ? 'action.close' : 'action.cancel') | translate }}
60 61 </button>
61 62 </div>
62 63 </form>
... ...
... ... @@ -23,7 +23,7 @@ import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Valida
23 23 import { Router } from '@angular/router';
24 24 import { DialogComponent } from '@app/shared/components/dialog.component';
25 25 import {
26   - BooleanOperation,
  26 + BooleanOperation, createDefaultFilterPredicateUserInfo,
27 27 EntityKeyValueType, generateUserFilterValueLabel,
28 28 KeyFilterPredicateUserInfo, NumericOperation,
29 29 StringOperation
... ... @@ -35,6 +35,7 @@ export interface FilterUserInfoDialogData {
35 35 valueType: EntityKeyValueType;
36 36 operation: StringOperation | BooleanOperation | NumericOperation;
37 37 keyFilterPredicateUserInfo: KeyFilterPredicateUserInfo;
  38 + readonly: boolean;
38 39 }
39 40
40 41 @Component({
... ... @@ -60,18 +61,24 @@ export class FilterUserInfoDialogComponent extends
60 61 private translate: TranslateService) {
61 62 super(store, router, dialogRef);
62 63
  64 + const userInfo: KeyFilterPredicateUserInfo = this.data.keyFilterPredicateUserInfo || createDefaultFilterPredicateUserInfo();
  65 +
63 66 this.filterUserInfoFormGroup = this.fb.group(
64 67 {
65   - editable: [this.data.keyFilterPredicateUserInfo.editable],
66   - label: [this.data.keyFilterPredicateUserInfo.label],
67   - autogeneratedLabel: [this.data.keyFilterPredicateUserInfo.autogeneratedLabel],
68   - order: [this.data.keyFilterPredicateUserInfo.order]
  68 + editable: [userInfo.editable],
  69 + label: [userInfo.label],
  70 + autogeneratedLabel: [userInfo.autogeneratedLabel],
  71 + order: [userInfo.order]
69 72 }
70 73 );
71 74 this.onAutogeneratedLabelChange();
72   - this.filterUserInfoFormGroup.get('autogeneratedLabel').valueChanges.subscribe(() => {
73   - this.onAutogeneratedLabelChange();
74   - });
  75 + if (!this.data.readonly) {
  76 + this.filterUserInfoFormGroup.get('autogeneratedLabel').valueChanges.subscribe(() => {
  77 + this.onAutogeneratedLabelChange();
  78 + });
  79 + } else {
  80 + this.filterUserInfoFormGroup.disable({emitEvent: false});
  81 + }
75 82 }
76 83
77 84 private onAutogeneratedLabelChange() {
... ...
... ... @@ -17,10 +17,9 @@
17 17 -->
18 18 <button mat-icon-button color="primary"
19 19 class="tb-mat-32"
20   - [fxShow]="!disabled"
21 20 type="button"
22 21 (click)="openFilterUserInfoDialog()"
23   - matTooltip="{{ 'filter.edit-filter-user-params' | translate }}"
  22 + matTooltip="{{ (disabled ? 'filter.filter-user-params' : 'filter.edit-filter-user-params') | translate }}"
24 23 matTooltipPosition="above">
25 24 <mat-icon>settings</mat-icon>
26 25 </button>
... ...
... ... @@ -76,7 +76,7 @@ export class FilterUserInfoComponent implements ControlValueAccessor, OnInit {
76 76 this.keyFilterPredicateUserInfo = keyFilterPredicateUserInfo;
77 77 }
78 78
79   - private openFilterUserInfoDialog() {
  79 + public openFilterUserInfoDialog() {
80 80 this.dialog.open<FilterUserInfoDialogComponent, FilterUserInfoDialogData,
81 81 KeyFilterPredicateUserInfo>(FilterUserInfoDialogComponent, {
82 82 disableClose: true,
... ... @@ -85,7 +85,8 @@ export class FilterUserInfoComponent implements ControlValueAccessor, OnInit {
85 85 keyFilterPredicateUserInfo: deepClone(this.keyFilterPredicateUserInfo),
86 86 valueType: this.valueType,
87 87 key: this.key,
88   - operation: this.operation
  88 + operation: this.operation,
  89 + readonly: this.disabled
89 90 }
90 91 }).afterClosed().subscribe(
91 92 (result) => {
... ...
... ... @@ -17,7 +17,7 @@
17 17 -->
18 18 <form [formGroup]="keyFilterFormGroup" (ngSubmit)="save()" style="width: 900px;">
19 19 <mat-toolbar color="primary">
20   - <h2>{{(data.isAdd ? 'filter.add-key-filter' : 'filter.edit-key-filter') | translate}}</h2>
  20 + <h2>{{(data.isAdd ? 'filter.add-key-filter' : (data.readonly ? 'filter.key-filter' : 'filter.edit-key-filter')) | translate}}</h2>
21 21 <span fxFlex></span>
22 22 <button mat-icon-button
23 23 (click)="cancel()"
... ... @@ -70,6 +70,7 @@
70 70 </mat-form-field>
71 71 </section>
72 72 <tb-filter-predicate-list *ngIf="keyFilterFormGroup.get('valueType').value"
  73 + [displayUserParameters]="data.displayUserParameters"
73 74 [valueType]="keyFilterFormGroup.get('valueType').value"
74 75 [key]="keyFilterFormGroup.get('key.key').value"
75 76 formControlName="predicates">
... ... @@ -79,6 +80,7 @@
79 80 <div mat-dialog-actions fxLayoutAlign="end center">
80 81 <button mat-raised-button color="primary"
81 82 type="submit"
  83 + *ngIf="!data.readonly"
82 84 [disabled]="(isLoading$ | async) || keyFilterFormGroup.invalid || !keyFilterFormGroup.dirty">
83 85 {{ (data.isAdd ? 'action.add' : 'action.update') | translate }}
84 86 </button>
... ... @@ -87,7 +89,7 @@
87 89 [disabled]="(isLoading$ | async)"
88 90 (click)="cancel()"
89 91 cdkFocusInitial>
90   - {{ 'action.cancel' | translate }}
  92 + {{ (data.readonly ? 'action.close' : 'action.cancel') | translate }}
91 93 </button>
92 94 </div>
93 95 </form>
... ...
... ... @@ -39,6 +39,9 @@ import { filter, map, startWith } from 'rxjs/operators';
39 39 export interface KeyFilterDialogData {
40 40 keyFilter: KeyFilterInfo;
41 41 isAdd: boolean;
  42 + displayUserParameters: boolean;
  43 + readonly: boolean;
  44 + telemetryKeysOnly: boolean;
42 45 }
43 46
44 47 @Component({
... ... @@ -53,7 +56,10 @@ export class KeyFilterDialogComponent extends
53 56
54 57 keyFilterFormGroup: FormGroup;
55 58
56   - entityKeyTypes = [EntityKeyType.ENTITY_FIELD, EntityKeyType.ATTRIBUTE, EntityKeyType.TIME_SERIES];
  59 + entityKeyTypes =
  60 + this.data.telemetryKeysOnly ?
  61 + [EntityKeyType.ATTRIBUTE, EntityKeyType.TIME_SERIES] :
  62 + [EntityKeyType.ENTITY_FIELD, EntityKeyType.ATTRIBUTE, EntityKeyType.TIME_SERIES];
57 63
58 64 entityKeyTypeTranslations = entityKeyTypeTranslationMap;
59 65
... ... @@ -95,32 +101,37 @@ export class KeyFilterDialogComponent extends
95 101 predicates: [this.data.keyFilter.predicates, [Validators.required]]
96 102 }
97 103 );
98   - this.keyFilterFormGroup.get('valueType').valueChanges.subscribe((valueType: EntityKeyValueType) => {
99   - const prevValue: EntityKeyValueType = this.keyFilterFormGroup.value.valueType;
100   - const predicates: KeyFilterPredicate[] = this.keyFilterFormGroup.get('predicates').value;
101   - if (prevValue && prevValue !== valueType && predicates && predicates.length) {
102   - this.dialogs.confirm(this.translate.instant('filter.key-value-type-change-title'),
103   - this.translate.instant('filter.key-value-type-change-message')).subscribe(
104   - (result) => {
105   - if (result) {
106   - this.keyFilterFormGroup.get('predicates').setValue([]);
107   - } else {
108   - this.keyFilterFormGroup.get('valueType').setValue(prevValue, {emitEvent: false});
  104 +
  105 + if (!this.data.readonly) {
  106 + this.keyFilterFormGroup.get('valueType').valueChanges.subscribe((valueType: EntityKeyValueType) => {
  107 + const prevValue: EntityKeyValueType = this.keyFilterFormGroup.value.valueType;
  108 + const predicates: KeyFilterPredicate[] = this.keyFilterFormGroup.get('predicates').value;
  109 + if (prevValue && prevValue !== valueType && predicates && predicates.length) {
  110 + this.dialogs.confirm(this.translate.instant('filter.key-value-type-change-title'),
  111 + this.translate.instant('filter.key-value-type-change-message')).subscribe(
  112 + (result) => {
  113 + if (result) {
  114 + this.keyFilterFormGroup.get('predicates').setValue([]);
  115 + } else {
  116 + this.keyFilterFormGroup.get('valueType').setValue(prevValue, {emitEvent: false});
  117 + }
109 118 }
110   - }
111   - );
112   - }
113   - });
114   -
115   - this.keyFilterFormGroup.get('key.key').valueChanges.pipe(
116   - filter((keyName) => this.keyFilterFormGroup.get('key.type').value === this.entityField && this.entityFields.hasOwnProperty(keyName))
117   - ).subscribe((keyName: string) => {
118   - const prevValueType: EntityKeyValueType = this.keyFilterFormGroup.value.valueType;
119   - const newValueType = this.entityFields[keyName]?.time ? EntityKeyValueType.DATE_TIME : EntityKeyValueType.STRING;
120   - if (prevValueType !== newValueType) {
121   - this.keyFilterFormGroup.get('valueType').patchValue(newValueType, {emitEvent: false});
122   - }
123   - });
  119 + );
  120 + }
  121 + });
  122 +
  123 + this.keyFilterFormGroup.get('key.key').valueChanges.pipe(
  124 + filter((keyName) => this.keyFilterFormGroup.get('key.type').value === this.entityField && this.entityFields.hasOwnProperty(keyName))
  125 + ).subscribe((keyName: string) => {
  126 + const prevValueType: EntityKeyValueType = this.keyFilterFormGroup.value.valueType;
  127 + const newValueType = this.entityFields[keyName]?.time ? EntityKeyValueType.DATE_TIME : EntityKeyValueType.STRING;
  128 + if (prevValueType !== newValueType) {
  129 + this.keyFilterFormGroup.get('valueType').patchValue(newValueType, {emitEvent: false});
  130 + }
  131 + });
  132 + } else {
  133 + this.keyFilterFormGroup.disable({emitEvent: false});
  134 + }
124 135
125 136 this.entityFields = entityFields;
126 137 this.entityFieldsList = Object.values(entityFields).map(entityField => entityField.keyName).sort();
... ...
... ... @@ -46,9 +46,9 @@
46 46 <button mat-icon-button color="primary"
47 47 type="button"
48 48 (click)="editKeyFilter($index)"
49   - matTooltip="{{ 'filter.edit-key-filter' | translate }}"
  49 + matTooltip="{{ (disabled ? 'filter.key-filter' : 'filter.edit-key-filter') | translate }}"
50 50 matTooltipPosition="above">
51   - <mat-icon>edit</mat-icon>
  51 + <mat-icon>{{disabled ? 'more_vert' : 'edit'}}</mat-icon>
52 52 </button>
53 53 <button mat-icon-button color="primary"
54 54 [fxShow]="!disabled"
... ...
... ... @@ -46,6 +46,10 @@ export class KeyFilterListComponent implements ControlValueAccessor, OnInit {
46 46
47 47 @Input() disabled: boolean;
48 48
  49 + @Input() displayUserParameters = true;
  50 +
  51 + @Input() telemetryKeysOnly = false;
  52 +
49 53 keyFilterListFormGroup: FormGroup;
50 54
51 55 entityKeyTypeTranslations = entityKeyTypeTranslationMap;
... ... @@ -147,8 +151,11 @@ export class KeyFilterListComponent implements ControlValueAccessor, OnInit {
147 151 disableClose: true,
148 152 panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
149 153 data: {
150   - keyFilter: keyFilter ? deepClone(keyFilter): null,
151   - isAdd
  154 + keyFilter: keyFilter ? (this.disabled ? keyFilter : deepClone(keyFilter)) : null,
  155 + isAdd,
  156 + readonly: this.disabled,
  157 + displayUserParameters: this.displayUserParameters,
  158 + telemetryKeysOnly: this.telemetryKeysOnly
152 159 }
153 160 }).afterClosed();
154 161 }
... ...
... ... @@ -100,10 +100,10 @@ import { MqttDeviceProfileTransportConfigurationComponent } from './profile/devi
100 100 import { Lwm2mDeviceProfileTransportConfigurationComponent } from './profile/device/lwm2m-device-profile-transport-configuration.component';
101 101 import { DeviceProfileAlarmsComponent } from './profile/alarm/device-profile-alarms.component';
102 102 import { DeviceProfileAlarmComponent } from './profile/alarm/device-profile-alarm.component';
103   -import { DeviceProfileAlarmDialogComponent } from './profile/alarm/device-profile-alarm-dialog.component';
104 103 import { CreateAlarmRulesComponent } from './profile/alarm/create-alarm-rules.component';
105 104 import { AlarmRuleComponent } from './profile/alarm/alarm-rule.component';
106 105 import { AlarmRuleConditionComponent } from './profile/alarm/alarm-rule-condition.component';
  106 +import { AlarmRuleKeyFiltersDialogComponent } from './profile/alarm/alarm-rule-key-filters-dialog.component';
107 107
108 108 @NgModule({
109 109 declarations:
... ... @@ -184,9 +184,9 @@ import { AlarmRuleConditionComponent } from './profile/alarm/alarm-rule-conditio
184 184 DeviceProfileTransportConfigurationComponent,
185 185 CreateAlarmRulesComponent,
186 186 AlarmRuleComponent,
  187 + AlarmRuleKeyFiltersDialogComponent,
187 188 AlarmRuleConditionComponent,
188 189 DeviceProfileAlarmComponent,
189   - DeviceProfileAlarmDialogComponent,
190 190 DeviceProfileAlarmsComponent,
191 191 DeviceProfileDataComponent,
192 192 DeviceProfileComponent,
... ... @@ -260,9 +260,9 @@ import { AlarmRuleConditionComponent } from './profile/alarm/alarm-rule-conditio
260 260 DeviceProfileTransportConfigurationComponent,
261 261 CreateAlarmRulesComponent,
262 262 AlarmRuleComponent,
  263 + AlarmRuleKeyFiltersDialogComponent,
263 264 AlarmRuleConditionComponent,
264 265 DeviceProfileAlarmComponent,
265   - DeviceProfileAlarmDialogComponent,
266 266 DeviceProfileAlarmsComponent,
267 267 DeviceProfileDataComponent,
268 268 DeviceProfileComponent,
... ...
... ... @@ -15,5 +15,16 @@
15 15 limitations under the License.
16 16
17 17 -->
18   -<div fxLayout="row" [formGroup]="alarmRuleConditionFormGroup">
19   -</div>
  18 +<mat-form-field class="mat-block" (click)="openFilterDialog($event)" floatLabel="always" hideRequiredMarker>
  19 + <mat-label></mat-label>
  20 + <input readonly
  21 + required matInput [formControl]="alarmRuleConditionControl"
  22 + placeholder="{{'device-profile.enter-alarm-rule-condition-prompt' | translate}}">
  23 + <a matSuffix mat-icon-button color="primary"
  24 + type="button"
  25 + (click)="openFilterDialog($event)"
  26 + matTooltip="{{ (disabled ? 'action.view' : 'action.edit') | translate }}"
  27 + matTooltipPosition="above">
  28 + <mat-icon>{{ disabled ? 'more_vert' : 'edit' }}</mat-icon>
  29 + </a>
  30 +</mat-form-field>
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +:host {
  17 + a.mat-icon-button {
  18 + &:hover, &:focus {
  19 + border-bottom: none;
  20 + }
  21 + }
  22 +}
... ...
... ... @@ -19,19 +19,22 @@ import {
19 19 ControlValueAccessor,
20 20 FormBuilder,
21 21 FormControl,
22   - FormGroup,
23 22 NG_VALIDATORS,
24 23 NG_VALUE_ACCESSOR,
25   - Validator,
26   - Validators
  24 + Validator
27 25 } from '@angular/forms';
28   -import { AlarmCondition } from '@shared/models/device.models';
29 26 import { MatDialog } from '@angular/material/dialog';
  27 +import { KeyFilter } from '@shared/models/query/query.models';
  28 +import { deepClone } from '@core/utils';
  29 +import {
  30 + AlarmRuleKeyFiltersDialogComponent,
  31 + AlarmRuleKeyFiltersDialogData
  32 +} from './alarm-rule-key-filters-dialog.component';
30 33
31 34 @Component({
32 35 selector: 'tb-alarm-rule-condition',
33 36 templateUrl: './alarm-rule-condition.component.html',
34   - styleUrls: [],
  37 + styleUrls: ['./alarm-rule-condition.component.scss'],
35 38 providers: [
36 39 {
37 40 provide: NG_VALUE_ACCESSOR,
... ... @@ -50,9 +53,9 @@ export class AlarmRuleConditionComponent implements ControlValueAccessor, OnInit
50 53 @Input()
51 54 disabled: boolean;
52 55
53   - private modelValue: AlarmCondition;
  56 + alarmRuleConditionControl: FormControl;
54 57
55   - alarmRuleConditionFormGroup: FormGroup;
  58 + private modelValue: Array<KeyFilter>;
56 59
57 60 private propagateChange = (v: any) => { };
58 61
... ... @@ -68,45 +71,56 @@ export class AlarmRuleConditionComponent implements ControlValueAccessor, OnInit
68 71 }
69 72
70 73 ngOnInit() {
71   - this.alarmRuleConditionFormGroup = this.fb.group({
72   - condition: [null, Validators.required],
73   - durationUnit: [null],
74   - durationValue: [null]
75   - });
76   - this.alarmRuleConditionFormGroup.valueChanges.subscribe(() => {
77   - this.updateModel();
78   - });
  74 + this.alarmRuleConditionControl = this.fb.control(null);
79 75 }
80 76
81 77 setDisabledState(isDisabled: boolean): void {
82 78 this.disabled = isDisabled;
83   - if (this.disabled) {
84   - this.alarmRuleConditionFormGroup.disable({emitEvent: false});
85   - } else {
86   - this.alarmRuleConditionFormGroup.enable({emitEvent: false});
87   - }
88 79 }
89 80
90   - writeValue(value: AlarmCondition): void {
  81 + writeValue(value: Array<KeyFilter>): void {
91 82 this.modelValue = value;
92   - this.alarmRuleConditionFormGroup.reset(this.modelValue, {emitEvent: false});
  83 + this.updateConditionInfo();
93 84 }
94 85
95 86 public validate(c: FormControl) {
96   - return (this.alarmRuleConditionFormGroup.valid) ? null : {
  87 + return (this.modelValue && this.modelValue.length) ? null : {
97 88 alarmRuleCondition: {
98 89 valid: false,
99 90 },
100 91 };
101 92 }
102 93
103   - private updateModel() {
104   - if (this.alarmRuleConditionFormGroup.valid) {
105   - const value = this.alarmRuleConditionFormGroup.value;
106   - this.modelValue = {...this.modelValue, ...value};
107   - this.propagateChange(this.modelValue);
  94 + public openFilterDialog($event: Event) {
  95 + if ($event) {
  96 + $event.stopPropagation();
  97 + }
  98 + this.dialog.open<AlarmRuleKeyFiltersDialogComponent, AlarmRuleKeyFiltersDialogData,
  99 + Array<KeyFilter>>(AlarmRuleKeyFiltersDialogComponent, {
  100 + disableClose: true,
  101 + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
  102 + data: {
  103 + readonly: this.disabled,
  104 + keyFilters: this.disabled ? this.modelValue : deepClone(this.modelValue)
  105 + }
  106 + }).afterClosed().subscribe((result) => {
  107 + if (result) {
  108 + this.modelValue = result;
  109 + this.updateModel();
  110 + }
  111 + });
  112 + }
  113 +
  114 + private updateConditionInfo() {
  115 + if (this.modelValue && this.modelValue.length) {
  116 + this.alarmRuleConditionControl.patchValue('Condition set');
108 117 } else {
109   - this.propagateChange(null);
  118 + this.alarmRuleConditionControl.patchValue(null);
110 119 }
111 120 }
  121 +
  122 + private updateModel() {
  123 + this.updateConditionInfo();
  124 + this.propagateChange(this.modelValue);
  125 + }
112 126 }
... ...
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-key-filters-dialog.component.html renamed from ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm-dialog.component.html
... ... @@ -15,11 +15,11 @@
15 15 limitations under the License.
16 16
17 17 -->
18   -<form [formGroup]="alarmFormGroup" (ngSubmit)="save()" style="min-width: 600px;">
19   - <mat-toolbar fxLayout="row" color="primary">
20   - <h2>{{ (isReadOnly ? 'device-profile.alarm-rule-details' : (isAdd ? 'device-profile.add-alarm-rule' : 'device-profile.edit-alarm-rule')) | translate }}</h2>
  18 +<form [formGroup]="keyFiltersFormGroup" (ngSubmit)="save()" style="width: 700px;">
  19 + <mat-toolbar color="primary">
  20 + <h2>{{ (readonly ? 'device-profile.alarm-rule-condition' : 'device-profile.edit-alarm-rule-condition') | translate }}</h2>
21 21 <span fxFlex></span>
22   - <button mat-button mat-icon-button
  22 + <button mat-icon-button
23 23 (click)="cancel()"
24 24 type="button">
25 25 <mat-icon class="material-icons">close</mat-icon>
... ... @@ -27,39 +27,29 @@
27 27 </mat-toolbar>
28 28 <mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
29 29 </mat-progress-bar>
30   - <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
31 30 <div mat-dialog-content>
32   - <fieldset [disabled]="(isLoading$ | async) || isReadOnly" fxLayout="column">
33   - <mat-form-field fxFlex class="mat-block">
34   - <mat-label translate>device-profile.alarm-type</mat-label>
35   - <input required matInput formControlName="alarmType">
36   - <mat-error *ngIf="alarmFormGroup.get('alarmType').hasError('required')">
37   - {{ 'device-profile.alarm-type-required' | translate }}
38   - </mat-error>
39   - <mat-hint innerHTML="{{ 'device-profile.alarm-type-pattern-hint' | translate }}"></mat-hint>
40   - </mat-form-field>
41   - <fieldset>
42   - <legend translate>device-profile.create-alarm-rules</legend>
43   - </fieldset>
44   - <fieldset>
45   - <legend translate>device-profile.clear-alarm-rule</legend>
46   - </fieldset>
  31 + <fieldset [disabled]="isLoading$ | async">
  32 + <div fxFlex fxLayout="column">
  33 + <tb-key-filter-list
  34 + [displayUserParameters]="false"
  35 + [telemetryKeysOnly]="true"
  36 + formControlName="keyFilters">
  37 + </tb-key-filter-list>
  38 + </div>
47 39 </fieldset>
48 40 </div>
49   - <div mat-dialog-actions fxLayout="row">
50   - <span fxFlex></span>
51   - <button *ngIf="!isReadOnly" mat-button mat-raised-button color="primary"
  41 + <div mat-dialog-actions fxLayoutAlign="end center">
  42 + <button mat-raised-button color="primary"
  43 + *ngIf="!readonly"
52 44 type="submit"
53   - [disabled]="(isLoading$ | async) || alarmFormGroup.invalid
54   - || !alarmFormGroup.dirty">
55   - {{ (isAdd ? 'action.add' : 'action.save') | translate }}
  45 + [disabled]="(isLoading$ | async) || keyFiltersFormGroup.invalid || !keyFiltersFormGroup.dirty">
  46 + {{ 'action.save' | translate }}
56 47 </button>
57 48 <button mat-button color="primary"
58   - style="margin-right: 20px;"
59 49 type="button"
60 50 [disabled]="(isLoading$ | async)"
61 51 (click)="cancel()" cdkFocusInitial>
62   - {{ (isReadOnly ? 'action.close' : 'action.cancel') | translate }}
  52 + {{ (readonly ? 'action.close' : 'action.cancel') | translate }}
63 53 </button>
64 54 </div>
65 55 </form>
... ...
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-key-filters-dialog.component.ts renamed from ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm-dialog.component.ts
... ... @@ -14,70 +14,60 @@
14 14 /// limitations under the License.
15 15 ///
16 16
17   -import {
18   - Component,
19   - Inject,
20   - OnInit,
21   - SkipSelf
22   -} from '@angular/core';
  17 +import { Component, Inject, OnInit, SkipSelf } from '@angular/core';
23 18 import { ErrorStateMatcher } from '@angular/material/core';
24 19 import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
25 20 import { Store } from '@ngrx/store';
26 21 import { AppState } from '@core/core.state';
27 22 import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms';
28   -import { DialogComponent } from '@shared/components/dialog.component';
29 23 import { Router } from '@angular/router';
30   -import { DeviceProfileAlarm } from '@shared/models/device.models';
  24 +import { DialogComponent } from '@app/shared/components/dialog.component';
  25 +import { UtilsService } from '@core/services/utils.service';
  26 +import { TranslateService } from '@ngx-translate/core';
  27 +import { KeyFilter, keyFilterInfosToKeyFilters, keyFiltersToKeyFilterInfos } from '@shared/models/query/query.models';
31 28
32   -export interface DeviceProfileAlarmDialogData {
33   - alarm: DeviceProfileAlarm;
34   - isAdd: boolean;
35   - isReadOnly: boolean;
  29 +export interface AlarmRuleKeyFiltersDialogData {
  30 + readonly: boolean;
  31 + keyFilters: Array<KeyFilter>;
36 32 }
37 33
38 34 @Component({
39   - selector: 'tb-device-profile-alarm-dialog',
40   - templateUrl: './device-profile-alarm-dialog.component.html',
41   - providers: [{provide: ErrorStateMatcher, useExisting: DeviceProfileAlarmDialogComponent}],
  35 + selector: 'tb-alarm-rule-key-filters-dialog',
  36 + templateUrl: './alarm-rule-key-filters-dialog.component.html',
  37 + providers: [{provide: ErrorStateMatcher, useExisting: AlarmRuleKeyFiltersDialogComponent}],
42 38 styleUrls: []
43 39 })
44   -export class DeviceProfileAlarmDialogComponent extends
45   - DialogComponent<DeviceProfileAlarmDialogComponent, DeviceProfileAlarm> implements OnInit, ErrorStateMatcher {
  40 +export class AlarmRuleKeyFiltersDialogComponent extends DialogComponent<AlarmRuleKeyFiltersDialogComponent, Array<KeyFilter>>
  41 + implements OnInit, ErrorStateMatcher {
46 42
47   - alarmFormGroup: FormGroup;
  43 + readonly = this.data.readonly;
  44 + keyFilters = this.data.keyFilters;
48 45
49   - isReadOnly = this.data.isReadOnly;
50   - alarm = this.data.alarm;
51   - isAdd = this.data.isAdd;
  46 + keyFiltersFormGroup: FormGroup;
52 47
53 48 submitted = false;
54 49
55 50 constructor(protected store: Store<AppState>,
56 51 protected router: Router,
57   - @Inject(MAT_DIALOG_DATA) public data: DeviceProfileAlarmDialogData,
58   - public dialogRef: MatDialogRef<DeviceProfileAlarmDialogComponent, DeviceProfileAlarm>,
  52 + @Inject(MAT_DIALOG_DATA) public data: AlarmRuleKeyFiltersDialogData,
59 53 @SkipSelf() private errorStateMatcher: ErrorStateMatcher,
60   - public fb: FormBuilder) {
  54 + public dialogRef: MatDialogRef<AlarmRuleKeyFiltersDialogComponent, Array<KeyFilter>>,
  55 + private fb: FormBuilder,
  56 + private utils: UtilsService,
  57 + public translate: TranslateService) {
61 58 super(store, router, dialogRef);
62   - this.isAdd = this.data.isAdd;
63   - this.alarm = this.data.alarm;
64   - }
65 59
66   - ngOnInit(): void {
67   - this.alarmFormGroup = this.fb.group({
68   - id: [null, Validators.required],
69   - alarmType: [null, Validators.required],
70   - createRules: [null],
71   - clearRule: [null],
72   - propagate: [null],
73   - propagateRelationTypes: [null]
  60 + this.keyFiltersFormGroup = this.fb.group({
  61 + keyFilters: [keyFiltersToKeyFilterInfos(this.keyFilters), Validators.required]
74 62 });
75   - this.alarmFormGroup.reset(this.alarm, {emitEvent: false});
76   - if (this.isReadOnly) {
77   - this.alarmFormGroup.disable({emitEvent: false});
  63 + if (this.readonly) {
  64 + this.keyFiltersFormGroup.disable({emitEvent: false});
78 65 }
79 66 }
80 67
  68 + ngOnInit(): void {
  69 + }
  70 +
81 71 isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
82 72 const originalErrorState = this.errorStateMatcher.isErrorState(control, form);
83 73 const customErrorState = !!(control && control.invalid && this.submitted);
... ... @@ -90,10 +80,7 @@ export class DeviceProfileAlarmDialogComponent extends
90 80
91 81 save(): void {
92 82 this.submitted = true;
93   - if (this.alarmFormGroup.valid) {
94   - this.alarm = {...this.alarm, ...this.alarmFormGroup.value};
95   - this.dialogRef.close(this.alarm);
96   - }
  83 + this.keyFilters = keyFilterInfosToKeyFilters(this.keyFiltersFormGroup.get('keyFilters').value);
  84 + this.dialogRef.close(this.keyFilters);
97 85 }
98   -
99 86 }
... ...
... ... @@ -15,8 +15,71 @@
15 15 limitations under the License.
16 16
17 17 -->
18   -<div fxLayout="row" [formGroup]="alarmRuleFormGroup">
19   - <tb-alarm-rule-condition
20   - formControlName="condition">
21   - </tb-alarm-rule-condition>
  18 +<div fxLayout="column" [formGroup]="alarmRuleFormGroup">
  19 + <div formGroupName="condition" fxLayout="row" fxLayoutAlign="start" fxLayoutGap="8px" fxFlex>
  20 + <div fxLayout="column" fxFlex>
  21 + <div fxLayout="row" fxLayoutAlign="start center" style="min-height: 40px;">
  22 + <label class="tb-small" translate>device-profile.alarm-rule-condition</label>
  23 + </div>
  24 + <tb-alarm-rule-condition fxFlex
  25 + formControlName="condition">
  26 + </tb-alarm-rule-condition>
  27 + </div>
  28 + <div fxLayout="column" style="min-width: 250px;">
  29 + <div fxLayout="row" fxLayoutAlign="start center" style="min-height: 40px;">
  30 + <label fxFlex class="tb-small" translate>device-profile.condition-duration</label>
  31 + <mat-slide-toggle [disabled]="disabled"
  32 + [ngModelOptions]="{standalone: true}"
  33 + (ngModelChange)="enableDurationChanged($event)"
  34 + [ngModel]="enableDuration">
  35 + </mat-slide-toggle>
  36 + </div>
  37 + <div fxLayout="row" fxLayoutGap="8px" [fxShow]="enableDuration">
  38 + <mat-form-field class="mat-block duration-value-field" hideRequiredMarker floatLabel="always">
  39 + <mat-label></mat-label>
  40 + <input type="number"
  41 + [required]="enableDuration"
  42 + step="1"
  43 + min="1" max="2147483647" matInput
  44 + placeholder="{{ 'device-profile.condition-duration-value' | translate }}"
  45 + formControlName="durationValue">
  46 + <mat-error *ngIf="alarmRuleFormGroup.get('condition').get('durationValue').hasError('required')">
  47 + {{ 'device-profile.condition-duration-value-required' | translate }}
  48 + </mat-error>
  49 + <mat-error *ngIf="alarmRuleFormGroup.get('condition').get('durationValue').hasError('min')">
  50 + {{ 'device-profile.condition-duration-value-range' | translate }}
  51 + </mat-error>
  52 + <mat-error *ngIf="alarmRuleFormGroup.get('condition').get('durationValue').hasError('max')">
  53 + {{ 'device-profile.condition-duration-value-range' | translate }}
  54 + </mat-error>
  55 + </mat-form-field>
  56 + <mat-form-field class="mat-block duration-unit-field" hideRequiredMarker floatLabel="always">
  57 + <mat-label></mat-label>
  58 + <mat-select formControlName="durationUnit"
  59 + [required]="enableDuration"
  60 + placeholder="{{ 'device-profile.condition-duration-time-unit' | translate }}">
  61 + <mat-option *ngFor="let timeUnit of timeUnits" [value]="timeUnit">
  62 + {{ timeUnitTranslations.get(timeUnit) | translate }}
  63 + </mat-option>
  64 + </mat-select>
  65 + <mat-error *ngIf="alarmRuleFormGroup.get('condition').get('durationUnit').hasError('required')">
  66 + {{ 'device-profile.condition-duration-time-unit-required' | translate }}
  67 + </mat-error>
  68 + </mat-form-field>
  69 + </div>
  70 + </div>
  71 + </div>
  72 + <mat-expansion-panel class="advanced-settings" [expanded]="false">
  73 + <mat-expansion-panel-header>
  74 + <mat-panel-title>
  75 + <div fxFlex fxLayout="row" fxLayoutAlign="end center">
  76 + <div class="tb-small" translate>device-profile.advanced-settings</div>
  77 + </div>
  78 + </mat-panel-title>
  79 + </mat-expansion-panel-header>
  80 + <mat-form-field class="mat-block">
  81 + <mat-label translate>device-profile.alarm-details</mat-label>
  82 + <textarea matInput formControlName="alarmDetails" rows="5"></textarea>
  83 + </mat-form-field>
  84 + </mat-expansion-panel>
22 85 </div>
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +:host {
  17 + .mat-expansion-panel.advanced-settings {
  18 + box-shadow: none;
  19 + border: none;
  20 + padding: 0;
  21 + }
  22 +}
  23 +
  24 +:host ::ng-deep {
  25 + .mat-expansion-panel.advanced-settings {
  26 + .mat-expansion-panel-body {
  27 + padding: 0;
  28 + }
  29 + }
  30 + .mat-form-field.duration-value-field {
  31 + .mat-form-field-infix {
  32 + width: 120px;
  33 + }
  34 + }
  35 + .mat-form-field.duration-unit-field {
  36 + .mat-form-field-infix {
  37 + width: 120px;
  38 + }
  39 + }
  40 +}
  41 +
... ...
... ... @@ -14,7 +14,7 @@
14 14 /// limitations under the License.
15 15 ///
16 16
17   -import { Component, forwardRef, Input, OnInit } from '@angular/core';
  17 +import { ChangeDetectorRef, Component, forwardRef, Input, NgZone, OnInit } from '@angular/core';
18 18 import {
19 19 ControlValueAccessor,
20 20 FormBuilder,
... ... @@ -27,11 +27,13 @@ import {
27 27 } from '@angular/forms';
28 28 import { AlarmRule } from '@shared/models/device.models';
29 29 import { MatDialog } from '@angular/material/dialog';
  30 +import { TimeUnit, timeUnitTranslationMap } from '../../../../../shared/models/time/time.models';
  31 +import { coerceBooleanProperty } from '@angular/cdk/coercion';
30 32
31 33 @Component({
32 34 selector: 'tb-alarm-rule',
33 35 templateUrl: './alarm-rule.component.html',
34   - styleUrls: [],
  36 + styleUrls: ['./alarm-rule.component.scss'],
35 37 providers: [
36 38 {
37 39 provide: NG_VALUE_ACCESSOR,
... ... @@ -47,9 +49,23 @@ import { MatDialog } from '@angular/material/dialog';
47 49 })
48 50 export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validator {
49 51
  52 + timeUnits = Object.keys(TimeUnit);
  53 + timeUnitTranslations = timeUnitTranslationMap;
  54 +
50 55 @Input()
51 56 disabled: boolean;
52 57
  58 + private requiredValue: boolean;
  59 + get required(): boolean {
  60 + return this.requiredValue;
  61 + }
  62 + @Input()
  63 + set required(value: boolean) {
  64 + this.requiredValue = coerceBooleanProperty(value);
  65 + }
  66 +
  67 + enableDuration = false;
  68 +
53 69 private modelValue: AlarmRule;
54 70
55 71 alarmRuleFormGroup: FormGroup;
... ... @@ -69,7 +85,11 @@ export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validat
69 85
70 86 ngOnInit() {
71 87 this.alarmRuleFormGroup = this.fb.group({
72   - condition: [null, Validators.required],
  88 + condition: this.fb.group({
  89 + condition: [null, Validators.required],
  90 + durationUnit: [null],
  91 + durationValue: [null]
  92 + }, Validators.required),
73 93 alarmDetails: [null]
74 94 });
75 95 this.alarmRuleFormGroup.valueChanges.subscribe(() => {
... ... @@ -88,24 +108,51 @@ export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validat
88 108
89 109 writeValue(value: AlarmRule): void {
90 110 this.modelValue = value;
91   - this.alarmRuleFormGroup.reset(this.modelValue, {emitEvent: false});
  111 + this.enableDuration = value && !!value.condition.durationValue;
  112 + this.alarmRuleFormGroup.reset(this.modelValue || undefined, {emitEvent: false});
  113 + this.updateValidators();
92 114 }
93 115
94 116 public validate(c: FormControl) {
95   - return (this.alarmRuleFormGroup.valid) ? null : {
  117 + return (!this.required && !this.modelValue || this.alarmRuleFormGroup.valid) ? null : {
96 118 alarmRule: {
97 119 valid: false,
98 120 },
99 121 };
100 122 }
101 123
  124 + public enableDurationChanged(enableDuration) {
  125 + this.enableDuration = enableDuration;
  126 + this.updateValidators(true, true);
  127 + }
  128 +
  129 + private updateValidators(resetDuration = false, emitEvent = false) {
  130 + if (this.enableDuration) {
  131 + this.alarmRuleFormGroup.get('condition').get('durationValue')
  132 + .setValidators([Validators.required, Validators.min(1), Validators.max(2147483647)]);
  133 + this.alarmRuleFormGroup.get('condition').get('durationUnit')
  134 + .setValidators([Validators.required]);
  135 + } else {
  136 + this.alarmRuleFormGroup.get('condition').get('durationValue')
  137 + .setValidators([]);
  138 + this.alarmRuleFormGroup.get('condition').get('durationUnit')
  139 + .setValidators([]);
  140 + if (resetDuration) {
  141 + this.alarmRuleFormGroup.get('condition').patchValue({
  142 + durationValue: null,
  143 + durationUnit: null
  144 + });
  145 + }
  146 + }
  147 + this.alarmRuleFormGroup.get('condition').get('durationValue').updateValueAndValidity({emitEvent});
  148 + this.alarmRuleFormGroup.get('condition').get('durationUnit').updateValueAndValidity({emitEvent});
  149 + }
  150 +
102 151 private updateModel() {
103   - if (this.alarmRuleFormGroup.valid) {
104   - const value = this.alarmRuleFormGroup.value;
  152 + const value = this.alarmRuleFormGroup.value;
  153 + if (this.modelValue) {
105 154 this.modelValue = {...this.modelValue, ...value};
106 155 this.propagateChange(this.modelValue);
107   - } else {
108   - this.propagateChange(null);
109 156 }
110 157 }
111 158 }
... ...
... ... @@ -17,38 +17,42 @@
17 17 -->
18 18 <div fxFlex fxLayout="column">
19 19 <div *ngFor="let createAlarmRuleControl of createAlarmRulesFormArray().controls; let $index = index;
20   - last as isLast;" fxLayout="row" style="padding-left: 20px;" [formGroup]="createAlarmRuleControl">
21   - <mat-form-field class="mat-block" floatLabel="always" hideRequiredMarker>
22   - <mat-label translate></mat-label>
23   - <mat-select formControlName="severity"
24   - required
25   - placeholder="{{ 'device-profile.select-alarm-severity' | translate }}">
26   - <mat-option *ngFor="let alarmSeverity of alarmSeverities" [value]="alarmSeverity">
27   - {{ alarmSeverityTranslationMap.get(alarmSeverityEnum[alarmSeverity]) | translate }}
28   - </mat-option>
29   - </mat-select>
30   - <mat-error *ngIf="createAlarmRuleControl.get('severity').hasError('required')">
31   - {{ 'device-profile.alarm-severity-required' | translate }}
32   - </mat-error>
33   - </mat-form-field>
34   - <tb-alarm-rule formControlName="alarmRule">
35   - </tb-alarm-rule>
  20 + last as isLast;" fxLayout="row" fxLayoutAlign="start center"
  21 + fxLayoutGap="8px" style="padding-bottom: 8px;" [formGroup]="createAlarmRuleControl">
  22 + <div class="create-alarm-rule" fxFlex fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start">
  23 + <mat-form-field class="severity mat-block" floatLabel="always" hideRequiredMarker>
  24 + <mat-label translate>alarm.severity</mat-label>
  25 + <mat-select formControlName="severity"
  26 + required
  27 + placeholder="{{ 'device-profile.select-alarm-severity' | translate }}">
  28 + <mat-option *ngFor="let alarmSeverity of alarmSeverities" [value]="alarmSeverity">
  29 + {{ alarmSeverityTranslationMap.get(alarmSeverityEnum[alarmSeverity]) | translate }}
  30 + </mat-option>
  31 + </mat-select>
  32 + <mat-error *ngIf="createAlarmRuleControl.get('severity').hasError('required')">
  33 + {{ 'device-profile.alarm-severity-required' | translate }}
  34 + </mat-error>
  35 + </mat-form-field>
  36 + <tb-alarm-rule formControlName="alarmRule" required fxFlex>
  37 + </tb-alarm-rule>
  38 + </div>
36 39 <button *ngIf="!disabled && createAlarmRulesFormArray().controls.length > 1"
37 40 mat-icon-button color="primary" style="min-width: 40px;"
38 41 type="button"
39 42 (click)="removeCreateAlarmRule($index)"
40 43 matTooltip="{{ 'action.remove' | translate }}"
41 44 matTooltipPosition="above">
42   - <mat-icon>close</mat-icon>
  45 + <mat-icon>remove_circle_outline</mat-icon>
43 46 </button>
44 47 </div>
45   - <div *ngIf="!disabled" fxFlex fxLayout="row" fxLayoutAlign="start center">
46   - <button mat-icon-button color="primary"
  48 + <div fxLayout="row" *ngIf="!disabled">
  49 + <button mat-stroked-button color="primary"
47 50 type="button"
48 51 (click)="addCreateAlarmRule()"
49 52 matTooltip="{{ 'device-profile.add-create-alarm-rule' | translate }}"
50 53 matTooltipPosition="above">
51   - <mat-icon>add</mat-icon>
  54 + <mat-icon>add_circle_outline</mat-icon>
  55 + {{ 'device-profile.add-create-alarm-rule' | translate }}
52 56 </button>
53 57 </div>
54 58 </div>
... ...
... ... @@ -13,6 +13,23 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
  16 +
16 17 :host {
  18 + .create-alarm-rule {
  19 + border: 1px groove rgba(0, 0, 0, .25);
  20 + border-radius: 4px;
  21 + padding: 8px;
  22 + .mat-form-field.severity {
  23 + border-right: 1px groove rgba(0, 0, 0, 0.25);
  24 + padding-right: 8px;
  25 + }
  26 + }
  27 +}
17 28
  29 +:host ::ng-deep {
  30 + .mat-form-field.severity {
  31 + .mat-form-field-infix {
  32 + width: 160px;
  33 + }
  34 + }
18 35 }
... ...
... ... @@ -150,15 +150,11 @@ export class CreateAlarmRulesComponent implements ControlValueAccessor, OnInit,
150 150 }
151 151
152 152 private updateModel() {
153   - if (this.createAlarmRulesFormGroup.valid) {
154   - const value: {severity: string, alarmRule: AlarmRule}[] = this.createAlarmRulesFormGroup.get('createAlarmRules').value;
155   - const createAlarmRules: {[severity: string]: AlarmRule} = {};
156   - value.forEach(v => {
157   - createAlarmRules[v.severity] = v.alarmRule;
158   - });
159   - this.propagateChange(createAlarmRules);
160   - } else {
161   - this.propagateChange(null);
162   - }
  153 + const value: {severity: string, alarmRule: AlarmRule}[] = this.createAlarmRulesFormGroup.get('createAlarmRules').value;
  154 + const createAlarmRules: {[severity: string]: AlarmRule} = {};
  155 + value.forEach(v => {
  156 + createAlarmRules[v.severity] = v.alarmRule;
  157 + });
  158 + this.propagateChange(createAlarmRules);
163 159 }
164 160 }
... ...
... ... @@ -15,33 +15,80 @@
15 15 limitations under the License.
16 16
17 17 -->
18   -<fieldset [formGroup]="alarmFormGroup" class="fields-group tb-device-profile-alarm" fxFlex
19   - style="position: relative;">
20   - <button *ngIf="!disabled" mat-icon-button color="primary" style="min-width: 40px;"
21   - type="button"
22   - style="position: absolute; top: 0; right: 0;"
23   - (click)="removeAlarm.emit()"
24   - matTooltip="{{ 'action.remove' | translate }}"
25   - matTooltipPosition="above">
26   - <mat-icon>close</mat-icon>
27   - </button>
28   - <legend>
29   - <mat-form-field floatLabel="always"
30   - matTooltip="{{ 'device-profile.alarm-type-pattern-hint' | translate }}">
31   - <mat-label>{{'device-profile.alarm-type' | translate}}</mat-label>
32   - <input required matInput formControlName="alarmType" placeholder="Enter alarm type">
33   - <mat-error *ngIf="alarmFormGroup.get('alarmType').hasError('required')">
34   - {{ 'device-profile.alarm-type-required' | translate }}
35   - </mat-error>
36   - <!--mat-hint innerHTML="{{ 'device-profile.alarm-type-pattern-hint' | translate }}"></mat-hint-->
37   - </mat-form-field>
38   - </legend>
  18 +<mat-expansion-panel class="device-profile-alarm" fxFlex [formGroup]="alarmFormGroup" [(expanded)]="expanded">
  19 + <mat-expansion-panel-header>
  20 + <div fxFlex fxLayout="row" fxLayoutAlign="start center">
  21 + <mat-panel-title [fxShow]="!expanded">
  22 + <div fxLayout="row" fxFlex fxLayoutAlign="start center">
  23 + {{ alarmFormGroup.get('alarmType').value }}
  24 + </div>
  25 + </mat-panel-title>
  26 + <mat-form-field floatLabel="always"
  27 + style="width: 600px;"
  28 + [fxShow]="expanded"
  29 + (click)="!disabled ? $event.stopPropagation() : null;">
  30 + <mat-label>{{'device-profile.alarm-type' | translate}}</mat-label>
  31 + <input required matInput formControlName="alarmType" placeholder="Enter alarm type">
  32 + <mat-error *ngIf="alarmFormGroup.get('alarmType').hasError('required')">
  33 + {{ 'device-profile.alarm-type-required' | translate }}
  34 + </mat-error>
  35 + <mat-hint *ngIf="!disabled"
  36 + innerHTML="{{ 'device-profile.alarm-type-pattern-hint' | translate }}"></mat-hint>
  37 + </mat-form-field>
  38 + <span fxFlex></span>
  39 + <button *ngIf="!disabled" mat-icon-button style="min-width: 40px;"
  40 + type="button"
  41 + (click)="removeAlarm.emit()"
  42 + matTooltip="{{ 'action.remove' | translate }}"
  43 + matTooltipPosition="above">
  44 + <mat-icon>delete</mat-icon>
  45 + </button>
  46 + </div>
  47 + </mat-expansion-panel-header>
39 48 <div fxFlex fxLayout="column">
40   - <div translate class="tb-small" style="font-weight: 500;">device-profile.create-alarm-rules</div>
41   - <mat-divider></mat-divider>
42   - <tb-create-alarm-rules formControlName="createRules">
  49 + <div translate class="tb-small" style="padding-bottom: 8px;">device-profile.create-alarm-rules</div>
  50 + <tb-create-alarm-rules formControlName="createRules" style="padding-bottom: 16px;">
43 51 </tb-create-alarm-rules>
44   - <div translate class="tb-small" style="font-weight: 500;">device-profile.clear-alarm-rule</div>
45   - <mat-divider></mat-divider>
  52 + <div translate class="tb-small" style="padding-bottom: 8px;">device-profile.clear-alarm-rule</div>
  53 + <div fxFlex fxLayout="row"
  54 + [fxShow]="alarmFormGroup.get('clearRule').value"
  55 + fxLayoutGap="8px;" fxLayoutAlign="start center" style="padding-bottom: 8px;">
  56 + <div class="clear-alarm-rule" fxFlex fxLayout="row">
  57 + <tb-alarm-rule formControlName="clearRule" fxFlex>
  58 + </tb-alarm-rule>
  59 + </div>
  60 + <button *ngIf="!disabled"
  61 + mat-icon-button color="primary" style="min-width: 40px;"
  62 + type="button"
  63 + (click)="removeClearAlarmRule()"
  64 + matTooltip="{{ 'action.remove' | translate }}"
  65 + matTooltipPosition="above">
  66 + <mat-icon>remove_circle_outline</mat-icon>
  67 + </button>
  68 + </div>
  69 + <div fxLayout="row" *ngIf="!disabled"
  70 + [fxShow]="!alarmFormGroup.get('clearRule').value">
  71 + <button mat-stroked-button color="primary"
  72 + type="button"
  73 + (click)="addClearAlarmRule()"
  74 + matTooltip="{{ 'device-profile.add-clear-alarm-rule' | translate }}"
  75 + matTooltipPosition="above">
  76 + <mat-icon>add_circle_outline</mat-icon>
  77 + {{ 'device-profile.add-clear-alarm-rule' | translate }}
  78 + </button>
  79 + </div>
46 80 </div>
47   -</fieldset>
  81 + <mat-expansion-panel class="advanced-settings" [expanded]="false">
  82 + <mat-expansion-panel-header>
  83 + <mat-panel-title>
  84 + <div fxFlex fxLayout="row" fxLayoutAlign="end center">
  85 + <div class="tb-small" translate>device-profile.advanced-settings</div>
  86 + </div>
  87 + </mat-panel-title>
  88 + </mat-expansion-panel-header>
  89 + <mat-checkbox formControlName="propagate" style="padding-bottom: 16px;">
  90 + {{ 'device-profile.propagate-alarm' | translate }}
  91 + </mat-checkbox>
  92 + <div>TODO: Propagate relation types</div>
  93 + </mat-expansion-panel>
  94 +</mat-expansion-panel>
... ...
... ... @@ -13,35 +13,42 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -@import '../scss/constants';
17 16
18 17 :host {
19 18 display: block;
20   - .tb-device-profile-alarm {
21   - &.mat-padding {
22   - padding: 8px;
23   - @media #{$mat-gt-sm} {
24   - padding: 16px;
  19 + .clear-alarm-rule {
  20 + border: 1px groove rgba(0, 0, 0, .25);
  21 + border-radius: 4px;
  22 + padding: 8px;
  23 + }
  24 + .mat-expansion-panel {
  25 + box-shadow: none;
  26 + &.device-profile-alarm {
  27 + border: 1px groove rgba(0, 0, 0, .25);
  28 + .mat-expansion-panel-header {
  29 + padding: 0 24px 0 8px;
  30 + &.mat-expanded {
  31 + height: 80px;
  32 + }
25 33 }
26 34 }
27   - }
28   - a.mat-icon-button {
29   - &:hover, &:focus {
30   - border-bottom: none;
  35 + &.advanced-settings {
  36 + border: none;
  37 + padding: 0;
31 38 }
32 39 }
33   - .fields-group {
34   - padding: 8px;
35   - margin: 10px 0;
36   - border: 1px groove rgba(0, 0, 0, .25);
37   - border-radius: 4px;
  40 +}
38 41
39   - legend {
40   - padding-left: 8px;
41   - padding-right: 8px;
42   - margin-bottom: -30px;
43   - .mat-form-field {
44   - margin-bottom: 21px;
  42 +:host ::ng-deep {
  43 + .mat-expansion-panel {
  44 + &.device-profile-alarm {
  45 + .mat-expansion-panel-body {
  46 + padding: 0 8px;
  47 + }
  48 + }
  49 + &.advanced-settings {
  50 + .mat-expansion-panel-body {
  51 + padding: 0;
45 52 }
46 53 }
47 54 }
... ...
... ... @@ -19,17 +19,14 @@ import {
19 19 ControlValueAccessor,
20 20 FormBuilder,
21 21 FormControl,
22   - FormGroup, NG_VALIDATORS,
23   - NG_VALUE_ACCESSOR, Validator,
  22 + FormGroup,
  23 + NG_VALIDATORS,
  24 + NG_VALUE_ACCESSOR,
  25 + Validator,
24 26 Validators
25 27 } from '@angular/forms';
26   -import { DeviceProfileAlarm } from '@shared/models/device.models';
27   -import { deepClone } from '@core/utils';
  28 +import { AlarmRule, DeviceProfileAlarm } from '@shared/models/device.models';
28 29 import { MatDialog } from '@angular/material/dialog';
29   -import {
30   - DeviceProfileAlarmDialogComponent,
31   - DeviceProfileAlarmDialogData
32   -} from './device-profile-alarm-dialog.component';
33 30
34 31 @Component({
35 32 selector: 'tb-device-profile-alarm',
... ... @@ -56,6 +53,8 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit
56 53 @Output()
57 54 removeAlarm = new EventEmitter();
58 55
  56 + expanded = false;
  57 +
59 58 private modelValue: DeviceProfileAlarm;
60 59
61 60 alarmFormGroup: FormGroup;
... ... @@ -98,31 +97,24 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit
98 97
99 98 writeValue(value: DeviceProfileAlarm): void {
100 99 this.modelValue = value;
101   - this.alarmFormGroup.reset(this.modelValue, {emitEvent: false});
  100 + if (!this.modelValue.alarmType) {
  101 + this.expanded = true;
  102 + }
  103 + this.alarmFormGroup.reset(this.modelValue || undefined, {emitEvent: false});
102 104 }
103 105
104   -/* openAlarm($event: Event) {
105   - if ($event) {
106   - $event.stopPropagation();
107   - }
108   - this.dialog.open<DeviceProfileAlarmDialogComponent, DeviceProfileAlarmDialogData,
109   - DeviceProfileAlarm>(DeviceProfileAlarmDialogComponent, {
110   - disableClose: true,
111   - panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
112   - data: {
113   - isAdd: false,
114   - alarm: this.disabled ? this.modelValue : deepClone(this.modelValue),
115   - isReadOnly: this.disabled
116   - }
117   - }).afterClosed().subscribe(
118   - (deviceProfileAlarm) => {
119   - if (deviceProfileAlarm) {
120   - this.modelValue = deviceProfileAlarm;
121   - this.updateModel();
122   - }
  106 + public addClearAlarmRule() {
  107 + const clearAlarmRule: AlarmRule = {
  108 + condition: {
  109 + condition: []
123 110 }
124   - );
125   - } */
  111 + };
  112 + this.alarmFormGroup.patchValue({clearRule: clearAlarmRule});
  113 + }
  114 +
  115 + public removeClearAlarmRule() {
  116 + this.alarmFormGroup.patchValue({clearRule: null});
  117 + }
126 118
127 119 public validate(c: FormControl) {
128 120 return (this.alarmFormGroup.valid) ? null : {
... ... @@ -133,12 +125,8 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit
133 125 }
134 126
135 127 private updateModel() {
136   - if (this.alarmFormGroup.valid) {
137   - const value = this.alarmFormGroup.value;
138   - this.modelValue = {...this.modelValue, ...value};
139   - this.propagateChange(this.modelValue);
140   - } else {
141   - this.propagateChange(null);
142   - }
  128 + const value = this.alarmFormGroup.value;
  129 + this.modelValue = {...this.modelValue, ...value};
  130 + this.propagateChange(this.modelValue);
143 131 }
144 132 }
... ...
... ... @@ -17,8 +17,9 @@
17 17 -->
18 18 <div fxLayout="column">
19 19 <div class="tb-device-profile-alarms">
20   - <div *ngFor="let alarmControl of alarmsFormArray().controls; let $index = index; last as isLast;"
21   - fxLayout="column">
  20 + <div *ngFor="let alarmControl of alarmsFormArray().controls; trackBy: trackByAlarm;
  21 + let $index = index; last as isLast;"
  22 + fxLayout="column" [ngStyle]="!isLast ? {paddingBottom: '8px'} : {}">
22 23 <tb-device-profile-alarm [formControl]="alarmControl"
23 24 (removeAlarm)="removeAlarm($index)">
24 25 </tb-device-profile-alarm>
... ...
... ... @@ -17,7 +17,6 @@
17 17
18 18 :host {
19 19 .tb-device-profile-alarms {
20   - max-height: 400px;
21 20 overflow-y: auto;
22 21 &.mat-padding {
23 22 padding: 8px;
... ...
... ... @@ -19,9 +19,12 @@ import {
19 19 AbstractControl,
20 20 ControlValueAccessor,
21 21 FormArray,
22   - FormBuilder, FormControl,
23   - FormGroup, NG_VALIDATORS,
24   - NG_VALUE_ACCESSOR, Validator,
  22 + FormBuilder,
  23 + FormControl,
  24 + FormGroup,
  25 + NG_VALIDATORS,
  26 + NG_VALUE_ACCESSOR,
  27 + Validator,
25 28 Validators
26 29 } from '@angular/forms';
27 30 import { Store } from '@ngrx/store';
... ... @@ -30,10 +33,6 @@ import { coerceBooleanProperty } from '@angular/cdk/coercion';
30 33 import { DeviceProfileAlarm } from '@shared/models/device.models';
31 34 import { guid } from '@core/utils';
32 35 import { Subscription } from 'rxjs';
33   -import {
34   - DeviceProfileAlarmDialogComponent,
35   - DeviceProfileAlarmDialogData
36   -} from './device-profile-alarm-dialog.component';
37 36 import { MatDialog } from '@angular/material/dialog';
38 37
39 38 @Component({
... ... @@ -125,6 +124,14 @@ export class DeviceProfileAlarmsComponent implements ControlValueAccessor, OnIni
125 124 });
126 125 }
127 126
  127 + public trackByAlarm(index: number, alarmControl: AbstractControl): string {
  128 + if (alarmControl) {
  129 + return alarmControl.value.id;
  130 + } else {
  131 + return null;
  132 + }
  133 + }
  134 +
128 135 public removeAlarm(index: number) {
129 136 (this.deviceProfileAlarmsFormGroup.get('alarms') as FormArray).removeAt(index);
130 137 }
... ... @@ -144,22 +151,6 @@ export class DeviceProfileAlarmsComponent implements ControlValueAccessor, OnIni
144 151 const alarmsArray = this.deviceProfileAlarmsFormGroup.get('alarms') as FormArray;
145 152 alarmsArray.push(this.fb.control(alarm, [Validators.required]));
146 153 this.deviceProfileAlarmsFormGroup.updateValueAndValidity();
147   -
148   -/* this.dialog.open<DeviceProfileAlarmDialogComponent, DeviceProfileAlarmDialogData,
149   - DeviceProfileAlarm>(DeviceProfileAlarmDialogComponent, {
150   - disableClose: true,
151   - panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
152   - data: {
153   - isAdd: true,
154   - alarm,
155   - isReadOnly: false
156   - }
157   - }).afterClosed().subscribe(
158   - (deviceProfileAlarm) => {
159   - if (deviceProfileAlarm) {
160   - }
161   - }
162   - ); */
163 154 }
164 155
165 156 public validate(c: FormControl) {
... ... @@ -171,11 +162,11 @@ export class DeviceProfileAlarmsComponent implements ControlValueAccessor, OnIni
171 162 }
172 163
173 164 private updateModel() {
174   - if (this.deviceProfileAlarmsFormGroup.valid) {
  165 +// if (this.deviceProfileAlarmsFormGroup.valid) {
175 166 const alarms: Array<DeviceProfileAlarm> = this.deviceProfileAlarmsFormGroup.get('alarms').value;
176 167 this.propagateChange(alarms);
177   - } else {
  168 + /* } else {
178 169 this.propagateChange(null);
179   - }
  170 + } */
180 171 }
181 172 }
... ...
... ... @@ -15,7 +15,7 @@
15 15 limitations under the License.
16 16
17 17 -->
18   -<form (ngSubmit)="save()" style="min-width: 600px;">
  18 +<form (ngSubmit)="save()" style="min-width: 1000px;">
19 19 <mat-toolbar color="primary">
20 20 <h2>{{ (isAdd ? 'device-profile.add' : 'device-profile.edit' ) | translate }}</h2>
21 21 <span fxFlex></span>
... ...
... ... @@ -52,7 +52,7 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon
52 52 this.config.entityTranslations = entityTypeTranslations.get(EntityType.DEVICE_PROFILE);
53 53 this.config.entityResources = entityTypeResources.get(EntityType.DEVICE_PROFILE);
54 54
55   - this.config.addDialogStyle = {width: '600px'};
  55 + this.config.addDialogStyle = {width: '1000px'};
56 56
57 57 this.config.columns.push(
58 58 new DateEntityTableColumn<DeviceProfile>('createdTime', 'common.created-time', this.datePipe, '150px'),
... ...
... ... @@ -148,12 +148,16 @@ export function createDefaultFilterPredicateInfo(valueType: EntityKeyValueType,
148 148 const predicate = createDefaultFilterPredicate(valueType, complex);
149 149 return {
150 150 keyFilterPredicate: predicate,
151   - userInfo: {
152   - editable: true,
153   - label: '',
154   - autogeneratedLabel: true,
155   - order: 0
156   - }
  151 + userInfo: createDefaultFilterPredicateUserInfo()
  152 + };
  153 +}
  154 +
  155 +export function createDefaultFilterPredicateUserInfo(): KeyFilterPredicateUserInfo {
  156 + return {
  157 + editable: true,
  158 + label: '',
  159 + autogeneratedLabel: true,
  160 + order: 0
157 161 };
158 162 }
159 163
... ... @@ -334,6 +338,7 @@ export interface KeyFilterPredicateInfo {
334 338
335 339 export interface KeyFilter {
336 340 key: EntityKey;
  341 + valueType: EntityKeyValueType;
337 342 predicate: KeyFilterPredicate;
338 343 }
339 344
... ... @@ -353,6 +358,45 @@ export interface FiltersInfo {
353 358 datasourceFilters: {[datasourceIndex: number]: FilterInfo};
354 359 }
355 360
  361 +export function keyFilterInfosToKeyFilters(keyFilterInfos: Array<KeyFilterInfo>): Array<KeyFilter> {
  362 + const keyFilters: Array<KeyFilter> = [];
  363 + for (const keyFilterInfo of keyFilterInfos) {
  364 + const key = keyFilterInfo.key;
  365 + for (const predicate of keyFilterInfo.predicates) {
  366 + const keyFilter: KeyFilter = {
  367 + key,
  368 + valueType: keyFilterInfo.valueType,
  369 + predicate: keyFilterPredicateInfoToKeyFilterPredicate(predicate)
  370 + };
  371 + keyFilters.push(keyFilter);
  372 + }
  373 + }
  374 + return keyFilters;
  375 +}
  376 +
  377 +export function keyFiltersToKeyFilterInfos(keyFilters: Array<KeyFilter>): Array<KeyFilterInfo> {
  378 + const keyFilterInfos: Array<KeyFilterInfo> = [];
  379 + const keyFilterInfoMap: {[infoKey: string]: KeyFilterInfo} = {};
  380 + for (const keyFilter of keyFilters) {
  381 + const key = keyFilter.key;
  382 + const infoKey = key.key + key.type + keyFilter.valueType;
  383 + let keyFilterInfo = keyFilterInfoMap[infoKey];
  384 + if (!keyFilterInfo) {
  385 + keyFilterInfo = {
  386 + key,
  387 + valueType: keyFilter.valueType,
  388 + predicates: []
  389 + };
  390 + keyFilterInfoMap[infoKey] = keyFilterInfo;
  391 + keyFilterInfos.push(keyFilterInfo);
  392 + }
  393 + if (keyFilter.predicate) {
  394 + keyFilterInfo.predicates.push(keyFilterPredicateToKeyFilterPredicateInfo(keyFilter.predicate));
  395 + }
  396 + }
  397 + return keyFilterInfos;
  398 +}
  399 +
356 400 export function filterInfoToKeyFilters(filter: FilterInfo): Array<KeyFilter> {
357 401 const keyFilterInfos = filter.keyFilters;
358 402 const keyFilters: Array<KeyFilter> = [];
... ... @@ -361,6 +405,7 @@ export function filterInfoToKeyFilters(filter: FilterInfo): Array<KeyFilter> {
361 405 for (const predicate of keyFilterInfo.predicates) {
362 406 const keyFilter: KeyFilter = {
363 407 key,
  408 + valueType: keyFilterInfo.valueType,
364 409 predicate: keyFilterPredicateInfoToKeyFilterPredicate(predicate)
365 410 };
366 411 keyFilters.push(keyFilter);
... ... @@ -383,6 +428,26 @@ export function keyFilterPredicateInfoToKeyFilterPredicate(keyFilterPredicateInf
383 428 return keyFilterPredicate;
384 429 }
385 430
  431 +export function keyFilterPredicateToKeyFilterPredicateInfo(keyFilterPredicate: KeyFilterPredicate): KeyFilterPredicateInfo {
  432 + const keyFilterPredicateInfo: KeyFilterPredicateInfo = {
  433 + keyFilterPredicate: null,
  434 + userInfo: null
  435 + };
  436 + if (keyFilterPredicate.type === FilterPredicateType.COMPLEX) {
  437 + const complexPredicate = keyFilterPredicate as ComplexFilterPredicate;
  438 + const predicateInfos = complexPredicate.predicates.map(
  439 + predicate => keyFilterPredicateToKeyFilterPredicateInfo(predicate));
  440 + keyFilterPredicateInfo.keyFilterPredicate = {
  441 + predicates: predicateInfos,
  442 + operation: complexPredicate.operation,
  443 + type: FilterPredicateType.COMPLEX
  444 + } as ComplexFilterPredicateInfo;
  445 + } else {
  446 + keyFilterPredicateInfo.keyFilterPredicate = keyFilterPredicate;
  447 + }
  448 + return keyFilterPredicateInfo;
  449 +}
  450 +
386 451 export function isFilterEditable(filter: FilterInfo): boolean {
387 452 if (filter.editable) {
388 453 return filter.keyFilters.some(value => isKeyFilterInfoEditable(value));
... ...
... ... @@ -815,8 +815,21 @@
815 815 "create-alarm-rules": "Create alarm rules",
816 816 "clear-alarm-rule": "Clear alarm rule",
817 817 "add-create-alarm-rule": "Add create alarm rule",
  818 + "add-clear-alarm-rule": "Add clear alarm rule",
818 819 "select-alarm-severity": "Select alarm severity",
819   - "alarm-severity-required": "Alarm severity is required."
  820 + "alarm-severity-required": "Alarm severity is required.",
  821 + "condition-duration": "Condition duration",
  822 + "condition-duration-value": "Duration value",
  823 + "condition-duration-time-unit": "Time unit",
  824 + "condition-duration-value-range": "Duration value should be in a range from 1 to 2147483647.",
  825 + "condition-duration-value-required": "Duration value is required.",
  826 + "condition-duration-time-unit-required": "Time unit is required.",
  827 + "advanced-settings": "Advanced settings",
  828 + "propagate-alarm": "Propagate alarm",
  829 + "alarm-details": "Alarm details",
  830 + "alarm-rule-condition": "Alarm rule condition",
  831 + "enter-alarm-rule-condition-prompt": "Please add alarm rule condition",
  832 + "edit-alarm-rule-condition": "Edit alarm rule condition"
820 833 },
821 834 "dialog": {
822 835 "close": "Close dialog"
... ... @@ -1286,6 +1299,7 @@
1286 1299 "complex-filter": "Complex filter",
1287 1300 "edit-complex-filter": "Edit complex filter",
1288 1301 "edit-filter-user-params": "Edit filter predicate user parameters",
  1302 + "filter-user-params": "Filter predicate user parameters",
1289 1303 "user-parameters": "User parameters",
1290 1304 "display-label": "Label to display",
1291 1305 "autogenerated-label": "Auto generate label",
... ...
... ... @@ -563,7 +563,7 @@ mat-label {
563 563 }
564 564 }
565 565
566   - mat-toolbar.mat-table-toolbar:not(.mat-primary), .mat-cell {
  566 + mat-toolbar.mat-table-toolbar:not(.mat-primary), .mat-cell, .mat-expansion-panel-header {
567 567 button.mat-icon-button {
568 568 mat-icon {
569 569 color: rgba(0, 0, 0, .54);
... ...