UI: Device profile alarm rules
This commit is contained in:
parent
3b55ebe5d4
commit
d9321b4816
@ -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
|
||||
}
|
||||
@ -21,6 +21,7 @@ import lombok.Data;
|
||||
public class KeyFilter {
|
||||
|
||||
private EntityKey key;
|
||||
private EntityKeyValueType valueType;
|
||||
private KeyFilterPredicate predicate;
|
||||
|
||||
}
|
||||
|
||||
@ -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)) ) {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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;"> </span>
|
||||
</div>
|
||||
</div>
|
||||
@ -50,6 +51,7 @@
|
||||
<tb-filter-predicate
|
||||
fxFlex
|
||||
[valueType]="valueType"
|
||||
[displayUserParameters]="displayUserParameters"
|
||||
[key]="key"
|
||||
[formControl]="predicateControl">
|
||||
</tb-filter-predicate>
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -41,6 +41,8 @@ export class FilterPredicateComponent implements ControlValueAccessor, OnInit {
|
||||
|
||||
@Input() key: string;
|
||||
|
||||
@Input() displayUserParameters = true;
|
||||
|
||||
filterPredicateFormGroup: FormGroup;
|
||||
|
||||
type: FilterPredicateType;
|
||||
|
||||
@ -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,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() {
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
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});
|
||||
}
|
||||
});
|
||||
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});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.keyFilterFormGroup.disable({emitEvent: false});
|
||||
}
|
||||
|
||||
this.entityFields = entityFields;
|
||||
this.entityFieldsList = Object.values(entityFields).map(entityField => entityField.keyName).sort();
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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,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>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
@ -14,68 +14,58 @@
|
||||
/// 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;
|
||||
|
||||
this.keyFiltersFormGroup = this.fb.group({
|
||||
keyFilters: [keyFiltersToKeyFilterInfos(this.keyFilters), Validators.required]
|
||||
});
|
||||
if (this.readonly) {
|
||||
this.keyFiltersFormGroup.disable({emitEvent: false});
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.alarmFormGroup = this.fb.group({
|
||||
id: [null, Validators.required],
|
||||
alarmType: [null, Validators.required],
|
||||
createRules: [null],
|
||||
clearRule: [null],
|
||||
propagate: [null],
|
||||
propagateRelationTypes: [null]
|
||||
});
|
||||
this.alarmFormGroup.reset(this.alarm, {emitEvent: false});
|
||||
if (this.isReadOnly) {
|
||||
this.alarmFormGroup.disable({emitEvent: false});
|
||||
}
|
||||
}
|
||||
|
||||
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -13,6 +13,23 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
:host {
|
||||
|
||||
: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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
a.mat-icon-button {
|
||||
&:hover, &:focus {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
.fields-group {
|
||||
padding: 8px;
|
||||
margin: 10px 0;
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.advanced-settings {
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
public addClearAlarmRule() {
|
||||
const clearAlarmRule: AlarmRule = {
|
||||
condition: {
|
||||
condition: []
|
||||
}
|
||||
}).afterClosed().subscribe(
|
||||
(deviceProfileAlarm) => {
|
||||
if (deviceProfileAlarm) {
|
||||
this.modelValue = deviceProfileAlarm;
|
||||
this.updateModel();
|
||||
}
|
||||
}
|
||||
);
|
||||
} */
|
||||
};
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -17,7 +17,6 @@
|
||||
|
||||
:host {
|
||||
.tb-device-profile-alarms {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
&.mat-padding {
|
||||
padding: 8px;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
} */
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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'),
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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…
x
Reference in New Issue
Block a user