Browse Source

UI: Device profile alarm rules

pull/3477/head
Igor Kulikov 6 years ago
parent
commit
d9321b4816
  1. 23
      common/data/src/main/java/org/thingsboard/server/common/data/query/EntityKeyValueType.java
  2. 1
      common/data/src/main/java/org/thingsboard/server/common/data/query/KeyFilter.java
  3. 8
      ui-ngx/src/app/core/http/entity.service.ts
  4. 5
      ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts
  5. 1
      ui-ngx/src/app/modules/home/components/entity/entity.component.ts
  6. 4
      ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate-dialog.component.html
  7. 6
      ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate-dialog.component.ts
  8. 5
      ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate.component.html
  9. 9
      ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate.component.ts
  10. 4
      ui-ngx/src/app/modules/home/components/filter/filter-predicate-list.component.html
  11. 7
      ui-ngx/src/app/modules/home/components/filter/filter-predicate-list.component.ts
  12. 3
      ui-ngx/src/app/modules/home/components/filter/filter-predicate.component.html
  13. 2
      ui-ngx/src/app/modules/home/components/filter/filter-predicate.component.ts
  14. 5
      ui-ngx/src/app/modules/home/components/filter/filter-user-info-dialog.component.html
  15. 23
      ui-ngx/src/app/modules/home/components/filter/filter-user-info-dialog.component.ts
  16. 3
      ui-ngx/src/app/modules/home/components/filter/filter-user-info.component.html
  17. 5
      ui-ngx/src/app/modules/home/components/filter/filter-user-info.component.ts
  18. 6
      ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.html
  19. 63
      ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.ts
  20. 4
      ui-ngx/src/app/modules/home/components/filter/key-filter-list.component.html
  21. 11
      ui-ngx/src/app/modules/home/components/filter/key-filter-list.component.ts
  22. 6
      ui-ngx/src/app/modules/home/components/home-components.module.ts
  23. 15
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.html
  24. 22
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.scss
  25. 72
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.ts
  26. 55
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-key-filters-dialog.component.html
  27. 73
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-key-filters-dialog.component.ts
  28. 71
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.html
  29. 41
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.scss
  30. 65
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.ts
  31. 44
      ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.html
  32. 17
      ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.scss
  33. 16
      ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.ts
  34. 65
      ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm-dialog.component.html
  35. 101
      ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html
  36. 49
      ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.scss
  37. 62
      ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.ts
  38. 5
      ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarms.component.html
  39. 1
      ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarms.component.scss
  40. 43
      ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarms.component.ts
  41. 2
      ui-ngx/src/app/modules/home/components/profile/device-profile-dialog.component.html
  42. 2
      ui-ngx/src/app/modules/home/pages/device-profile/device-profiles-table-config.resolver.ts
  43. 77
      ui-ngx/src/app/shared/models/query/query.models.ts
  44. 16
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  45. 2
      ui-ngx/src/styles.scss

23
common/data/src/main/java/org/thingsboard/server/common/data/query/EntityKeyValueType.java

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.query;
public enum EntityKeyValueType {
STRING,
NUMERIC,
BOOLEAN,
DATE_TIME
}

1
common/data/src/main/java/org/thingsboard/server/common/data/query/KeyFilter.java

@ -21,6 +21,7 @@ import lombok.Data;
public class KeyFilter {
private EntityKey key;
private EntityKeyValueType valueType;
private KeyFilterPredicate predicate;
}

8
ui-ngx/src/app/core/http/entity.service.ts

