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,6 +21,7 @@ import lombok.Data;
21 public class KeyFilter { 21 public class KeyFilter {
22 22
23 private EntityKey key; 23 private EntityKey key;
  24 + private EntityKeyValueType valueType;
24 private KeyFilterPredicate predicate; 25 private KeyFilterPredicate predicate;
25 26
26 } 27 }
@@ -62,6 +62,7 @@ import { @@ -62,6 +62,7 @@ import {
62 entityInfoFields, 62 entityInfoFields,
63 EntityKey, 63 EntityKey,
64 EntityKeyType, 64 EntityKeyType,
  65 + EntityKeyValueType,
65 FilterPredicateType, 66 FilterPredicateType,
66 singleEntityDataPageLink, 67 singleEntityDataPageLink,
67 StringOperation 68 StringOperation
@@ -399,6 +400,7 @@ export class EntityService { @@ -399,6 +400,7 @@ export class EntityService {
399 keyFilters: searchText && searchText.length ? [ 400 keyFilters: searchText && searchText.length ? [
400 { 401 {
401 key: nameField, 402 key: nameField,
  403 + valueType: EntityKeyValueType.STRING,
402 predicate: { 404 predicate: {
403 type: FilterPredicateType.STRING, 405 type: FilterPredicateType.STRING,
404 operation: StringOperation.STARTS_WITH, 406 operation: StringOperation.STARTS_WITH,
@@ -593,10 +595,10 @@ export class EntityService { @@ -593,10 +595,10 @@ export class EntityService {
593 return entityTypes; 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 const entityFieldKeys: string[] = [entityFields.createdTime.keyName]; 599 const entityFieldKeys: string[] = [entityFields.createdTime.keyName];
598 const query = searchText.toLowerCase(); 600 const query = searchText.toLowerCase();
599 - switch(entityType) { 601 + switch (entityType) {
600 case EntityType.USER: 602 case EntityType.USER:
601 entityFieldKeys.push(entityFields.name.keyName); 603 entityFieldKeys.push(entityFields.name.keyName);
602 entityFieldKeys.push(entityFields.email.keyName); 604 entityFieldKeys.push(entityFields.email.keyName);
@@ -863,7 +865,7 @@ export class EntityService { @@ -863,7 +865,7 @@ export class EntityService {
863 const tasks: Observable<any>[] = []; 865 const tasks: Observable<any>[] = [];
864 const result: Device | Asset = entity as (Device | Asset); 866 const result: Device | Asset = entity as (Device | Asset);
865 const additionalInfo = result.additionalInfo || {}; 867 const additionalInfo = result.additionalInfo || {};
866 - if(result.label !== entityData.label || 868 + if (result.label !== entityData.label ||
867 result.type !== entityData.type || 869 result.type !== entityData.type ||
868 additionalInfo.description !== entityData.description || 870 additionalInfo.description !== entityData.description ||
869 (result.id.entityType === EntityType.DEVICE && (additionalInfo.gateway !== entityData.gateway)) ) { 871 (result.id.entityType === EntityType.DEVICE && (additionalInfo.gateway !== entityData.gateway)) ) {
@@ -251,13 +251,14 @@ export class EntityDetailsPanelComponent extends PageComponent implements OnInit @@ -251,13 +251,14 @@ export class EntityDetailsPanelComponent extends PageComponent implements OnInit
251 } 251 }
252 252
253 onToggleEditMode(isEdit: boolean) { 253 onToggleEditMode(isEdit: boolean) {
254 - this.isEdit = isEdit;  
255 - if (!this.isEdit) { 254 + if (!isEdit) {
256 this.entityComponent.entity = this.entity; 255 this.entityComponent.entity = this.entity;
257 if (this.entityTabsComponent) { 256 if (this.entityTabsComponent) {
258 this.entityTabsComponent.entity = this.entity; 257 this.entityTabsComponent.entity = this.entity;
259 } 258 }
  259 + this.isEdit = isEdit;
260 } else { 260 } else {
  261 + this.isEdit = isEdit;
261 this.editingEntity = deepClone(this.entity); 262 this.editingEntity = deepClone(this.entity);
262 this.entityComponent.entity = this.editingEntity; 263 this.entityComponent.entity = this.editingEntity;
263 if (this.entityTabsComponent) { 264 if (this.entityTabsComponent) {
@@ -65,7 +65,6 @@ export abstract class EntityComponent<T extends BaseData<HasId>, @@ -65,7 +65,6 @@ export abstract class EntityComponent<T extends BaseData<HasId>,
65 set entity(entity: T) { 65 set entity(entity: T) {
66 this.entityValue = entity; 66 this.entityValue = entity;
67 if (this.entityForm) { 67 if (this.entityForm) {
68 - this.entityForm.reset(undefined, {emitEvent: false});  
69 this.entityForm.markAsPristine(); 68 this.entityForm.markAsPristine();
70 this.updateForm(entity); 69 this.updateForm(entity);
71 } 70 }
@@ -37,6 +37,7 @@ @@ -37,6 +37,7 @@
37 </mat-form-field> 37 </mat-form-field>
38 <tb-filter-predicate-list 38 <tb-filter-predicate-list
39 [valueType]="data.valueType" 39 [valueType]="data.valueType"
  40 + [displayUserParameters]="data.displayUserParameters"
40 [operation]="complexFilterFormGroup.get('operation').value" 41 [operation]="complexFilterFormGroup.get('operation').value"
41 [key]="data.key" 42 [key]="data.key"
42 formControlName="predicates"> 43 formControlName="predicates">
@@ -45,6 +46,7 @@ @@ -45,6 +46,7 @@
45 </div> 46 </div>
46 <div mat-dialog-actions fxLayoutAlign="end center"> 47 <div mat-dialog-actions fxLayoutAlign="end center">
47 <button mat-raised-button color="primary" 48 <button mat-raised-button color="primary"
  49 + *ngIf="!data.readonly"
48 type="submit" 50 type="submit"
49 [disabled]="(isLoading$ | async) || complexFilterFormGroup.invalid || !complexFilterFormGroup.dirty"> 51 [disabled]="(isLoading$ | async) || complexFilterFormGroup.invalid || !complexFilterFormGroup.dirty">
50 {{ (isAdd ? 'action.add' : 'action.update') | translate }} 52 {{ (isAdd ? 'action.add' : 'action.update') | translate }}
@@ -54,7 +56,7 @@ @@ -54,7 +56,7 @@
54 [disabled]="(isLoading$ | async)" 56 [disabled]="(isLoading$ | async)"
55 (click)="cancel()" 57 (click)="cancel()"
56 cdkFocusInitial> 58 cdkFocusInitial>
57 - {{ 'action.cancel' | translate }} 59 + {{ (data.readonly ? 'action.close' : 'action.cancel') | translate }}
58 </button> 60 </button>
59 </div> 61 </div>
60 </form> 62 </form>
@@ -32,9 +32,10 @@ import { @@ -32,9 +32,10 @@ import {
32 export interface ComplexFilterPredicateDialogData { 32 export interface ComplexFilterPredicateDialogData {
33 complexPredicate: ComplexFilterPredicateInfo; 33 complexPredicate: ComplexFilterPredicateInfo;
34 key: string; 34 key: string;
35 - disabled: boolean; 35 + readonly: boolean;
36 isAdd: boolean; 36 isAdd: boolean;
37 valueType: EntityKeyValueType; 37 valueType: EntityKeyValueType;
  38 + displayUserParameters: boolean;
38 } 39 }
39 40
40 @Component({ 41 @Component({
@@ -73,6 +74,9 @@ export class ComplexFilterPredicateDialogComponent extends @@ -73,6 +74,9 @@ export class ComplexFilterPredicateDialogComponent extends
73 predicates: [this.data.complexPredicate.predicates, [Validators.required]] 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 ngOnInit(): void { 82 ngOnInit(): void {
@@ -19,11 +19,10 @@ @@ -19,11 +19,10 @@
19 <mat-label translate>filter.complex-filter</mat-label> 19 <mat-label translate>filter.complex-filter</mat-label>
20 <button mat-icon-button color="primary" 20 <button mat-icon-button color="primary"
21 class="tb-mat-32" 21 class="tb-mat-32"
22 - [fxShow]="!disabled"  
23 type="button" 22 type="button"
24 (click)="openComplexFilterDialog()" 23 (click)="openComplexFilterDialog()"
25 - matTooltip="{{ 'filter.edit-complex-filter' | translate }}" 24 + matTooltip="{{ (disabled ? 'filter.complex-filter' : 'filter.edit-complex-filter') | translate }}"
26 matTooltipPosition="above"> 25 matTooltipPosition="above">
27 - <mat-icon>edit</mat-icon> 26 + <mat-icon>{{ disabled ? 'more_vert' : 'edit' }}</mat-icon>
28 </button> 27 </button>
29 </div> 28 </div>
@@ -48,6 +48,8 @@ export class ComplexFilterPredicateComponent implements ControlValueAccessor, On @@ -48,6 +48,8 @@ export class ComplexFilterPredicateComponent implements ControlValueAccessor, On
48 48
49 @Input() key: string; 49 @Input() key: string;
50 50
  51 + @Input() displayUserParameters = true;
  52 +
51 private propagateChange = null; 53 private propagateChange = null;
52 54
53 private complexFilterPredicate: ComplexFilterPredicateInfo; 55 private complexFilterPredicate: ComplexFilterPredicateInfo;
@@ -79,11 +81,12 @@ export class ComplexFilterPredicateComponent implements ControlValueAccessor, On @@ -79,11 +81,12 @@ export class ComplexFilterPredicateComponent implements ControlValueAccessor, On
79 disableClose: true, 81 disableClose: true,
80 panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], 82 panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
81 data: { 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 valueType: this.valueType, 86 valueType: this.valueType,
85 isAdd: false, 87 isAdd: false,
86 - key: this.key 88 + key: this.key,
  89 + displayUserParameters: this.displayUserParameters
87 } 90 }
88 }).afterClosed().subscribe( 91 }).afterClosed().subscribe(
89 (result) => { 92 (result) => {
@@ -33,7 +33,8 @@ @@ -33,7 +33,8 @@
33 </div> 33 </div>
34 <label fxFlex="60" translate class="tb-title no-padding">filter.value</label> 34 <label fxFlex="60" translate class="tb-title no-padding">filter.value</label>
35 </div> 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 <span [fxShow]="!disabled" style="min-width: 40px;">&nbsp;</span> 38 <span [fxShow]="!disabled" style="min-width: 40px;">&nbsp;</span>
38 </div> 39 </div>
39 </div> 40 </div>
@@ -50,6 +51,7 @@ @@ -50,6 +51,7 @@
50 <tb-filter-predicate 51 <tb-filter-predicate
51 fxFlex 52 fxFlex
52 [valueType]="valueType" 53 [valueType]="valueType"
  54 + [displayUserParameters]="displayUserParameters"
53 [key]="key" 55 [key]="key"
54 [formControl]="predicateControl"> 56 [formControl]="predicateControl">
55 </tb-filter-predicate> 57 </tb-filter-predicate>
@@ -62,6 +62,8 @@ export class FilterPredicateListComponent implements ControlValueAccessor, OnIni @@ -62,6 +62,8 @@ export class FilterPredicateListComponent implements ControlValueAccessor, OnIni
62 62
63 @Input() operation: ComplexOperation = ComplexOperation.AND; 63 @Input() operation: ComplexOperation = ComplexOperation.AND;
64 64
  65 + @Input() displayUserParameters = true;
  66 +
65 filterListFormGroup: FormGroup; 67 filterListFormGroup: FormGroup;
66 68
67 valueTypeEnum = EntityKeyValueType; 69 valueTypeEnum = EntityKeyValueType;
@@ -150,10 +152,11 @@ export class FilterPredicateListComponent implements ControlValueAccessor, OnIni @@ -150,10 +152,11 @@ export class FilterPredicateListComponent implements ControlValueAccessor, OnIni
150 panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], 152 panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
151 data: { 153 data: {
152 complexPredicate: predicate.keyFilterPredicate as ComplexFilterPredicateInfo, 154 complexPredicate: predicate.keyFilterPredicate as ComplexFilterPredicateInfo,
153 - disabled: this.disabled, 155 + readonly: this.disabled,
154 valueType: this.valueType, 156 valueType: this.valueType,
155 key: this.key, 157 key: this.key,
156 - isAdd: true 158 + isAdd: true,
  159 + displayUserParameters: this.displayUserParameters
157 } 160 }
158 }).afterClosed().pipe( 161 }).afterClosed().pipe(
159 map((result) => { 162 map((result) => {
@@ -35,11 +35,12 @@ @@ -35,11 +35,12 @@
35 <tb-complex-filter-predicate 35 <tb-complex-filter-predicate
36 [key]="key" 36 [key]="key"
37 [valueType]="valueType" 37 [valueType]="valueType"
  38 + [displayUserParameters]="displayUserParameters"
38 formControlName="predicate"> 39 formControlName="predicate">
39 </tb-complex-filter-predicate> 40 </tb-complex-filter-predicate>
40 </ng-template> 41 </ng-template>
41 </div> 42 </div>
42 - <tb-filter-user-info *ngIf="type !== filterPredicateType.COMPLEX" 43 + <tb-filter-user-info *ngIf="type !== filterPredicateType.COMPLEX && displayUserParameters"
43 style="width: 60px;" 44 style="width: 60px;"
44 fxLayout="row" fxLayoutAlign="center" 45 fxLayout="row" fxLayoutAlign="center"
45 [valueType]="valueType" 46 [valueType]="valueType"
@@ -41,6 +41,8 @@ export class FilterPredicateComponent implements ControlValueAccessor, OnInit { @@ -41,6 +41,8 @@ export class FilterPredicateComponent implements ControlValueAccessor, OnInit {
41 41
42 @Input() key: string; 42 @Input() key: string;
43 43
  44 + @Input() displayUserParameters = true;
  45 +
44 filterPredicateFormGroup: FormGroup; 46 filterPredicateFormGroup: FormGroup;
45 47
46 type: FilterPredicateType; 48 type: FilterPredicateType;
@@ -17,7 +17,7 @@ @@ -17,7 +17,7 @@
17 --> 17 -->
18 <form [formGroup]="filterUserInfoFormGroup" (ngSubmit)="save()" style="width: 500px;"> 18 <form [formGroup]="filterUserInfoFormGroup" (ngSubmit)="save()" style="width: 500px;">
19 <mat-toolbar color="primary"> 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 <span fxFlex></span> 21 <span fxFlex></span>
22 <button mat-icon-button 22 <button mat-icon-button
23 (click)="cancel()" 23 (click)="cancel()"
@@ -47,6 +47,7 @@ @@ -47,6 +47,7 @@
47 </div> 47 </div>
48 <div mat-dialog-actions fxLayoutAlign="end center"> 48 <div mat-dialog-actions fxLayoutAlign="end center">
49 <button mat-raised-button color="primary" 49 <button mat-raised-button color="primary"
  50 + *ngIf="!data.readonly"
50 type="submit" 51 type="submit"
51 [disabled]="(isLoading$ | async) || filterUserInfoFormGroup.invalid || !filterUserInfoFormGroup.dirty"> 52 [disabled]="(isLoading$ | async) || filterUserInfoFormGroup.invalid || !filterUserInfoFormGroup.dirty">
52 {{ 'action.update' | translate }} 53 {{ 'action.update' | translate }}
@@ -56,7 +57,7 @@ @@ -56,7 +57,7 @@
56 [disabled]="(isLoading$ | async)" 57 [disabled]="(isLoading$ | async)"
57 (click)="cancel()" 58 (click)="cancel()"
58 cdkFocusInitial> 59 cdkFocusInitial>
59 - {{ 'action.cancel' | translate }} 60 + {{ (data.readonly ? 'action.close' : 'action.cancel') | translate }}
60 </button> 61 </button>
61 </div> 62 </div>
62 </form> 63 </form>
@@ -23,7 +23,7 @@ import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Valida @@ -23,7 +23,7 @@ import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Valida
23 import { Router } from '@angular/router'; 23 import { Router } from '@angular/router';
24 import { DialogComponent } from '@app/shared/components/dialog.component'; 24 import { DialogComponent } from '@app/shared/components/dialog.component';
25 import { 25 import {
26 - BooleanOperation, 26 + BooleanOperation, createDefaultFilterPredicateUserInfo,
27 EntityKeyValueType, generateUserFilterValueLabel, 27 EntityKeyValueType, generateUserFilterValueLabel,
28 KeyFilterPredicateUserInfo, NumericOperation, 28 KeyFilterPredicateUserInfo, NumericOperation,
29 StringOperation 29 StringOperation
@@ -35,6 +35,7 @@ export interface FilterUserInfoDialogData { @@ -35,6 +35,7 @@ export interface FilterUserInfoDialogData {
35 valueType: EntityKeyValueType; 35 valueType: EntityKeyValueType;
36 operation: StringOperation | BooleanOperation | NumericOperation; 36 operation: StringOperation | BooleanOperation | NumericOperation;
37 keyFilterPredicateUserInfo: KeyFilterPredicateUserInfo; 37 keyFilterPredicateUserInfo: KeyFilterPredicateUserInfo;
  38 + readonly: boolean;
38 } 39 }
39 40
40 @Component({ 41 @Component({
@@ -60,18 +61,24 @@ export class FilterUserInfoDialogComponent extends @@ -60,18 +61,24 @@ export class FilterUserInfoDialogComponent extends
60 private translate: TranslateService) { 61 private translate: TranslateService) {
61 super(store, router, dialogRef); 62 super(store, router, dialogRef);
62 63
  64 + const userInfo: KeyFilterPredicateUserInfo = this.data.keyFilterPredicateUserInfo || createDefaultFilterPredicateUserInfo();
  65 +
63 this.filterUserInfoFormGroup = this.fb.group( 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 this.onAutogeneratedLabelChange(); 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 private onAutogeneratedLabelChange() { 84 private onAutogeneratedLabelChange() {
@@ -17,10 +17,9 @@ @@ -17,10 +17,9 @@
17 --> 17 -->
18 <button mat-icon-button color="primary" 18 <button mat-icon-button color="primary"
19 class="tb-mat-32" 19 class="tb-mat-32"
20 - [fxShow]="!disabled"  
21 type="button" 20 type="button"
22 (click)="openFilterUserInfoDialog()" 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 matTooltipPosition="above"> 23 matTooltipPosition="above">
25 <mat-icon>settings</mat-icon> 24 <mat-icon>settings</mat-icon>
26 </button> 25 </button>
@@ -76,7 +76,7 @@ export class FilterUserInfoComponent implements ControlValueAccessor, OnInit { @@ -76,7 +76,7 @@ export class FilterUserInfoComponent implements ControlValueAccessor, OnInit {
76 this.keyFilterPredicateUserInfo = keyFilterPredicateUserInfo; 76 this.keyFilterPredicateUserInfo = keyFilterPredicateUserInfo;
77 } 77 }
78 78
79 - private openFilterUserInfoDialog() { 79 + public openFilterUserInfoDialog() {
80 this.dialog.open<FilterUserInfoDialogComponent, FilterUserInfoDialogData, 80 this.dialog.open<FilterUserInfoDialogComponent, FilterUserInfoDialogData,
81 KeyFilterPredicateUserInfo>(FilterUserInfoDialogComponent, { 81 KeyFilterPredicateUserInfo>(FilterUserInfoDialogComponent, {
82 disableClose: true, 82 disableClose: true,
@@ -85,7 +85,8 @@ export class FilterUserInfoComponent implements ControlValueAccessor, OnInit { @@ -85,7 +85,8 @@ export class FilterUserInfoComponent implements ControlValueAccessor, OnInit {
85 keyFilterPredicateUserInfo: deepClone(this.keyFilterPredicateUserInfo), 85 keyFilterPredicateUserInfo: deepClone(this.keyFilterPredicateUserInfo),
86 valueType: this.valueType, 86 valueType: this.valueType,
87 key: this.key, 87 key: this.key,
88 - operation: this.operation 88 + operation: this.operation,
  89 + readonly: this.disabled
89 } 90 }
90 }).afterClosed().subscribe( 91 }).afterClosed().subscribe(
91 (result) => { 92 (result) => {
@@ -17,7 +17,7 @@ @@ -17,7 +17,7 @@
17 --> 17 -->
18 <form [formGroup]="keyFilterFormGroup" (ngSubmit)="save()" style="width: 900px;"> 18 <form [formGroup]="keyFilterFormGroup" (ngSubmit)="save()" style="width: 900px;">
19 <mat-toolbar color="primary"> 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 <span fxFlex></span> 21 <span fxFlex></span>
22 <button mat-icon-button 22 <button mat-icon-button
23 (click)="cancel()" 23 (click)="cancel()"
@@ -70,6 +70,7 @@ @@ -70,6 +70,7 @@
70 </mat-form-field> 70 </mat-form-field>
71 </section> 71 </section>
72 <tb-filter-predicate-list *ngIf="keyFilterFormGroup.get('valueType').value" 72 <tb-filter-predicate-list *ngIf="keyFilterFormGroup.get('valueType').value"
  73 + [displayUserParameters]="data.displayUserParameters"
73 [valueType]="keyFilterFormGroup.get('valueType').value" 74 [valueType]="keyFilterFormGroup.get('valueType').value"
74 [key]="keyFilterFormGroup.get('key.key').value" 75 [key]="keyFilterFormGroup.get('key.key').value"
75 formControlName="predicates"> 76 formControlName="predicates">
@@ -79,6 +80,7 @@ @@ -79,6 +80,7 @@
79 <div mat-dialog-actions fxLayoutAlign="end center"> 80 <div mat-dialog-actions fxLayoutAlign="end center">
80 <button mat-raised-button color="primary" 81 <button mat-raised-button color="primary"
81 type="submit" 82 type="submit"
  83 + *ngIf="!data.readonly"
82 [disabled]="(isLoading$ | async) || keyFilterFormGroup.invalid || !keyFilterFormGroup.dirty"> 84 [disabled]="(isLoading$ | async) || keyFilterFormGroup.invalid || !keyFilterFormGroup.dirty">
83 {{ (data.isAdd ? 'action.add' : 'action.update') | translate }} 85 {{ (data.isAdd ? 'action.add' : 'action.update') | translate }}
84 </button> 86 </button>
@@ -87,7 +89,7 @@ @@ -87,7 +89,7 @@
87 [disabled]="(isLoading$ | async)" 89 [disabled]="(isLoading$ | async)"
88 (click)="cancel()" 90 (click)="cancel()"
89 cdkFocusInitial> 91 cdkFocusInitial>
90 - {{ 'action.cancel' | translate }} 92 + {{ (data.readonly ? 'action.close' : 'action.cancel') | translate }}
91 </button> 93 </button>
92 </div> 94 </div>
93 </form> 95 </form>
@@ -39,6 +39,9 @@ import { filter, map, startWith } from 'rxjs/operators'; @@ -39,6 +39,9 @@ import { filter, map, startWith } from 'rxjs/operators';
39 export interface KeyFilterDialogData { 39 export interface KeyFilterDialogData {
40 keyFilter: KeyFilterInfo; 40 keyFilter: KeyFilterInfo;
41 isAdd: boolean; 41 isAdd: boolean;
  42 + displayUserParameters: boolean;
  43 + readonly: boolean;
  44 + telemetryKeysOnly: boolean;
42 } 45 }
43 46
44 @Component({ 47 @Component({
@@ -53,7 +56,10 @@ export class KeyFilterDialogComponent extends @@ -53,7 +56,10 @@ export class KeyFilterDialogComponent extends
53 56
54 keyFilterFormGroup: FormGroup; 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 entityKeyTypeTranslations = entityKeyTypeTranslationMap; 64 entityKeyTypeTranslations = entityKeyTypeTranslationMap;
59 65
@@ -95,32 +101,37 @@ export class KeyFilterDialogComponent extends @@ -95,32 +101,37 @@ export class KeyFilterDialogComponent extends
95 predicates: [this.data.keyFilter.predicates, [Validators.required]] 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 this.entityFields = entityFields; 136 this.entityFields = entityFields;
126 this.entityFieldsList = Object.values(entityFields).map(entityField => entityField.keyName).sort(); 137 this.entityFieldsList = Object.values(entityFields).map(entityField => entityField.keyName).sort();
@@ -46,9 +46,9 @@ @@ -46,9 +46,9 @@
46 <button mat-icon-button color="primary" 46 <button mat-icon-button color="primary"
47 type="button" 47 type="button"
48 (click)="editKeyFilter($index)" 48 (click)="editKeyFilter($index)"
49 - matTooltip="{{ 'filter.edit-key-filter' | translate }}" 49 + matTooltip="{{ (disabled ? 'filter.key-filter' : 'filter.edit-key-filter') | translate }}"
50 matTooltipPosition="above"> 50 matTooltipPosition="above">
51 - <mat-icon>edit</mat-icon> 51 + <mat-icon>{{disabled ? 'more_vert' : 'edit'}}</mat-icon>
52 </button> 52 </button>
53 <button mat-icon-button color="primary" 53 <button mat-icon-button color="primary"
54 [fxShow]="!disabled" 54 [fxShow]="!disabled"
@@ -46,6 +46,10 @@ export class KeyFilterListComponent implements ControlValueAccessor, OnInit { @@ -46,6 +46,10 @@ export class KeyFilterListComponent implements ControlValueAccessor, OnInit {
46 46
47 @Input() disabled: boolean; 47 @Input() disabled: boolean;
48 48
  49 + @Input() displayUserParameters = true;
  50 +
  51 + @Input() telemetryKeysOnly = false;
  52 +
49 keyFilterListFormGroup: FormGroup; 53 keyFilterListFormGroup: FormGroup;
50 54
51 entityKeyTypeTranslations = entityKeyTypeTranslationMap; 55 entityKeyTypeTranslations = entityKeyTypeTranslationMap;
@@ -147,8 +151,11 @@ export class KeyFilterListComponent implements ControlValueAccessor, OnInit { @@ -147,8 +151,11 @@ export class KeyFilterListComponent implements ControlValueAccessor, OnInit {
147 disableClose: true, 151 disableClose: true,
148 panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], 152 panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
149 data: { 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 }).afterClosed(); 160 }).afterClosed();
154 } 161 }
@@ -100,10 +100,10 @@ import { MqttDeviceProfileTransportConfigurationComponent } from './profile/devi @@ -100,10 +100,10 @@ import { MqttDeviceProfileTransportConfigurationComponent } from './profile/devi
100 import { Lwm2mDeviceProfileTransportConfigurationComponent } from './profile/device/lwm2m-device-profile-transport-configuration.component'; 100 import { Lwm2mDeviceProfileTransportConfigurationComponent } from './profile/device/lwm2m-device-profile-transport-configuration.component';
101 import { DeviceProfileAlarmsComponent } from './profile/alarm/device-profile-alarms.component'; 101 import { DeviceProfileAlarmsComponent } from './profile/alarm/device-profile-alarms.component';
102 import { DeviceProfileAlarmComponent } from './profile/alarm/device-profile-alarm.component'; 102 import { DeviceProfileAlarmComponent } from './profile/alarm/device-profile-alarm.component';
103 -import { DeviceProfileAlarmDialogComponent } from './profile/alarm/device-profile-alarm-dialog.component';  
104 import { CreateAlarmRulesComponent } from './profile/alarm/create-alarm-rules.component'; 103 import { CreateAlarmRulesComponent } from './profile/alarm/create-alarm-rules.component';
105 import { AlarmRuleComponent } from './profile/alarm/alarm-rule.component'; 104 import { AlarmRuleComponent } from './profile/alarm/alarm-rule.component';
106 import { AlarmRuleConditionComponent } from './profile/alarm/alarm-rule-condition.component'; 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 @NgModule({ 108 @NgModule({
109 declarations: 109 declarations:
@@ -184,9 +184,9 @@ import { AlarmRuleConditionComponent } from './profile/alarm/alarm-rule-conditio @@ -184,9 +184,9 @@ import { AlarmRuleConditionComponent } from './profile/alarm/alarm-rule-conditio
184 DeviceProfileTransportConfigurationComponent, 184 DeviceProfileTransportConfigurationComponent,
185 CreateAlarmRulesComponent, 185 CreateAlarmRulesComponent,
186 AlarmRuleComponent, 186 AlarmRuleComponent,
  187 + AlarmRuleKeyFiltersDialogComponent,
187 AlarmRuleConditionComponent, 188 AlarmRuleConditionComponent,
188 DeviceProfileAlarmComponent, 189 DeviceProfileAlarmComponent,
189 - DeviceProfileAlarmDialogComponent,  
190 DeviceProfileAlarmsComponent, 190 DeviceProfileAlarmsComponent,
191 DeviceProfileDataComponent, 191 DeviceProfileDataComponent,
192 DeviceProfileComponent, 192 DeviceProfileComponent,
@@ -260,9 +260,9 @@ import { AlarmRuleConditionComponent } from './profile/alarm/alarm-rule-conditio @@ -260,9 +260,9 @@ import { AlarmRuleConditionComponent } from './profile/alarm/alarm-rule-conditio
260 DeviceProfileTransportConfigurationComponent, 260 DeviceProfileTransportConfigurationComponent,
261 CreateAlarmRulesComponent, 261 CreateAlarmRulesComponent,
262 AlarmRuleComponent, 262 AlarmRuleComponent,
  263 + AlarmRuleKeyFiltersDialogComponent,
263 AlarmRuleConditionComponent, 264 AlarmRuleConditionComponent,
264 DeviceProfileAlarmComponent, 265 DeviceProfileAlarmComponent,
265 - DeviceProfileAlarmDialogComponent,  
266 DeviceProfileAlarmsComponent, 266 DeviceProfileAlarmsComponent,
267 DeviceProfileDataComponent, 267 DeviceProfileDataComponent,
268 DeviceProfileComponent, 268 DeviceProfileComponent,
@@ -15,5 +15,16 @@ @@ -15,5 +15,16 @@
15 limitations under the License. 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 +19,22 @@ import {
19 ControlValueAccessor, 19 ControlValueAccessor,
20 FormBuilder, 20 FormBuilder,
21 FormControl, 21 FormControl,
22 - FormGroup,  
23 NG_VALIDATORS, 22 NG_VALIDATORS,
24 NG_VALUE_ACCESSOR, 23 NG_VALUE_ACCESSOR,
25 - Validator,  
26 - Validators 24 + Validator
27 } from '@angular/forms'; 25 } from '@angular/forms';
28 -import { AlarmCondition } from '@shared/models/device.models';  
29 import { MatDialog } from '@angular/material/dialog'; 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 @Component({ 34 @Component({
32 selector: 'tb-alarm-rule-condition', 35 selector: 'tb-alarm-rule-condition',
33 templateUrl: './alarm-rule-condition.component.html', 36 templateUrl: './alarm-rule-condition.component.html',
34 - styleUrls: [], 37 + styleUrls: ['./alarm-rule-condition.component.scss'],
35 providers: [ 38 providers: [
36 { 39 {
37 provide: NG_VALUE_ACCESSOR, 40 provide: NG_VALUE_ACCESSOR,
@@ -50,9 +53,9 @@ export class AlarmRuleConditionComponent implements ControlValueAccessor, OnInit @@ -50,9 +53,9 @@ export class AlarmRuleConditionComponent implements ControlValueAccessor, OnInit
50 @Input() 53 @Input()
51 disabled: boolean; 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 private propagateChange = (v: any) => { }; 60 private propagateChange = (v: any) => { };
58 61
@@ -68,45 +71,56 @@ export class AlarmRuleConditionComponent implements ControlValueAccessor, OnInit @@ -68,45 +71,56 @@ export class AlarmRuleConditionComponent implements ControlValueAccessor, OnInit
68 } 71 }
69 72
70 ngOnInit() { 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 setDisabledState(isDisabled: boolean): void { 77 setDisabledState(isDisabled: boolean): void {
82 this.disabled = isDisabled; 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 this.modelValue = value; 82 this.modelValue = value;
92 - this.alarmRuleConditionFormGroup.reset(this.modelValue, {emitEvent: false}); 83 + this.updateConditionInfo();
93 } 84 }
94 85
95 public validate(c: FormControl) { 86 public validate(c: FormControl) {
96 - return (this.alarmRuleConditionFormGroup.valid) ? null : { 87 + return (this.modelValue && this.modelValue.length) ? null : {
97 alarmRuleCondition: { 88 alarmRuleCondition: {
98 valid: false, 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 } else { 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,11 +15,11 @@
15 limitations under the License. 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 <span fxFlex></span> 21 <span fxFlex></span>
22 - <button mat-button mat-icon-button 22 + <button mat-icon-button
23 (click)="cancel()" 23 (click)="cancel()"
24 type="button"> 24 type="button">
25 <mat-icon class="material-icons">close</mat-icon> 25 <mat-icon class="material-icons">close</mat-icon>
@@ -27,39 +27,29 @@ @@ -27,39 +27,29 @@
27 </mat-toolbar> 27 </mat-toolbar>
28 <mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> 28 <mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
29 </mat-progress-bar> 29 </mat-progress-bar>
30 - <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>  
31 <div mat-dialog-content> 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 </fieldset> 39 </fieldset>
48 </div> 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 type="submit" 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 </button> 47 </button>
57 <button mat-button color="primary" 48 <button mat-button color="primary"
58 - style="margin-right: 20px;"  
59 type="button" 49 type="button"
60 [disabled]="(isLoading$ | async)" 50 [disabled]="(isLoading$ | async)"
61 (click)="cancel()" cdkFocusInitial> 51 (click)="cancel()" cdkFocusInitial>
62 - {{ (isReadOnly ? 'action.close' : 'action.cancel') | translate }} 52 + {{ (readonly ? 'action.close' : 'action.cancel') | translate }}
63 </button> 53 </button>
64 </div> 54 </div>
65 </form> 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,70 +14,60 @@
14 /// limitations under the License. 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 import { ErrorStateMatcher } from '@angular/material/core'; 18 import { ErrorStateMatcher } from '@angular/material/core';
24 import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; 19 import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
25 import { Store } from '@ngrx/store'; 20 import { Store } from '@ngrx/store';
26 import { AppState } from '@core/core.state'; 21 import { AppState } from '@core/core.state';
27 import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; 22 import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms';
28 -import { DialogComponent } from '@shared/components/dialog.component';  
29 import { Router } from '@angular/router'; 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 @Component({ 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 styleUrls: [] 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 submitted = false; 48 submitted = false;
54 49
55 constructor(protected store: Store<AppState>, 50 constructor(protected store: Store<AppState>,
56 protected router: Router, 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 @SkipSelf() private errorStateMatcher: ErrorStateMatcher, 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 super(store, router, dialogRef); 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 isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { 71 isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
82 const originalErrorState = this.errorStateMatcher.isErrorState(control, form); 72 const originalErrorState = this.errorStateMatcher.isErrorState(control, form);
83 const customErrorState = !!(control && control.invalid && this.submitted); 73 const customErrorState = !!(control && control.invalid && this.submitted);
@@ -90,10 +80,7 @@ export class DeviceProfileAlarmDialogComponent extends @@ -90,10 +80,7 @@ export class DeviceProfileAlarmDialogComponent extends
90 80
91 save(): void { 81 save(): void {
92 this.submitted = true; 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,8 +15,71 @@
15 limitations under the License. 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 </div> 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,7 +14,7 @@
14 /// limitations under the License. 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 import { 18 import {
19 ControlValueAccessor, 19 ControlValueAccessor,
20 FormBuilder, 20 FormBuilder,
@@ -27,11 +27,13 @@ import { @@ -27,11 +27,13 @@ import {
27 } from '@angular/forms'; 27 } from '@angular/forms';
28 import { AlarmRule } from '@shared/models/device.models'; 28 import { AlarmRule } from '@shared/models/device.models';
29 import { MatDialog } from '@angular/material/dialog'; 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 @Component({ 33 @Component({
32 selector: 'tb-alarm-rule', 34 selector: 'tb-alarm-rule',
33 templateUrl: './alarm-rule.component.html', 35 templateUrl: './alarm-rule.component.html',
34 - styleUrls: [], 36 + styleUrls: ['./alarm-rule.component.scss'],
35 providers: [ 37 providers: [
36 { 38 {
37 provide: NG_VALUE_ACCESSOR, 39 provide: NG_VALUE_ACCESSOR,
@@ -47,9 +49,23 @@ import { MatDialog } from '@angular/material/dialog'; @@ -47,9 +49,23 @@ import { MatDialog } from '@angular/material/dialog';
47 }) 49 })
48 export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validator { 50 export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validator {
49 51
  52 + timeUnits = Object.keys(TimeUnit);
  53 + timeUnitTranslations = timeUnitTranslationMap;
  54 +
50 @Input() 55 @Input()
51 disabled: boolean; 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 private modelValue: AlarmRule; 69 private modelValue: AlarmRule;
54 70
55 alarmRuleFormGroup: FormGroup; 71 alarmRuleFormGroup: FormGroup;
@@ -69,7 +85,11 @@ export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validat @@ -69,7 +85,11 @@ export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validat
69 85
70 ngOnInit() { 86 ngOnInit() {
71 this.alarmRuleFormGroup = this.fb.group({ 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 alarmDetails: [null] 93 alarmDetails: [null]
74 }); 94 });
75 this.alarmRuleFormGroup.valueChanges.subscribe(() => { 95 this.alarmRuleFormGroup.valueChanges.subscribe(() => {
@@ -88,24 +108,51 @@ export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validat @@ -88,24 +108,51 @@ export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validat
88 108
89 writeValue(value: AlarmRule): void { 109 writeValue(value: AlarmRule): void {
90 this.modelValue = value; 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 public validate(c: FormControl) { 116 public validate(c: FormControl) {
95 - return (this.alarmRuleFormGroup.valid) ? null : { 117 + return (!this.required && !this.modelValue || this.alarmRuleFormGroup.valid) ? null : {
96 alarmRule: { 118 alarmRule: {
97 valid: false, 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 private updateModel() { 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 this.modelValue = {...this.modelValue, ...value}; 154 this.modelValue = {...this.modelValue, ...value};
106 this.propagateChange(this.modelValue); 155 this.propagateChange(this.modelValue);
107 - } else {  
108 - this.propagateChange(null);  
109 } 156 }
110 } 157 }
111 } 158 }
@@ -17,38 +17,42 @@ @@ -17,38 +17,42 @@
17 --> 17 -->
18 <div fxFlex fxLayout="column"> 18 <div fxFlex fxLayout="column">
19 <div *ngFor="let createAlarmRuleControl of createAlarmRulesFormArray().controls; let $index = index; 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 <button *ngIf="!disabled && createAlarmRulesFormArray().controls.length > 1" 39 <button *ngIf="!disabled && createAlarmRulesFormArray().controls.length > 1"
37 mat-icon-button color="primary" style="min-width: 40px;" 40 mat-icon-button color="primary" style="min-width: 40px;"
38 type="button" 41 type="button"
39 (click)="removeCreateAlarmRule($index)" 42 (click)="removeCreateAlarmRule($index)"
40 matTooltip="{{ 'action.remove' | translate }}" 43 matTooltip="{{ 'action.remove' | translate }}"
41 matTooltipPosition="above"> 44 matTooltipPosition="above">
42 - <mat-icon>close</mat-icon> 45 + <mat-icon>remove_circle_outline</mat-icon>
43 </button> 46 </button>
44 </div> 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 type="button" 50 type="button"
48 (click)="addCreateAlarmRule()" 51 (click)="addCreateAlarmRule()"
49 matTooltip="{{ 'device-profile.add-create-alarm-rule' | translate }}" 52 matTooltip="{{ 'device-profile.add-create-alarm-rule' | translate }}"
50 matTooltipPosition="above"> 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 </button> 56 </button>
53 </div> 57 </div>
54 </div> 58 </div>
@@ -13,6 +13,23 @@ @@ -13,6 +13,23 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
  16 +
16 :host { 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,15 +150,11 @@ export class CreateAlarmRulesComponent implements ControlValueAccessor, OnInit,
150 } 150 }
151 151
152 private updateModel() { 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,33 +15,80 @@
15 limitations under the License. 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 <div fxFlex fxLayout="column"> 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 </tb-create-alarm-rules> 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 </div> 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,35 +13,42 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 -@import '../scss/constants';  
17 16
18 :host { 17 :host {
19 display: block; 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,17 +19,14 @@ import {
19 ControlValueAccessor, 19 ControlValueAccessor,
20 FormBuilder, 20 FormBuilder,
21 FormControl, 21 FormControl,
22 - FormGroup, NG_VALIDATORS,  
23 - NG_VALUE_ACCESSOR, Validator, 22 + FormGroup,
  23 + NG_VALIDATORS,
  24 + NG_VALUE_ACCESSOR,
  25 + Validator,
24 Validators 26 Validators
25 } from '@angular/forms'; 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 import { MatDialog } from '@angular/material/dialog'; 29 import { MatDialog } from '@angular/material/dialog';
29 -import {  
30 - DeviceProfileAlarmDialogComponent,  
31 - DeviceProfileAlarmDialogData  
32 -} from './device-profile-alarm-dialog.component';  
33 30
34 @Component({ 31 @Component({
35 selector: 'tb-device-profile-alarm', 32 selector: 'tb-device-profile-alarm',
@@ -56,6 +53,8 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit @@ -56,6 +53,8 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit
56 @Output() 53 @Output()
57 removeAlarm = new EventEmitter(); 54 removeAlarm = new EventEmitter();
58 55
  56 + expanded = false;
  57 +
59 private modelValue: DeviceProfileAlarm; 58 private modelValue: DeviceProfileAlarm;
60 59
61 alarmFormGroup: FormGroup; 60 alarmFormGroup: FormGroup;
@@ -98,31 +97,24 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit @@ -98,31 +97,24 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit
98 97
99 writeValue(value: DeviceProfileAlarm): void { 98 writeValue(value: DeviceProfileAlarm): void {
100 this.modelValue = value; 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 public validate(c: FormControl) { 119 public validate(c: FormControl) {
128 return (this.alarmFormGroup.valid) ? null : { 120 return (this.alarmFormGroup.valid) ? null : {
@@ -133,12 +125,8 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit @@ -133,12 +125,8 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit
133 } 125 }
134 126
135 private updateModel() { 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,8 +17,9 @@
17 --> 17 -->
18 <div fxLayout="column"> 18 <div fxLayout="column">
19 <div class="tb-device-profile-alarms"> 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 <tb-device-profile-alarm [formControl]="alarmControl" 23 <tb-device-profile-alarm [formControl]="alarmControl"
23 (removeAlarm)="removeAlarm($index)"> 24 (removeAlarm)="removeAlarm($index)">
24 </tb-device-profile-alarm> 25 </tb-device-profile-alarm>
@@ -17,7 +17,6 @@ @@ -17,7 +17,6 @@
17 17
18 :host { 18 :host {
19 .tb-device-profile-alarms { 19 .tb-device-profile-alarms {
20 - max-height: 400px;  
21 overflow-y: auto; 20 overflow-y: auto;
22 &.mat-padding { 21 &.mat-padding {
23 padding: 8px; 22 padding: 8px;
@@ -19,9 +19,12 @@ import { @@ -19,9 +19,12 @@ import {
19 AbstractControl, 19 AbstractControl,
20 ControlValueAccessor, 20 ControlValueAccessor,
21 FormArray, 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 Validators 28 Validators
26 } from '@angular/forms'; 29 } from '@angular/forms';
27 import { Store } from '@ngrx/store'; 30 import { Store } from '@ngrx/store';
@@ -30,10 +33,6 @@ import { coerceBooleanProperty } from '@angular/cdk/coercion'; @@ -30,10 +33,6 @@ import { coerceBooleanProperty } from '@angular/cdk/coercion';
30 import { DeviceProfileAlarm } from '@shared/models/device.models'; 33 import { DeviceProfileAlarm } from '@shared/models/device.models';
31 import { guid } from '@core/utils'; 34 import { guid } from '@core/utils';
32 import { Subscription } from 'rxjs'; 35 import { Subscription } from 'rxjs';
33 -import {  
34 - DeviceProfileAlarmDialogComponent,  
35 - DeviceProfileAlarmDialogData  
36 -} from './device-profile-alarm-dialog.component';  
37 import { MatDialog } from '@angular/material/dialog'; 36 import { MatDialog } from '@angular/material/dialog';
38 37
39 @Component({ 38 @Component({
@@ -125,6 +124,14 @@ export class DeviceProfileAlarmsComponent implements ControlValueAccessor, OnIni @@ -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 public removeAlarm(index: number) { 135 public removeAlarm(index: number) {
129 (this.deviceProfileAlarmsFormGroup.get('alarms') as FormArray).removeAt(index); 136 (this.deviceProfileAlarmsFormGroup.get('alarms') as FormArray).removeAt(index);
130 } 137 }
@@ -144,22 +151,6 @@ export class DeviceProfileAlarmsComponent implements ControlValueAccessor, OnIni @@ -144,22 +151,6 @@ export class DeviceProfileAlarmsComponent implements ControlValueAccessor, OnIni
144 const alarmsArray = this.deviceProfileAlarmsFormGroup.get('alarms') as FormArray; 151 const alarmsArray = this.deviceProfileAlarmsFormGroup.get('alarms') as FormArray;
145 alarmsArray.push(this.fb.control(alarm, [Validators.required])); 152 alarmsArray.push(this.fb.control(alarm, [Validators.required]));
146 this.deviceProfileAlarmsFormGroup.updateValueAndValidity(); 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 public validate(c: FormControl) { 156 public validate(c: FormControl) {
@@ -171,11 +162,11 @@ export class DeviceProfileAlarmsComponent implements ControlValueAccessor, OnIni @@ -171,11 +162,11 @@ export class DeviceProfileAlarmsComponent implements ControlValueAccessor, OnIni
171 } 162 }
172 163
173 private updateModel() { 164 private updateModel() {
174 - if (this.deviceProfileAlarmsFormGroup.valid) { 165 +// if (this.deviceProfileAlarmsFormGroup.valid) {
175 const alarms: Array<DeviceProfileAlarm> = this.deviceProfileAlarmsFormGroup.get('alarms').value; 166 const alarms: Array<DeviceProfileAlarm> = this.deviceProfileAlarmsFormGroup.get('alarms').value;
176 this.propagateChange(alarms); 167 this.propagateChange(alarms);
177 - } else { 168 + /* } else {
178 this.propagateChange(null); 169 this.propagateChange(null);
179 - } 170 + } */
180 } 171 }
181 } 172 }
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 limitations under the License. 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 <mat-toolbar color="primary"> 19 <mat-toolbar color="primary">
20 <h2>{{ (isAdd ? 'device-profile.add' : 'device-profile.edit' ) | translate }}</h2> 20 <h2>{{ (isAdd ? 'device-profile.add' : 'device-profile.edit' ) | translate }}</h2>
21 <span fxFlex></span> 21 <span fxFlex></span>
@@ -52,7 +52,7 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon @@ -52,7 +52,7 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon
52 this.config.entityTranslations = entityTypeTranslations.get(EntityType.DEVICE_PROFILE); 52 this.config.entityTranslations = entityTypeTranslations.get(EntityType.DEVICE_PROFILE);
53 this.config.entityResources = entityTypeResources.get(EntityType.DEVICE_PROFILE); 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 this.config.columns.push( 57 this.config.columns.push(
58 new DateEntityTableColumn<DeviceProfile>('createdTime', 'common.created-time', this.datePipe, '150px'), 58 new DateEntityTableColumn<DeviceProfile>('createdTime', 'common.created-time', this.datePipe, '150px'),
@@ -148,12 +148,16 @@ export function createDefaultFilterPredicateInfo(valueType: EntityKeyValueType, @@ -148,12 +148,16 @@ export function createDefaultFilterPredicateInfo(valueType: EntityKeyValueType,
148 const predicate = createDefaultFilterPredicate(valueType, complex); 148 const predicate = createDefaultFilterPredicate(valueType, complex);
149 return { 149 return {
150 keyFilterPredicate: predicate, 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,6 +338,7 @@ export interface KeyFilterPredicateInfo {
334 338
335 export interface KeyFilter { 339 export interface KeyFilter {
336 key: EntityKey; 340 key: EntityKey;
  341 + valueType: EntityKeyValueType;
337 predicate: KeyFilterPredicate; 342 predicate: KeyFilterPredicate;
338 } 343 }
339 344
@@ -353,6 +358,45 @@ export interface FiltersInfo { @@ -353,6 +358,45 @@ export interface FiltersInfo {
353 datasourceFilters: {[datasourceIndex: number]: FilterInfo}; 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 export function filterInfoToKeyFilters(filter: FilterInfo): Array<KeyFilter> { 400 export function filterInfoToKeyFilters(filter: FilterInfo): Array<KeyFilter> {
357 const keyFilterInfos = filter.keyFilters; 401 const keyFilterInfos = filter.keyFilters;
358 const keyFilters: Array<KeyFilter> = []; 402 const keyFilters: Array<KeyFilter> = [];
@@ -361,6 +405,7 @@ export function filterInfoToKeyFilters(filter: FilterInfo): Array<KeyFilter> { @@ -361,6 +405,7 @@ export function filterInfoToKeyFilters(filter: FilterInfo): Array<KeyFilter> {
361 for (const predicate of keyFilterInfo.predicates) { 405 for (const predicate of keyFilterInfo.predicates) {
362 const keyFilter: KeyFilter = { 406 const keyFilter: KeyFilter = {
363 key, 407 key,
  408 + valueType: keyFilterInfo.valueType,
364 predicate: keyFilterPredicateInfoToKeyFilterPredicate(predicate) 409 predicate: keyFilterPredicateInfoToKeyFilterPredicate(predicate)
365 }; 410 };
366 keyFilters.push(keyFilter); 411 keyFilters.push(keyFilter);
@@ -383,6 +428,26 @@ export function keyFilterPredicateInfoToKeyFilterPredicate(keyFilterPredicateInf @@ -383,6 +428,26 @@ export function keyFilterPredicateInfoToKeyFilterPredicate(keyFilterPredicateInf
383 return keyFilterPredicate; 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 export function isFilterEditable(filter: FilterInfo): boolean { 451 export function isFilterEditable(filter: FilterInfo): boolean {
387 if (filter.editable) { 452 if (filter.editable) {
388 return filter.keyFilters.some(value => isKeyFilterInfoEditable(value)); 453 return filter.keyFilters.some(value => isKeyFilterInfoEditable(value));
@@ -815,8 +815,21 @@ @@ -815,8 +815,21 @@
815 "create-alarm-rules": "Create alarm rules", 815 "create-alarm-rules": "Create alarm rules",
816 "clear-alarm-rule": "Clear alarm rule", 816 "clear-alarm-rule": "Clear alarm rule",
817 "add-create-alarm-rule": "Add create alarm rule", 817 "add-create-alarm-rule": "Add create alarm rule",
  818 + "add-clear-alarm-rule": "Add clear alarm rule",
818 "select-alarm-severity": "Select alarm severity", 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 "dialog": { 834 "dialog": {
822 "close": "Close dialog" 835 "close": "Close dialog"
@@ -1286,6 +1299,7 @@ @@ -1286,6 +1299,7 @@
1286 "complex-filter": "Complex filter", 1299 "complex-filter": "Complex filter",
1287 "edit-complex-filter": "Edit complex filter", 1300 "edit-complex-filter": "Edit complex filter",
1288 "edit-filter-user-params": "Edit filter predicate user parameters", 1301 "edit-filter-user-params": "Edit filter predicate user parameters",
  1302 + "filter-user-params": "Filter predicate user parameters",
1289 "user-parameters": "User parameters", 1303 "user-parameters": "User parameters",
1290 "display-label": "Label to display", 1304 "display-label": "Label to display",
1291 "autogenerated-label": "Auto generate label", 1305 "autogenerated-label": "Auto generate label",
@@ -563,7 +563,7 @@ mat-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 button.mat-icon-button { 567 button.mat-icon-button {
568 mat-icon { 568 mat-icon {
569 color: rgba(0, 0, 0, .54); 569 color: rgba(0, 0, 0, .54);