Showing
44 changed files
with
710 additions
and
336 deletions
common/data/src/main/java/org/thingsboard/server/common/data/query/EntityKeyValueType.java
0 → 100644
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;"> </span> | 38 | <span [fxShow]="!disabled" style="min-width: 40px;"> </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> |
@@ -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); |