@ -62,6 +62,7 @@ import {
entityInfoFields,
EntityKey,
EntityKeyType,
EntityKeyValueType,
FilterPredicateType,
singleEntityDataPageLink,
StringOperation
@ -399,6 +400,7 @@ export class EntityService {
keyFilters: searchText && searchText.length ? [
{
key: nameField,
valueType: EntityKeyValueType.STRING,
predicate: {
type: FilterPredicateType.STRING,
operation: StringOperation.STARTS_WITH,
@ -593,10 +595,10 @@ export class EntityService {
return entityTypes;
}
private getEntityFieldKeys (entityType: EntityType, searchText: string): Array<string> {
private getEntityFieldKeys(entityType: EntityType, searchText: string): Array<string> {
const entityFieldKeys: string[] = [entityFields.createdTime.keyName];
const query = searchText.toLowerCase();
switch(entityType) {
switch (entityType) {
case EntityType.USER:
entityFieldKeys.push(entityFields.name.keyName);
entityFieldKeys.push(entityFields.email.keyName);
@ -863,7 +865,7 @@ export class EntityService {
const tasks: Observable<any>[] = [];
const result: Device | Asset = entity as (Device | Asset);
const additionalInfo = result.additionalInfo || {};
if(result.label !== entityData.label ||
if (result.label !== entityData.label ||
result.type !== entityData.type ||
additionalInfo.description !== entityData.description ||
(result.id.entityType === EntityType.DEVICE && (additionalInfo.gateway !== entityData.gateway)) ) {

5
ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts

@ -251,13 +251,14 @@ export class EntityDetailsPanelComponent extends PageComponent implements OnInit
}
onToggleEditMode(isEdit: boolean) {
this.isEdit = isEdit;
if (!this.isEdit) {
if (!isEdit) {
this.entityComponent.entity = this.entity;
if (this.entityTabsComponent) {
this.entityTabsComponent.entity = this.entity;
}
this.isEdit = isEdit;
} else {
this.isEdit = isEdit;
this.editingEntity = deepClone(this.entity);
this.entityComponent.entity = this.editingEntity;
if (this.entityTabsComponent) {

1
ui-ngx/src/app/modules/home/components/entity/entity.component.ts

@ -65,7 +65,6 @@ export abstract class EntityComponent<T extends BaseData<HasId>,
set entity(entity: T) {
this.entityValue = entity;
if (this.entityForm) {
this.entityForm.reset(undefined, {emitEvent: false});
this.entityForm.markAsPristine();
this.updateForm(entity);
}

4
ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate-dialog.component.html

@ -37,6 +37,7 @@
</mat-form-field>
<tb-filter-predicate-list
[valueType]="data.valueType"
[displayUserParameters]="data.displayUserParameters"
[operation]="complexFilterFormGroup.get('operation').value"
[key]="data.key"
formControlName="predicates">
@ -45,6 +46,7 @@
</div>
<div mat-dialog-actions fxLayoutAlign="end center">
<button mat-raised-button color="primary"
*ngIf="!data.readonly"
type="submit"
[disabled]="(isLoading$ | async) || complexFilterFormGroup.invalid || !complexFilterFormGroup.dirty">
{{ (isAdd ? 'action.add' : 'action.update') | translate }}
@ -54,7 +56,7 @@
[disabled]="(isLoading$ | async)"
(click)="cancel()"
cdkFocusInitial>
{{ 'action.cancel' | translate }}
{{ (data.readonly ? 'action.close' : 'action.cancel') | translate }}
</button>
</div>
</form>

6
ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate-dialog.component.ts

@ -32,9 +32,10 @@ import {
export interface ComplexFilterPredicateDialogData {
complexPredicate: ComplexFilterPredicateInfo;
key: string;
disabled: boolean;
readonly: boolean;
isAdd: boolean;
valueType: EntityKeyValueType;
displayUserParameters: boolean;
}
@Component({
@ -73,6 +74,9 @@ export class ComplexFilterPredicateDialogComponent extends
predicates: [this.data.complexPredicate.predicates, [Validators.required]]
}
);
if (this.data.readonly) {
this.complexFilterFormGroup.disable({emitEvent: false});
}
}
ngOnInit(): void {

5
ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate.component.html

@ -19,11 +19,10 @@
<mat-label translate>filter.complex-filter</mat-label>
<button mat-icon-button color="primary"
class="tb-mat-32"
[fxShow]="!disabled"
type="button"
(click)="openComplexFilterDialog()"
matTooltip="{{ 'filter.edit-complex-filter' | translate }}"
matTooltip="{{ (disabled ? 'filter.complex-filter' : 'filter.edit-complex-filter') | translate }}"
matTooltipPosition="above">
<mat-icon>edit</mat-icon>
<mat-icon>{{ disabled ? 'more_vert' : 'edit' }}</mat-icon>
</button>
</div>

9
ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate.component.ts

@ -48,6 +48,8 @@ export class ComplexFilterPredicateComponent implements ControlValueAccessor, On
@Input() key: string;
@Input() displayUserParameters = true;
private propagateChange = null;
private complexFilterPredicate: ComplexFilterPredicateInfo;
@ -79,11 +81,12 @@ export class ComplexFilterPredicateComponent implements ControlValueAccessor, On
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
complexPredicate: deepClone(this.complexFilterPredicate),
disabled: this.disabled,
complexPredicate: this.disabled ? this.complexFilterPredicate : deepClone(this.complexFilterPredicate),
readonly: this.disabled,
valueType: this.valueType,
isAdd: false,
key: this.key
key: this.key,
displayUserParameters: this.displayUserParameters
}
}).afterClosed().subscribe(
(result) => {

4
ui-ngx/src/app/modules/home/components/filter/filter-predicate-list.component.html

@ -33,7 +33,8 @@
</div>
<label fxFlex="60" translate class="tb-title no-padding">filter.value</label>
</div>
<label translate class="tb-title no-padding" style="width: 60px;">filter.user-parameters</label>
<label *ngIf="displayUserParameters"
translate class="tb-title no-padding" style="width: 60px;">filter.user-parameters</label>
<span [fxShow]="!disabled" style="min-width: 40px;">&nbsp;</span>
</div>
</div>
@ -50,6 +51,7 @@
<tb-filter-predicate
fxFlex
[valueType]="valueType"
[displayUserParameters]="displayUserParameters"
[key]="key"
[formControl]="predicateControl">
</tb-filter-predicate>

7
ui-ngx/src/app/modules/home/components/filter/filter-predicate-list.component.ts

@ -62,6 +62,8 @@ export class FilterPredicateListComponent implements ControlValueAccessor, OnIni
@Input() operation: ComplexOperation = ComplexOperation.AND;
@Input() displayUserParameters = true;
filterListFormGroup: FormGroup;
valueTypeEnum = EntityKeyValueType;
@ -150,10 +152,11 @@ export class FilterPredicateListComponent implements ControlValueAccessor, OnIni
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
complexPredicate: predicate.keyFilterPredicate as ComplexFilterPredicateInfo,
disabled: this.disabled,
readonly: this.disabled,
valueType: this.valueType,
key: this.key,
isAdd: true
isAdd: true,
displayUserParameters: this.displayUserParameters
}
}).afterClosed().pipe(
map((result) => {

3
ui-ngx/src/app/modules/home/components/filter/filter-predicate.component.html

@ -35,11 +35,12 @@
<tb-complex-filter-predicate
[key]="key"
[valueType]="valueType"
[displayUserParameters]="displayUserParameters"
formControlName="predicate">
</tb-complex-filter-predicate>
</ng-template>
</div>
<tb-filter-user-info *ngIf="type !== filterPredicateType.COMPLEX"
<tb-filter-user-info *ngIf="type !== filterPredicateType.COMPLEX && displayUserParameters"
style="width: 60px;"
fxLayout="row" fxLayoutAlign="center"
[valueType]="valueType"

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

@ -41,6 +41,8 @@ export class FilterPredicateComponent implements ControlValueAccessor, OnInit {
@Input() key: string;
@Input() displayUserParameters = true;
filterPredicateFormGroup: FormGroup;
type: FilterPredicateType;

5
ui-ngx/src/app/modules/home/components/filter/filter-user-info-dialog.component.html

@ -17,7 +17,7 @@
-->
<form [formGroup]="filterUserInfoFormGroup" (ngSubmit)="save()" style="width: 500px;">
<mat-toolbar color="primary">
<h2 translate>filter.edit-filter-user-params</h2>
<h2>{{(data.readonly ? 'filter.filter-user-params' : 'filter.edit-filter-user-params') | translate}}</h2>
<span fxFlex></span>
<button mat-icon-button
(click)="cancel()"
@ -47,6 +47,7 @@
</div>
<div mat-dialog-actions fxLayoutAlign="end center">
<button mat-raised-button color="primary"
*ngIf="!data.readonly"
type="submit"
[disabled]="(isLoading$ | async) || filterUserInfoFormGroup.invalid || !filterUserInfoFormGroup.dirty">
{{ 'action.update' | translate }}
@ -56,7 +57,7 @@
[disabled]="(isLoading$ | async)"
(click)="cancel()"
cdkFocusInitial>
{{ 'action.cancel' | translate }}
{{ (data.readonly ? 'action.close' : 'action.cancel') | translate }}
</button>
</div>
</form>

23
ui-ngx/src/app/modules/home/components/filter/filter-user-info-dialog.component.ts

@ -23,7 +23,7 @@ import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Valida
import { Router } from '@angular/router';
import { DialogComponent } from '@app/shared/components/dialog.component';
import {
BooleanOperation,
BooleanOperation, createDefaultFilterPredicateUserInfo,
EntityKeyValueType, generateUserFilterValueLabel,
KeyFilterPredicateUserInfo, NumericOperation,
StringOperation
@ -35,6 +35,7 @@ export interface FilterUserInfoDialogData {
valueType: EntityKeyValueType;
operation: StringOperation | BooleanOperation | NumericOperation;
keyFilterPredicateUserInfo: KeyFilterPredicateUserInfo;
readonly: boolean;
}
@Component({
@ -60,18 +61,24 @@ export class FilterUserInfoDialogComponent extends
private translate: TranslateService) {
super(store, router, dialogRef);
const userInfo: KeyFilterPredicateUserInfo = this.data.keyFilterPredicateUserInfo || createDefaultFilterPredicateUserInfo();
this.filterUserInfoFormGroup = this.fb.group(
{
editable: [this.data.keyFilterPredicateUserInfo.editable],
label: [this.data.keyFilterPredicateUserInfo.label],
autogeneratedLabel: [this.data.keyFilterPredicateUserInfo.autogeneratedLabel],
order: [this.data.keyFilterPredicateUserInfo.order]
editable: [userInfo.editable],
label: [userInfo.label],
autogeneratedLabel: [userInfo.autogeneratedLabel],
order: [userInfo.order]
}
);
this.onAutogeneratedLabelChange();
this.filterUserInfoFormGroup.get('autogeneratedLabel').valueChanges.subscribe(() => {
this.onAutogeneratedLabelChange();
});
if (!this.data.readonly) {
this.filterUserInfoFormGroup.get('autogeneratedLabel').valueChanges.subscribe(() => {
this.onAutogeneratedLabelChange();
});
} else {
this.filterUserInfoFormGroup.disable({emitEvent: false});
}
}
private onAutogeneratedLabelChange() {

3
ui-ngx/src/app/modules/home/components/filter/filter-user-info.component.html

@ -17,10 +17,9 @@
-->
<button mat-icon-button color="primary"
class="tb-mat-32"
[fxShow]="!disabled"
type="button"
(click)="openFilterUserInfoDialog()"
matTooltip="{{ 'filter.edit-filter-user-params' | translate }}"
matTooltip="{{ (disabled ? 'filter.filter-user-params' : 'filter.edit-filter-user-params') | translate }}"
matTooltipPosition="above">
<mat-icon>settings</mat-icon>
</button>

5
ui-ngx/src/app/modules/home/components/filter/filter-user-info.component.ts

@ -76,7 +76,7 @@ export class FilterUserInfoComponent implements ControlValueAccessor, OnInit {
this.keyFilterPredicateUserInfo = keyFilterPredicateUserInfo;
}
private openFilterUserInfoDialog() {
public openFilterUserInfoDialog() {
this.dialog.open<FilterUserInfoDialogComponent, FilterUserInfoDialogData,
KeyFilterPredicateUserInfo>(FilterUserInfoDialogComponent, {
disableClose: true,
@ -85,7 +85,8 @@ export class FilterUserInfoComponent implements ControlValueAccessor, OnInit {
keyFilterPredicateUserInfo: deepClone(this.keyFilterPredicateUserInfo),
valueType: this.valueType,
key: this.key,
operation: this.operation
operation: this.operation,
readonly: this.disabled
}
}).afterClosed().subscribe(
(result) => {

6
ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.html

@ -17,7 +17,7 @@
-->
<form [formGroup]="keyFilterFormGroup" (ngSubmit)="save()" style="width: 900px;">
<mat-toolbar color="primary">
<h2>{{(data.isAdd ? 'filter.add-key-filter' : 'filter.edit-key-filter') | translate}}</h2>
<h2>{{(data.isAdd ? 'filter.add-key-filter' : (data.readonly ? 'filter.key-filter' : 'filter.edit-key-filter')) | translate}}</h2>
<span fxFlex></span>
<button mat-icon-button
(click)="cancel()"
@ -70,6 +70,7 @@
</mat-form-field>
</section>
<tb-filter-predicate-list *ngIf="keyFilterFormGroup.get('valueType').value"
[displayUserParameters]="data.displayUserParameters"
[valueType]="keyFilterFormGroup.get('valueType').value"
[key]="keyFilterFormGroup.get('key.key').value"
formControlName="predicates">
@ -79,6 +80,7 @@
<div mat-dialog-actions fxLayoutAlign="end center">
<button mat-raised-button color="primary"
type="submit"
*ngIf="!data.readonly"
[disabled]="(isLoading$ | async) || keyFilterFormGroup.invalid || !keyFilterFormGroup.dirty">
{{ (data.isAdd ? 'action.add' : 'action.update') | translate }}
</button>
@ -87,7 +89,7 @@
[disabled]="(isLoading$ | async)"
(click)="cancel()"
cdkFocusInitial>
{{ 'action.cancel' | translate }}
{{ (data.readonly ? 'action.close' : 'action.cancel') | translate }}
</button>
</div>
</form>

63
ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.ts

@ -39,6 +39,9 @@ import { filter, map, startWith } from 'rxjs/operators';
export interface KeyFilterDialogData {
keyFilter: KeyFilterInfo;
isAdd: boolean;
displayUserParameters: boolean;
readonly: boolean;
telemetryKeysOnly: boolean;
}
@Component({
@ -53,7 +56,10 @@ export class KeyFilterDialogComponent extends
keyFilterFormGroup: FormGroup;
entityKeyTypes = [EntityKeyType.ENTITY_FIELD, EntityKeyType.ATTRIBUTE, EntityKeyType.TIME_SERIES];
entityKeyTypes =
this.data.telemetryKeysOnly ?
[EntityKeyType.ATTRIBUTE, EntityKeyType.TIME_SERIES] :
[EntityKeyType.ENTITY_FIELD, EntityKeyType.ATTRIBUTE, EntityKeyType.TIME_SERIES];
entityKeyTypeTranslations = entityKeyTypeTranslationMap;
@ -95,32 +101,37 @@ export class KeyFilterDialogComponent extends
predicates: [this.data.keyFilter.predicates, [Validators.required]]
}
);
this.keyFilterFormGroup.get('valueType').valueChanges.subscribe((valueType: EntityKeyValueType) => {
const prevValue: EntityKeyValueType = this.keyFilterFormGroup.value.valueType;
const predicates: KeyFilterPredicate[] = this.keyFilterFormGroup.get('predicates').value;
if (prevValue && prevValue !== valueType && predicates && predicates.length) {
this.dialogs.confirm(this.translate.instant('filter.key-value-type-change-title'),
this.translate.instant('filter.key-value-type-change-message')).subscribe(
(result) => {
if (result) {
this.keyFilterFormGroup.get('predicates').setValue([]);
} else {
this.keyFilterFormGroup.get('valueType').setValue(prevValue, {emitEvent: false});
if (!this.data.readonly) {
this.keyFilterFormGroup.get('valueType').valueChanges.subscribe((valueType: EntityKeyValueType) => {
const prevValue: EntityKeyValueType = this.keyFilterFormGroup.value.valueType;
const predicates: KeyFilterPredicate[] = this.keyFilterFormGroup.get('predicates').value;
if (prevValue && prevValue !== valueType && predicates && predicates.length) {
this.dialogs.confirm(this.translate.instant('filter.key-value-type-change-title'),
this.translate.instant('filter.key-value-type-change-message')).subscribe(
(result) => {
if (result) {
this.keyFilterFormGroup.get('predicates').setValue([]);
} else {
this.keyFilterFormGroup.get('valueType').setValue(prevValue, {emitEvent: false});
}
}
}
);
}
});
this.keyFilterFormGroup.get('key.key').valueChanges.pipe(
filter((keyName) => this.keyFilterFormGroup.get('key.type').value === this.entityField && this.entityFields.hasOwnProperty(keyName))
).subscribe((keyName: string) => {
const prevValueType: EntityKeyValueType = this.keyFilterFormGroup.value.valueType;
const newValueType = this.entityFields[keyName]?.time ? EntityKeyValueType.DATE_TIME : EntityKeyValueType.STRING;
if (prevValueType !== newValueType) {
this.keyFilterFormGroup.get('valueType').patchValue(newValueType, {emitEvent: false});
}
});
);
}
});
this.keyFilterFormGroup.get('key.key').valueChanges.pipe(
filter((keyName) => this.keyFilterFormGroup.get('key.type').value === this.entityField && this.entityFields.hasOwnProperty(keyName))
).subscribe((keyName: string) => {
const prevValueType: EntityKeyValueType = this.keyFilterFormGroup.value.valueType;
const newValueType = this.entityFields[keyName]?.time ? EntityKeyValueType.DATE_TIME : EntityKeyValueType.STRING;
if (prevValueType !== newValueType) {
this.keyFilterFormGroup.get('valueType').patchValue(newValueType, {emitEvent: false});
}
});
} else {
this.keyFilterFormGroup.disable({emitEvent: false});
}
this.entityFields = entityFields;
this.entityFieldsList = Object.values(entityFields).map(entityField => entityField.keyName).sort();

4
ui-ngx/src/app/modules/home/components/filter/key-filter-list.component.html

@ -46,9 +46,9 @@
<button mat-icon-button color="primary"
type="button"
(click)="editKeyFilter($index)"
matTooltip="{{ 'filter.edit-key-filter' | translate }}"
matTooltip="{{ (disabled ? 'filter.key-filter' : 'filter.edit-key-filter') | translate }}"
matTooltipPosition="above">
<mat-icon>edit</mat-icon>
<mat-icon>{{disabled ? 'more_vert' : 'edit'}}</mat-icon>
</button>
<button mat-icon-button color="primary"
[fxShow]="!disabled"

11
ui-ngx/src/app/modules/home/components/filter/key-filter-list.component.ts

@ -46,6 +46,10 @@ export class KeyFilterListComponent implements ControlValueAccessor, OnInit {
@Input() disabled: boolean;
@Input() displayUserParameters = true;
@Input() telemetryKeysOnly = false;
keyFilterListFormGroup: FormGroup;
entityKeyTypeTranslations = entityKeyTypeTranslationMap;
@ -147,8 +151,11 @@ export class KeyFilterListComponent implements ControlValueAccessor, OnInit {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
keyFilter: keyFilter ? deepClone(keyFilter): null,
isAdd
keyFilter: keyFilter ? (this.disabled ? keyFilter : deepClone(keyFilter)) : null,
isAdd,
readonly: this.disabled,
displayUserParameters: this.displayUserParameters,
telemetryKeysOnly: this.telemetryKeysOnly
}
}).afterClosed();
}

6
ui-ngx/src/app/modules/home/components/home-components.module.ts

@ -100,10 +100,10 @@ import { MqttDeviceProfileTransportConfigurationComponent } from './profile/devi
import { Lwm2mDeviceProfileTransportConfigurationComponent } from './profile/device/lwm2m-device-profile-transport-configuration.component';
import { DeviceProfileAlarmsComponent } from './profile/alarm/device-profile-alarms.component';
import { DeviceProfileAlarmComponent } from './profile/alarm/device-profile-alarm.component';
import { DeviceProfileAlarmDialogComponent } from './profile/alarm/device-profile-alarm-dialog.component';
import { CreateAlarmRulesComponent } from './profile/alarm/create-alarm-rules.component';
import { AlarmRuleComponent } from './profile/alarm/alarm-rule.component';
import { AlarmRuleConditionComponent } from './profile/alarm/alarm-rule-condition.component';
import { AlarmRuleKeyFiltersDialogComponent } from './profile/alarm/alarm-rule-key-filters-dialog.component';
@NgModule({
declarations:
@ -184,9 +184,9 @@ import { AlarmRuleConditionComponent } from './profile/alarm/alarm-rule-conditio
DeviceProfileTransportConfigurationComponent,
CreateAlarmRulesComponent,
AlarmRuleComponent,
AlarmRuleKeyFiltersDialogComponent,
AlarmRuleConditionComponent,
DeviceProfileAlarmComponent,
DeviceProfileAlarmDialogComponent,
DeviceProfileAlarmsComponent,
DeviceProfileDataComponent,
DeviceProfileComponent,
@ -260,9 +260,9 @@ import { AlarmRuleConditionComponent } from './profile/alarm/alarm-rule-conditio
DeviceProfileTransportConfigurationComponent,
CreateAlarmRulesComponent,
AlarmRuleComponent,
AlarmRuleKeyFiltersDialogComponent,
AlarmRuleConditionComponent,
DeviceProfileAlarmComponent,
DeviceProfileAlarmDialogComponent,
DeviceProfileAlarmsComponent,
DeviceProfileDataComponent,
DeviceProfileComponent,

15
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.html

@ -15,5 +15,16 @@
limitations under the License.
-->
<div fxLayout="row" [formGroup]="alarmRuleConditionFormGroup">
</div>
<mat-form-field class="mat-block" (click)="openFilterDialog($event)" floatLabel="always" hideRequiredMarker>
<mat-label></mat-label>
<input readonly
required matInput [formControl]="alarmRuleConditionControl"
placeholder="{{'device-profile.enter-alarm-rule-condition-prompt' | translate}}">
<a matSuffix mat-icon-button color="primary"
type="button"
(click)="openFilterDialog($event)"
matTooltip="{{ (disabled ? 'action.view' : 'action.edit') | translate }}"
matTooltipPosition="above">
<mat-icon>{{ disabled ? 'more_vert' : 'edit' }}</mat-icon>
</a>
</mat-form-field>

22
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.scss

@ -0,0 +1,22 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
:host {
a.mat-icon-button {
&:hover, &:focus {
border-bottom: none;
}
}
}

72
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.ts

@ -19,19 +19,22 @@ import {
ControlValueAccessor,
FormBuilder,
FormControl,
FormGroup,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
Validator,
Validators
Validator
} from '@angular/forms';
import { AlarmCondition } from '@shared/models/device.models';
import { MatDialog } from '@angular/material/dialog';
import { KeyFilter } from '@shared/models/query/query.models';
import { deepClone } from '@core/utils';
import {
AlarmRuleKeyFiltersDialogComponent,
AlarmRuleKeyFiltersDialogData
} from './alarm-rule-key-filters-dialog.component';
@Component({
selector: 'tb-alarm-rule-condition',
templateUrl: './alarm-rule-condition.component.html',
styleUrls: [],
styleUrls: ['./alarm-rule-condition.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
@ -50,9 +53,9 @@ export class AlarmRuleConditionComponent implements ControlValueAccessor, OnInit
@Input()
disabled: boolean;
private modelValue: AlarmCondition;
alarmRuleConditionControl: FormControl;
alarmRuleConditionFormGroup: FormGroup;
private modelValue: Array<KeyFilter>;
private propagateChange = (v: any) => { };
@ -68,45 +71,56 @@ export class AlarmRuleConditionComponent implements ControlValueAccessor, OnInit
}
ngOnInit() {
this.alarmRuleConditionFormGroup = this.fb.group({
condition: [null, Validators.required],
durationUnit: [null],
durationValue: [null]
});
this.alarmRuleConditionFormGroup.valueChanges.subscribe(() => {
this.updateModel();
});
this.alarmRuleConditionControl = this.fb.control(null);
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (this.disabled) {
this.alarmRuleConditionFormGroup.disable({emitEvent: false});
} else {
this.alarmRuleConditionFormGroup.enable({emitEvent: false});
}
}
writeValue(value: AlarmCondition): void {
writeValue(value: Array<KeyFilter>): void {
this.modelValue = value;
this.alarmRuleConditionFormGroup.reset(this.modelValue, {emitEvent: false});
this.updateConditionInfo();
}
public validate(c: FormControl) {
return (this.alarmRuleConditionFormGroup.valid) ? null : {
return (this.modelValue && this.modelValue.length) ? null : {
alarmRuleCondition: {
valid: false,
},
};
}
private updateModel() {
if (this.alarmRuleConditionFormGroup.valid) {
const value = this.alarmRuleConditionFormGroup.value;
this.modelValue = {...this.modelValue, ...value};
this.propagateChange(this.modelValue);
public openFilterDialog($event: Event) {
if ($event) {
$event.stopPropagation();
}
this.dialog.open<AlarmRuleKeyFiltersDialogComponent, AlarmRuleKeyFiltersDialogData,
Array<KeyFilter>>(AlarmRuleKeyFiltersDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
readonly: this.disabled,
keyFilters: this.disabled ? this.modelValue : deepClone(this.modelValue)
}
}).afterClosed().subscribe((result) => {
if (result) {
this.modelValue = result;
this.updateModel();
}
});
}
private updateConditionInfo() {
if (this.modelValue && this.modelValue.length) {
this.alarmRuleConditionControl.patchValue('Condition set');
} else {
this.propagateChange(null);
this.alarmRuleConditionControl.patchValue(null);
}
}
private updateModel() {
this.updateConditionInfo();
this.propagateChange(this.modelValue);
}
}

55
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-key-filters-dialog.component.html

@ -0,0 +1,55 @@
<!--
Copyright © 2016-2020 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<form [formGroup]="keyFiltersFormGroup" (ngSubmit)="save()" style="width: 700px;">
<mat-toolbar color="primary">
<h2>{{ (readonly ? 'device-profile.alarm-rule-condition' : 'device-profile.edit-alarm-rule-condition') | translate }}</h2>
<span fxFlex></span>
<button mat-icon-button
(click)="cancel()"
type="button">
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>
<div mat-dialog-content>
<fieldset [disabled]="isLoading$ | async">
<div fxFlex fxLayout="column">
<tb-key-filter-list
[displayUserParameters]="false"
[telemetryKeysOnly]="true"
formControlName="keyFilters">
</tb-key-filter-list>
</div>
</fieldset>
</div>
<div mat-dialog-actions fxLayoutAlign="end center">
<button mat-raised-button color="primary"
*ngIf="!readonly"
type="submit"
[disabled]="(isLoading$ | async) || keyFiltersFormGroup.invalid || !keyFiltersFormGroup.dirty">
{{ 'action.save' | translate }}
</button>
<button mat-button color="primary"
type="button"
[disabled]="(isLoading$ | async)"
(click)="cancel()" cdkFocusInitial>
{{ (readonly ? 'action.close' : 'action.cancel') | translate }}
</button>
</div>
</form>

73
ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm-dialog.component.ts → ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-key-filters-dialog.component.ts

@ -14,70 +14,60 @@
/// limitations under the License.
///
import {
Component,
Inject,
OnInit,
SkipSelf
} from '@angular/core';
import { Component, Inject, OnInit, SkipSelf } from '@angular/core';
import { ErrorStateMatcher } from '@angular/material/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms';
import { DialogComponent } from '@shared/components/dialog.component';
import { Router } from '@angular/router';
import { DeviceProfileAlarm } from '@shared/models/device.models';
import { DialogComponent } from '@app/shared/components/dialog.component';
import { UtilsService } from '@core/services/utils.service';
import { TranslateService } from '@ngx-translate/core';
import { KeyFilter, keyFilterInfosToKeyFilters, keyFiltersToKeyFilterInfos } from '@shared/models/query/query.models';
export interface DeviceProfileAlarmDialogData {
alarm: DeviceProfileAlarm;
isAdd: boolean;
isReadOnly: boolean;
export interface AlarmRuleKeyFiltersDialogData {
readonly: boolean;
keyFilters: Array<KeyFilter>;
}
@Component({
selector: 'tb-device-profile-alarm-dialog',
templateUrl: './device-profile-alarm-dialog.component.html',
providers: [{provide: ErrorStateMatcher, useExisting: DeviceProfileAlarmDialogComponent}],
selector: 'tb-alarm-rule-key-filters-dialog',
templateUrl: './alarm-rule-key-filters-dialog.component.html',
providers: [{provide: ErrorStateMatcher, useExisting: AlarmRuleKeyFiltersDialogComponent}],
styleUrls: []
})
export class DeviceProfileAlarmDialogComponent extends
DialogComponent<DeviceProfileAlarmDialogComponent, DeviceProfileAlarm> implements OnInit, ErrorStateMatcher {
export class AlarmRuleKeyFiltersDialogComponent extends DialogComponent<AlarmRuleKeyFiltersDialogComponent, Array<KeyFilter>>
implements OnInit, ErrorStateMatcher {
alarmFormGroup: FormGroup;
readonly = this.data.readonly;
keyFilters = this.data.keyFilters;
isReadOnly = this.data.isReadOnly;
alarm = this.data.alarm;
isAdd = this.data.isAdd;
keyFiltersFormGroup: FormGroup;
submitted = false;
constructor(protected store: Store<AppState>,
protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: DeviceProfileAlarmDialogData,
public dialogRef: MatDialogRef<DeviceProfileAlarmDialogComponent, DeviceProfileAlarm>,
@Inject(MAT_DIALOG_DATA) public data: AlarmRuleKeyFiltersDialogData,
@SkipSelf() private errorStateMatcher: ErrorStateMatcher,
public fb: FormBuilder) {
public dialogRef: MatDialogRef<AlarmRuleKeyFiltersDialogComponent, Array<KeyFilter>>,
private fb: FormBuilder,
private utils: UtilsService,
public translate: TranslateService) {
super(store, router, dialogRef);
this.isAdd = this.data.isAdd;
this.alarm = this.data.alarm;
}
ngOnInit(): void {
this.alarmFormGroup = this.fb.group({
id: [null, Validators.required],
alarmType: [null, Validators.required],
createRules: [null],
clearRule: [null],
propagate: [null],
propagateRelationTypes: [null]
this.keyFiltersFormGroup = this.fb.group({
keyFilters: [keyFiltersToKeyFilterInfos(this.keyFilters), Validators.required]
});
this.alarmFormGroup.reset(this.alarm, {emitEvent: false});
if (this.isReadOnly) {
this.alarmFormGroup.disable({emitEvent: false});
if (this.readonly) {
this.keyFiltersFormGroup.disable({emitEvent: false});
}
}
ngOnInit(): void {
}
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
const originalErrorState = this.errorStateMatcher.isErrorState(control, form);
const customErrorState = !!(control && control.invalid && this.submitted);
@ -90,10 +80,7 @@ export class DeviceProfileAlarmDialogComponent extends
save(): void {
this.submitted = true;
if (this.alarmFormGroup.valid) {
this.alarm = {...this.alarm, ...this.alarmFormGroup.value};
this.dialogRef.close(this.alarm);
}
this.keyFilters = keyFilterInfosToKeyFilters(this.keyFiltersFormGroup.get('keyFilters').value);
this.dialogRef.close(this.keyFilters);
}
}

71
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.html

@ -15,8 +15,71 @@
limitations under the License.
-->
<div fxLayout="row" [formGroup]="alarmRuleFormGroup">
<tb-alarm-rule-condition
formControlName="condition">
</tb-alarm-rule-condition>
<div fxLayout="column" [formGroup]="alarmRuleFormGroup">
<div formGroupName="condition" fxLayout="row" fxLayoutAlign="start" fxLayoutGap="8px" fxFlex>
<div fxLayout="column" fxFlex>
<div fxLayout="row" fxLayoutAlign="start center" style="min-height: 40px;">
<label class="tb-small" translate>device-profile.alarm-rule-condition</label>
</div>
<tb-alarm-rule-condition fxFlex
formControlName="condition">
</tb-alarm-rule-condition>
</div>
<div fxLayout="column" style="min-width: 250px;">
<div fxLayout="row" fxLayoutAlign="start center" style="min-height: 40px;">
<label fxFlex class="tb-small" translate>device-profile.condition-duration</label>
<mat-slide-toggle [disabled]="disabled"
[ngModelOptions]="{standalone: true}"
(ngModelChange)="enableDurationChanged($event)"
[ngModel]="enableDuration">
</mat-slide-toggle>
</div>
<div fxLayout="row" fxLayoutGap="8px" [fxShow]="enableDuration">
<mat-form-field class="mat-block duration-value-field" hideRequiredMarker floatLabel="always">
<mat-label></mat-label>
<input type="number"
[required]="enableDuration"
step="1"
min="1" max="2147483647" matInput
placeholder="{{ 'device-profile.condition-duration-value' | translate }}"
formControlName="durationValue">
<mat-error *ngIf="alarmRuleFormGroup.get('condition').get('durationValue').hasError('required')">
{{ 'device-profile.condition-duration-value-required' | translate }}
</mat-error>
<mat-error *ngIf="alarmRuleFormGroup.get('condition').get('durationValue').hasError('min')">
{{ 'device-profile.condition-duration-value-range' | translate }}
</mat-error>
<mat-error *ngIf="alarmRuleFormGroup.get('condition').get('durationValue').hasError('max')">
{{ 'device-profile.condition-duration-value-range' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block duration-unit-field" hideRequiredMarker floatLabel="always">
<mat-label></mat-label>
<mat-select formControlName="durationUnit"
[required]="enableDuration"
placeholder="{{ 'device-profile.condition-duration-time-unit' | translate }}">
<mat-option *ngFor="let timeUnit of timeUnits" [value]="timeUnit">
{{ timeUnitTranslations.get(timeUnit) | translate }}
</mat-option>
</mat-select>
<mat-error *ngIf="alarmRuleFormGroup.get('condition').get('durationUnit').hasError('required')">
{{ 'device-profile.condition-duration-time-unit-required' | translate }}
</mat-error>
</mat-form-field>
</div>
</div>
</div>
<mat-expansion-panel class="advanced-settings" [expanded]="false">
<mat-expansion-panel-header>
<mat-panel-title>
<div fxFlex fxLayout="row" fxLayoutAlign="end center">
<div class="tb-small" translate>device-profile.advanced-settings</div>
</div>
</mat-panel-title>
</mat-expansion-panel-header>
<mat-form-field class="mat-block">
<mat-label translate>device-profile.alarm-details</mat-label>
<textarea matInput formControlName="alarmDetails" rows="5"></textarea>
</mat-form-field>
</mat-expansion-panel>
</div>

41
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.scss

@ -0,0 +1,41 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
:host {
.mat-expansion-panel.advanced-settings {
box-shadow: none;
border: none;
padding: 0;
}
}
:host ::ng-deep {
.mat-expansion-panel.advanced-settings {
.mat-expansion-panel-body {
padding: 0;
}
}
.mat-form-field.duration-value-field {
.mat-form-field-infix {
width: 120px;
}
}
.mat-form-field.duration-unit-field {
.mat-form-field-infix {
width: 120px;
}
}
}

65
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.ts

@ -14,7 +14,7 @@
/// limitations under the License.
///
import { Component, forwardRef, Input, OnInit } from '@angular/core';
import { ChangeDetectorRef, Component, forwardRef, Input, NgZone, OnInit } from '@angular/core';
import {
ControlValueAccessor,
FormBuilder,
@ -27,11 +27,13 @@ import {
} from '@angular/forms';
import { AlarmRule } from '@shared/models/device.models';
import { MatDialog } from '@angular/material/dialog';
import { TimeUnit, timeUnitTranslationMap } from '../../../../../shared/models/time/time.models';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
@Component({
selector: 'tb-alarm-rule',
templateUrl: './alarm-rule.component.html',
styleUrls: [],
styleUrls: ['./alarm-rule.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
@ -47,9 +49,23 @@ import { MatDialog } from '@angular/material/dialog';
})
export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validator {
timeUnits = Object.keys(TimeUnit);
timeUnitTranslations = timeUnitTranslationMap;
@Input()
disabled: boolean;
private requiredValue: boolean;
get required(): boolean {
return this.requiredValue;
}
@Input()
set required(value: boolean) {
this.requiredValue = coerceBooleanProperty(value);
}
enableDuration = false;
private modelValue: AlarmRule;
alarmRuleFormGroup: FormGroup;
@ -69,7 +85,11 @@ export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validat
ngOnInit() {
this.alarmRuleFormGroup = this.fb.group({
condition: [null, Validators.required],
condition: this.fb.group({
condition: [null, Validators.required],
durationUnit: [null],
durationValue: [null]
}, Validators.required),
alarmDetails: [null]
});
this.alarmRuleFormGroup.valueChanges.subscribe(() => {
@ -88,24 +108,51 @@ export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validat
writeValue(value: AlarmRule): void {
this.modelValue = value;
this.alarmRuleFormGroup.reset(this.modelValue, {emitEvent: false});
this.enableDuration = value && !!value.condition.durationValue;
this.alarmRuleFormGroup.reset(this.modelValue || undefined, {emitEvent: false});
this.updateValidators();
}
public validate(c: FormControl) {
return (this.alarmRuleFormGroup.valid) ? null : {
return (!this.required && !this.modelValue || this.alarmRuleFormGroup.valid) ? null : {
alarmRule: {
valid: false,
},
};
}
public enableDurationChanged(enableDuration) {
this.enableDuration = enableDuration;
this.updateValidators(true, true);
}
private updateValidators(resetDuration = false, emitEvent = false) {
if (this.enableDuration) {
this.alarmRuleFormGroup.get('condition').get('durationValue')
.setValidators([Validators.required, Validators.min(1), Validators.max(2147483647)]);
this.alarmRuleFormGroup.get('condition').get('durationUnit')
.setValidators([Validators.required]);
} else {
this.alarmRuleFormGroup.get('condition').get('durationValue')
.setValidators([]);
this.alarmRuleFormGroup.get('condition').get('durationUnit')
.setValidators([]);
if (resetDuration) {
this.alarmRuleFormGroup.get('condition').patchValue({
durationValue: null,
durationUnit: null
});
}
}
this.alarmRuleFormGroup.get('condition').get('durationValue').updateValueAndValidity({emitEvent});
this.alarmRuleFormGroup.get('condition').get('durationUnit').updateValueAndValidity({emitEvent});
}
private updateModel() {
if (this.alarmRuleFormGroup.valid) {
const value = this.alarmRuleFormGroup.value;
const value = this.alarmRuleFormGroup.value;
if (this.modelValue) {
this.modelValue = {...this.modelValue, ...value};
this.propagateChange(this.modelValue);
} else {
this.propagateChange(null);
}
}
}

44
ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.html

@ -17,38 +17,42 @@
-->
<div fxFlex fxLayout="column">
<div *ngFor="let createAlarmRuleControl of createAlarmRulesFormArray().controls; let $index = index;
last as isLast;" fxLayout="row" style="padding-left: 20px;" [formGroup]="createAlarmRuleControl">
<mat-form-field class="mat-block" floatLabel="always" hideRequiredMarker>
<mat-label translate></mat-label>
<mat-select formControlName="severity"
required
placeholder="{{ 'device-profile.select-alarm-severity' | translate }}">
<mat-option *ngFor="let alarmSeverity of alarmSeverities" [value]="alarmSeverity">
{{ alarmSeverityTranslationMap.get(alarmSeverityEnum[alarmSeverity]) | translate }}
</mat-option>
</mat-select>
<mat-error *ngIf="createAlarmRuleControl.get('severity').hasError('required')">
{{ 'device-profile.alarm-severity-required' | translate }}
</mat-error>
</mat-form-field>
<tb-alarm-rule formControlName="alarmRule">
</tb-alarm-rule>
last as isLast;" fxLayout="row" fxLayoutAlign="start center"
fxLayoutGap="8px" style="padding-bottom: 8px;" [formGroup]="createAlarmRuleControl">
<div class="create-alarm-rule" fxFlex fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start">
<mat-form-field class="severity mat-block" floatLabel="always" hideRequiredMarker>
<mat-label translate>alarm.severity</mat-label>
<mat-select formControlName="severity"
required
placeholder="{{ 'device-profile.select-alarm-severity' | translate }}">
<mat-option *ngFor="let alarmSeverity of alarmSeverities" [value]="alarmSeverity">
{{ alarmSeverityTranslationMap.get(alarmSeverityEnum[alarmSeverity]) | translate }}
</mat-option>
</mat-select>
<mat-error *ngIf="createAlarmRuleControl.get('severity').hasError('required')">
{{ 'device-profile.alarm-severity-required' | translate }}
</mat-error>
</mat-form-field>
<tb-alarm-rule formControlName="alarmRule" required fxFlex>
</tb-alarm-rule>
</div>
<button *ngIf="!disabled && createAlarmRulesFormArray().controls.length > 1"
mat-icon-button color="primary" style="min-width: 40px;"
type="button"
(click)="removeCreateAlarmRule($index)"
matTooltip="{{ 'action.remove' | translate }}"
matTooltipPosition="above">
<mat-icon>close</mat-icon>
<mat-icon>remove_circle_outline</mat-icon>
</button>
</div>
<div *ngIf="!disabled" fxFlex fxLayout="row" fxLayoutAlign="start center">
<button mat-icon-button color="primary"
<div fxLayout="row" *ngIf="!disabled">
<button mat-stroked-button color="primary"
type="button"
(click)="addCreateAlarmRule()"
matTooltip="{{ 'device-profile.add-create-alarm-rule' | translate }}"
matTooltipPosition="above">
<mat-icon>add</mat-icon>
<mat-icon>add_circle_outline</mat-icon>
{{ 'device-profile.add-create-alarm-rule' | translate }}
</button>
</div>
</div>

17
ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.scss

@ -13,6 +13,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
:host {
.create-alarm-rule {
border: 1px groove rgba(0, 0, 0, .25);
border-radius: 4px;
padding: 8px;
.mat-form-field.severity {
border-right: 1px groove rgba(0, 0, 0, 0.25);
padding-right: 8px;
}
}
}
:host ::ng-deep {
.mat-form-field.severity {
.mat-form-field-infix {
width: 160px;
}
}
}

16
ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.ts

@ -150,15 +150,11 @@ export class CreateAlarmRulesComponent implements ControlValueAccessor, OnInit,
}
private updateModel() {
if (this.createAlarmRulesFormGroup.valid) {
const value: {severity: string, alarmRule: AlarmRule}[] = this.createAlarmRulesFormGroup.get('createAlarmRules').value;
const createAlarmRules: {[severity: string]: AlarmRule} = {};
value.forEach(v => {
createAlarmRules[v.severity] = v.alarmRule;
});
this.propagateChange(createAlarmRules);
} else {
this.propagateChange(null);
}
const value: {severity: string, alarmRule: AlarmRule}[] = this.createAlarmRulesFormGroup.get('createAlarmRules').value;
const createAlarmRules: {[severity: string]: AlarmRule} = {};
value.forEach(v => {
createAlarmRules[v.severity] = v.alarmRule;
});
this.propagateChange(createAlarmRules);
}
}

65
ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm-dialog.component.html

@ -1,65 +0,0 @@
<!--
Copyright © 2016-2020 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<form [formGroup]="alarmFormGroup" (ngSubmit)="save()" style="min-width: 600px;">
<mat-toolbar fxLayout="row" color="primary">
<h2>{{ (isReadOnly ? 'device-profile.alarm-rule-details' : (isAdd ? 'device-profile.add-alarm-rule' : 'device-profile.edit-alarm-rule')) | translate }}</h2>
<span fxFlex></span>
<button mat-button mat-icon-button
(click)="cancel()"
type="button">
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<div mat-dialog-content>
<fieldset [disabled]="(isLoading$ | async) || isReadOnly" fxLayout="column">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>device-profile.alarm-type</mat-label>
<input required matInput formControlName="alarmType">
<mat-error *ngIf="alarmFormGroup.get('alarmType').hasError('required')">
{{ 'device-profile.alarm-type-required' | translate }}
</mat-error>
<mat-hint innerHTML="{{ 'device-profile.alarm-type-pattern-hint' | translate }}"></mat-hint>
</mat-form-field>
<fieldset>
<legend translate>device-profile.create-alarm-rules</legend>
</fieldset>
<fieldset>
<legend translate>device-profile.clear-alarm-rule</legend>
</fieldset>
</fieldset>
</div>
<div mat-dialog-actions fxLayout="row">
<span fxFlex></span>
<button *ngIf="!isReadOnly" mat-button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || alarmFormGroup.invalid
|| !alarmFormGroup.dirty">
{{ (isAdd ? 'action.add' : 'action.save') | translate }}
</button>
<button mat-button color="primary"
style="margin-right: 20px;"
type="button"
[disabled]="(isLoading$ | async)"
(click)="cancel()" cdkFocusInitial>
{{ (isReadOnly ? 'action.close' : 'action.cancel') | translate }}
</button>
</div>
</form>

101
ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html

@ -15,33 +15,80 @@
limitations under the License.
-->
<fieldset [formGroup]="alarmFormGroup" class="fields-group tb-device-profile-alarm" fxFlex
style="position: relative;">
<button *ngIf="!disabled" mat-icon-button color="primary" style="min-width: 40px;"
type="button"
style="position: absolute; top: 0; right: 0;"
(click)="removeAlarm.emit()"
matTooltip="{{ 'action.remove' | translate }}"
matTooltipPosition="above">
<mat-icon>close</mat-icon>
</button>
<legend>
<mat-form-field floatLabel="always"
matTooltip="{{ 'device-profile.alarm-type-pattern-hint' | translate }}">
<mat-label>{{'device-profile.alarm-type' | translate}}</mat-label>
<input required matInput formControlName="alarmType" placeholder="Enter alarm type">
<mat-error *ngIf="alarmFormGroup.get('alarmType').hasError('required')">
{{ 'device-profile.alarm-type-required' | translate }}
</mat-error>
<!--mat-hint innerHTML="{{ 'device-profile.alarm-type-pattern-hint' | translate }}"></mat-hint-->
</mat-form-field>
</legend>
<mat-expansion-panel class="device-profile-alarm" fxFlex [formGroup]="alarmFormGroup" [(expanded)]="expanded">
<mat-expansion-panel-header>
<div fxFlex fxLayout="row" fxLayoutAlign="start center">
<mat-panel-title [fxShow]="!expanded">
<div fxLayout="row" fxFlex fxLayoutAlign="start center">
{{ alarmFormGroup.get('alarmType').value }}
</div>
</mat-panel-title>
<mat-form-field floatLabel="always"
style="width: 600px;"
[fxShow]="expanded"
(click)="!disabled ? $event.stopPropagation() : null;">
<mat-label>{{'device-profile.alarm-type' | translate}}</mat-label>
<input required matInput formControlName="alarmType" placeholder="Enter alarm type">
<mat-error *ngIf="alarmFormGroup.get('alarmType').hasError('required')">
{{ 'device-profile.alarm-type-required' | translate }}
</mat-error>
<mat-hint *ngIf="!disabled"
innerHTML="{{ 'device-profile.alarm-type-pattern-hint' | translate }}"></mat-hint>
</mat-form-field>
<span fxFlex></span>
<button *ngIf="!disabled" mat-icon-button style="min-width: 40px;"
type="button"
(click)="removeAlarm.emit()"
matTooltip="{{ 'action.remove' | translate }}"
matTooltipPosition="above">
<mat-icon>delete</mat-icon>
</button>
</div>
</mat-expansion-panel-header>
<div fxFlex fxLayout="column">
<div translate class="tb-small" style="font-weight: 500;">device-profile.create-alarm-rules</div>
<mat-divider></mat-divider>
<tb-create-alarm-rules formControlName="createRules">
<div translate class="tb-small" style="padding-bottom: 8px;">device-profile.create-alarm-rules</div>
<tb-create-alarm-rules formControlName="createRules" style="padding-bottom: 16px;">
</tb-create-alarm-rules>
<div translate class="tb-small" style="font-weight: 500;">device-profile.clear-alarm-rule</div>
<mat-divider></mat-divider>
<div translate class="tb-small" style="padding-bottom: 8px;">device-profile.clear-alarm-rule</div>
<div fxFlex fxLayout="row"
[fxShow]="alarmFormGroup.get('clearRule').value"
fxLayoutGap="8px;" fxLayoutAlign="start center" style="padding-bottom: 8px;">
<div class="clear-alarm-rule" fxFlex fxLayout="row">
<tb-alarm-rule formControlName="clearRule" fxFlex>
</tb-alarm-rule>
</div>
<button *ngIf="!disabled"
mat-icon-button color="primary" style="min-width: 40px;"
type="button"
(click)="removeClearAlarmRule()"
matTooltip="{{ 'action.remove' | translate }}"
matTooltipPosition="above">
<mat-icon>remove_circle_outline</mat-icon>
</button>
</div>
<div fxLayout="row" *ngIf="!disabled"
[fxShow]="!alarmFormGroup.get('clearRule').value">
<button mat-stroked-button color="primary"
type="button"
(click)="addClearAlarmRule()"
matTooltip="{{ 'device-profile.add-clear-alarm-rule' | translate }}"
matTooltipPosition="above">
<mat-icon>add_circle_outline</mat-icon>
{{ 'device-profile.add-clear-alarm-rule' | translate }}
</button>
</div>
</div>
</fieldset>
<mat-expansion-panel class="advanced-settings" [expanded]="false">
<mat-expansion-panel-header>
<mat-panel-title>
<div fxFlex fxLayout="row" fxLayoutAlign="end center">
<div class="tb-small" translate>device-profile.advanced-settings</div>
</div>
</mat-panel-title>
</mat-expansion-panel-header>
<mat-checkbox formControlName="propagate" style="padding-bottom: 16px;">
{{ 'device-profile.propagate-alarm' | translate }}
</mat-checkbox>
<div>TODO: Propagate relation types</div>
</mat-expansion-panel>
</mat-expansion-panel>

49
ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.scss

@ -13,35 +13,42 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import '../scss/constants';
:host {
display: block;
.tb-device-profile-alarm {
&.mat-padding {
padding: 8px;
@media #{$mat-gt-sm} {
padding: 16px;
.clear-alarm-rule {
border: 1px groove rgba(0, 0, 0, .25);
border-radius: 4px;
padding: 8px;
}
.mat-expansion-panel {
box-shadow: none;
&.device-profile-alarm {
border: 1px groove rgba(0, 0, 0, .25);
.mat-expansion-panel-header {
padding: 0 24px 0 8px;
&.mat-expanded {
height: 80px;
}
}
}
}
a.mat-icon-button {
&:hover, &:focus {
border-bottom: none;
&.advanced-settings {
border: none;
padding: 0;
}
}
.fields-group {
padding: 8px;
margin: 10px 0;
border: 1px groove rgba(0, 0, 0, .25);
border-radius: 4px;
}
legend {
padding-left: 8px;
padding-right: 8px;
margin-bottom: -30px;
.mat-form-field {
margin-bottom: 21px;
:host ::ng-deep {
.mat-expansion-panel {
&.device-profile-alarm {
.mat-expansion-panel-body {
padding: 0 8px;
}
}
&.advanced-settings {
.mat-expansion-panel-body {
padding: 0;
}
}
}

62
ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.ts

@ -19,17 +19,14 @@ import {
ControlValueAccessor,
FormBuilder,
FormControl,
FormGroup, NG_VALIDATORS,
NG_VALUE_ACCESSOR, Validator,
FormGroup,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
Validator,
Validators
} from '@angular/forms';
import { DeviceProfileAlarm } from '@shared/models/device.models';
import { deepClone } from '@core/utils';
import { AlarmRule, DeviceProfileAlarm } from '@shared/models/device.models';
import { MatDialog } from '@angular/material/dialog';
import {
DeviceProfileAlarmDialogComponent,
DeviceProfileAlarmDialogData
} from './device-profile-alarm-dialog.component';
@Component({
selector: 'tb-device-profile-alarm',
@ -56,6 +53,8 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit
@Output()
removeAlarm = new EventEmitter();
expanded = false;
private modelValue: DeviceProfileAlarm;
alarmFormGroup: FormGroup;
@ -98,31 +97,24 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit
writeValue(value: DeviceProfileAlarm): void {
this.modelValue = value;
this.alarmFormGroup.reset(this.modelValue, {emitEvent: false});
if (!this.modelValue.alarmType) {
this.expanded = true;
}
this.alarmFormGroup.reset(this.modelValue || undefined, {emitEvent: false});
}
/* openAlarm($event: Event) {
if ($event) {
$event.stopPropagation();
}
this.dialog.open<DeviceProfileAlarmDialogComponent, DeviceProfileAlarmDialogData,
DeviceProfileAlarm>(DeviceProfileAlarmDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
isAdd: false,
alarm: this.disabled ? this.modelValue : deepClone(this.modelValue),
isReadOnly: this.disabled
}
}).afterClosed().subscribe(
(deviceProfileAlarm) => {
if (deviceProfileAlarm) {
this.modelValue = deviceProfileAlarm;
this.updateModel();
}
public addClearAlarmRule() {
const clearAlarmRule: AlarmRule = {
condition: {
condition: []
}
);
} */
};
this.alarmFormGroup.patchValue({clearRule: clearAlarmRule});
}
public removeClearAlarmRule() {
this.alarmFormGroup.patchValue({clearRule: null});
}
public validate(c: FormControl) {
return (this.alarmFormGroup.valid) ? null : {
@ -133,12 +125,8 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit
}
private updateModel() {
if (this.alarmFormGroup.valid) {
const value = this.alarmFormGroup.value;
this.modelValue = {...this.modelValue, ...value};
this.propagateChange(this.modelValue);
} else {
this.propagateChange(null);
}
const value = this.alarmFormGroup.value;
this.modelValue = {...this.modelValue, ...value};
this.propagateChange(this.modelValue);
}
}

5
ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarms.component.html

@ -17,8 +17,9 @@
-->
<div fxLayout="column">
<div class="tb-device-profile-alarms">
<div *ngFor="let alarmControl of alarmsFormArray().controls; let $index = index; last as isLast;"
fxLayout="column">
<div *ngFor="let alarmControl of alarmsFormArray().controls; trackBy: trackByAlarm;
let $index = index; last as isLast;"
fxLayout="column" [ngStyle]="!isLast ? {paddingBottom: '8px'} : {}">
<tb-device-profile-alarm [formControl]="alarmControl"
(removeAlarm)="removeAlarm($index)">
</tb-device-profile-alarm>

1
ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarms.component.scss

@ -17,7 +17,6 @@
:host {
.tb-device-profile-alarms {
max-height: 400px;
overflow-y: auto;
&.mat-padding {
padding: 8px;

43
ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarms.component.ts

@ -19,9 +19,12 @@ import {
AbstractControl,
ControlValueAccessor,
FormArray,
FormBuilder, FormControl,
FormGroup, NG_VALIDATORS,
NG_VALUE_ACCESSOR, Validator,
FormBuilder,
FormControl,
FormGroup,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
Validator,
Validators
} from '@angular/forms';
import { Store } from '@ngrx/store';
@ -30,10 +33,6 @@ import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { DeviceProfileAlarm } from '@shared/models/device.models';
import { guid } from '@core/utils';
import { Subscription } from 'rxjs';
import {
DeviceProfileAlarmDialogComponent,
DeviceProfileAlarmDialogData
} from './device-profile-alarm-dialog.component';
import { MatDialog } from '@angular/material/dialog';
@Component({
@ -125,6 +124,14 @@ export class DeviceProfileAlarmsComponent implements ControlValueAccessor, OnIni
});
}
public trackByAlarm(index: number, alarmControl: AbstractControl): string {
if (alarmControl) {
return alarmControl.value.id;
} else {
return null;
}
}
public removeAlarm(index: number) {
(this.deviceProfileAlarmsFormGroup.get('alarms') as FormArray).removeAt(index);
}
@ -144,22 +151,6 @@ export class DeviceProfileAlarmsComponent implements ControlValueAccessor, OnIni
const alarmsArray = this.deviceProfileAlarmsFormGroup.get('alarms') as FormArray;
alarmsArray.push(this.fb.control(alarm, [Validators.required]));
this.deviceProfileAlarmsFormGroup.updateValueAndValidity();
/* this.dialog.open<DeviceProfileAlarmDialogComponent, DeviceProfileAlarmDialogData,
DeviceProfileAlarm>(DeviceProfileAlarmDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
isAdd: true,
alarm,
isReadOnly: false
}
}).afterClosed().subscribe(
(deviceProfileAlarm) => {
if (deviceProfileAlarm) {
}
}
); */
}
public validate(c: FormControl) {
@ -171,11 +162,11 @@ export class DeviceProfileAlarmsComponent implements ControlValueAccessor, OnIni
}
private updateModel() {
if (this.deviceProfileAlarmsFormGroup.valid) {
// if (this.deviceProfileAlarmsFormGroup.valid) {
const alarms: Array<DeviceProfileAlarm> = this.deviceProfileAlarmsFormGroup.get('alarms').value;
this.propagateChange(alarms);
} else {
/* } else {
this.propagateChange(null);
}
} */
}
}

2
ui-ngx/src/app/modules/home/components/profile/device-profile-dialog.component.html

@ -15,7 +15,7 @@
limitations under the License.
-->
<form (ngSubmit)="save()" style="min-width: 600px;">
<form (ngSubmit)="save()" style="min-width: 1000px;">
<mat-toolbar color="primary">
<h2>{{ (isAdd ? 'device-profile.add' : 'device-profile.edit' ) | translate }}</h2>
<span fxFlex></span>

2
ui-ngx/src/app/modules/home/pages/device-profile/device-profiles-table-config.resolver.ts

@ -52,7 +52,7 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon
this.config.entityTranslations = entityTypeTranslations.get(EntityType.DEVICE_PROFILE);
this.config.entityResources = entityTypeResources.get(EntityType.DEVICE_PROFILE);
this.config.addDialogStyle = {width: '600px'};
this.config.addDialogStyle = {width: '1000px'};
this.config.columns.push(
new DateEntityTableColumn<DeviceProfile>('createdTime', 'common.created-time', this.datePipe, '150px'),

77
ui-ngx/src/app/shared/models/query/query.models.ts

@ -148,12 +148,16 @@ export function createDefaultFilterPredicateInfo(valueType: EntityKeyValueType,
const predicate = createDefaultFilterPredicate(valueType, complex);
return {
keyFilterPredicate: predicate,
userInfo: {
editable: true,
label: '',
autogeneratedLabel: true,
order: 0
}
userInfo: createDefaultFilterPredicateUserInfo()
};
}
export function createDefaultFilterPredicateUserInfo(): KeyFilterPredicateUserInfo {
return {
editable: true,
label: '',
autogeneratedLabel: true,
order: 0
};
}
@ -334,6 +338,7 @@ export interface KeyFilterPredicateInfo {
export interface KeyFilter {
key: EntityKey;
valueType: EntityKeyValueType;
predicate: KeyFilterPredicate;
}
@ -353,6 +358,45 @@ export interface FiltersInfo {
datasourceFilters: {[datasourceIndex: number]: FilterInfo};
}
export function keyFilterInfosToKeyFilters(keyFilterInfos: Array<KeyFilterInfo>): Array<KeyFilter> {
const keyFilters: Array<KeyFilter> = [];
for (const keyFilterInfo of keyFilterInfos) {
const key = keyFilterInfo.key;
for (const predicate of keyFilterInfo.predicates) {
const keyFilter: KeyFilter = {
key,
valueType: keyFilterInfo.valueType,
predicate: keyFilterPredicateInfoToKeyFilterPredicate(predicate)
};
keyFilters.push(keyFilter);
}
}
return keyFilters;
}
export function keyFiltersToKeyFilterInfos(keyFilters: Array<KeyFilter>): Array<KeyFilterInfo> {
const keyFilterInfos: Array<KeyFilterInfo> = [];
const keyFilterInfoMap: {[infoKey: string]: KeyFilterInfo} = {};
for (const keyFilter of keyFilters) {
const key = keyFilter.key;
const infoKey = key.key + key.type + keyFilter.valueType;
let keyFilterInfo = keyFilterInfoMap[infoKey];
if (!keyFilterInfo) {
keyFilterInfo = {
key,
valueType: keyFilter.valueType,
predicates: []
};
keyFilterInfoMap[infoKey] = keyFilterInfo;
keyFilterInfos.push(keyFilterInfo);
}
if (keyFilter.predicate) {
keyFilterInfo.predicates.push(keyFilterPredicateToKeyFilterPredicateInfo(keyFilter.predicate));
}
}
return keyFilterInfos;
}
export function filterInfoToKeyFilters(filter: FilterInfo): Array<KeyFilter> {
const keyFilterInfos = filter.keyFilters;
const keyFilters: Array<KeyFilter> = [];
@ -361,6 +405,7 @@ export function filterInfoToKeyFilters(filter: FilterInfo): Array<KeyFilter> {
for (const predicate of keyFilterInfo.predicates) {
const keyFilter: KeyFilter = {
key,
valueType: keyFilterInfo.valueType,
predicate: keyFilterPredicateInfoToKeyFilterPredicate(predicate)
};
keyFilters.push(keyFilter);
@ -383,6 +428,26 @@ export function keyFilterPredicateInfoToKeyFilterPredicate(keyFilterPredicateInf
return keyFilterPredicate;
}
export function keyFilterPredicateToKeyFilterPredicateInfo(keyFilterPredicate: KeyFilterPredicate): KeyFilterPredicateInfo {
const keyFilterPredicateInfo: KeyFilterPredicateInfo = {
keyFilterPredicate: null,
userInfo: null
};
if (keyFilterPredicate.type === FilterPredicateType.COMPLEX) {
const complexPredicate = keyFilterPredicate as ComplexFilterPredicate;
const predicateInfos = complexPredicate.predicates.map(
predicate => keyFilterPredicateToKeyFilterPredicateInfo(predicate));
keyFilterPredicateInfo.keyFilterPredicate = {
predicates: predicateInfos,
operation: complexPredicate.operation,
type: FilterPredicateType.COMPLEX
} as ComplexFilterPredicateInfo;
} else {
keyFilterPredicateInfo.keyFilterPredicate = keyFilterPredicate;
}
return keyFilterPredicateInfo;
}
export function isFilterEditable(filter: FilterInfo): boolean {
if (filter.editable) {
return filter.keyFilters.some(value => isKeyFilterInfoEditable(value));

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

@ -815,8 +815,21 @@
"create-alarm-rules": "Create alarm rules",
"clear-alarm-rule": "Clear alarm rule",
"add-create-alarm-rule": "Add create alarm rule",
"add-clear-alarm-rule": "Add clear alarm rule",
"select-alarm-severity": "Select alarm severity",
"alarm-severity-required": "Alarm severity is required."
"alarm-severity-required": "Alarm severity is required.",
"condition-duration": "Condition duration",
"condition-duration-value": "Duration value",
"condition-duration-time-unit": "Time unit",
"condition-duration-value-range": "Duration value should be in a range from 1 to 2147483647.",
"condition-duration-value-required": "Duration value is required.",
"condition-duration-time-unit-required": "Time unit is required.",
"advanced-settings": "Advanced settings",
"propagate-alarm": "Propagate alarm",
"alarm-details": "Alarm details",
"alarm-rule-condition": "Alarm rule condition",
"enter-alarm-rule-condition-prompt": "Please add alarm rule condition",
"edit-alarm-rule-condition": "Edit alarm rule condition"
},
"dialog": {
"close": "Close dialog"
@ -1286,6 +1299,7 @@
"complex-filter": "Complex filter",
"edit-complex-filter": "Edit complex filter",
"edit-filter-user-params": "Edit filter predicate user parameters",
"filter-user-params": "Filter predicate user parameters",
"user-parameters": "User parameters",
"display-label": "Label to display",
"autogenerated-label": "Auto generate label",

2
ui-ngx/src/styles.scss

@ -563,7 +563,7 @@ mat-label {
}
}
mat-toolbar.mat-table-toolbar:not(.mat-primary), .mat-cell {
mat-toolbar.mat-table-toolbar:not(.mat-primary), .mat-cell, .mat-expansion-panel-header {
button.mat-icon-button {
mat-icon {
color: rgba(0, 0, 0, .54);

Loading…
Cancel
Save