Browse Source

UI: Device profile alarm rules

pull/3477/head
Igor Kulikov 6 years ago
parent
commit
79e06fbbe7
  1. 2
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmCondition.java
  2. 3
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmRule.java
  3. 1
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileAlarm.java
  4. 18
      ui-ngx/src/app/modules/home/components/home-components.module.ts
  5. 19
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.html
  6. 112
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.ts
  7. 22
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.html
  8. 111
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.ts
  9. 54
      ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.html
  10. 18
      ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.scss
  11. 164
      ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.ts
  12. 65
      ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm-dialog.component.html
  13. 99
      ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm-dialog.component.ts
  14. 47
      ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html
  15. 48
      ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.scss
  16. 144
      ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.ts
  17. 38
      ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarms.component.html
  18. 29
      ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarms.component.scss
  19. 181
      ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarms.component.ts
  20. 12
      ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.html
  21. 4
      ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.ts
  22. 23
      ui-ngx/src/app/shared/models/device.models.ts
  23. 18
      ui-ngx/src/app/shared/models/time/time.models.ts
  24. 22
      ui-ngx/src/assets/locale/locale.constant-en_US.json

2
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmCondition.java

@ -15,11 +15,13 @@
*/
package org.thingsboard.server.common.data.device.profile;
import lombok.Data;
import org.thingsboard.server.common.data.query.KeyFilter;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Data
public class AlarmCondition {
private List<KeyFilter> condition;

3
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmRule.java

@ -15,6 +15,9 @@
*/
package org.thingsboard.server.common.data.device.profile;
import lombok.Data;
@Data
public class AlarmRule {
private AlarmCondition condition;

1
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileAlarm.java

@ -33,5 +33,4 @@ public class DeviceProfileAlarm {
// Hidden in advanced settings
private boolean propagate;
private List<String> propagateRelationTypes;
}

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

@ -98,6 +98,12 @@ import { DeviceProfileDialogComponent } from './profile/device-profile-dialog.co
import { DeviceProfileAutocompleteComponent } from './profile/device-profile-autocomplete.component';
import { MqttDeviceProfileTransportConfigurationComponent } from './profile/device/mqtt-device-profile-transport-configuration.component';
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';
@NgModule({
declarations:
@ -176,6 +182,12 @@ import { Lwm2mDeviceProfileTransportConfigurationComponent } from './profile/dev
MqttDeviceProfileTransportConfigurationComponent,
Lwm2mDeviceProfileTransportConfigurationComponent,
DeviceProfileTransportConfigurationComponent,
CreateAlarmRulesComponent,
AlarmRuleComponent,
AlarmRuleConditionComponent,
DeviceProfileAlarmComponent,
DeviceProfileAlarmDialogComponent,
DeviceProfileAlarmsComponent,
DeviceProfileDataComponent,
DeviceProfileComponent,
DeviceProfileDialogComponent
@ -246,6 +258,12 @@ import { Lwm2mDeviceProfileTransportConfigurationComponent } from './profile/dev
MqttDeviceProfileTransportConfigurationComponent,
Lwm2mDeviceProfileTransportConfigurationComponent,
DeviceProfileTransportConfigurationComponent,
CreateAlarmRulesComponent,
AlarmRuleComponent,
AlarmRuleConditionComponent,
DeviceProfileAlarmComponent,
DeviceProfileAlarmDialogComponent,
DeviceProfileAlarmsComponent,
DeviceProfileDataComponent,
DeviceProfileComponent,
DeviceProfileDialogComponent

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

@ -0,0 +1,19 @@
<!--
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.
-->
<div fxLayout="row" [formGroup]="alarmRuleConditionFormGroup">
</div>

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

@ -0,0 +1,112 @@
///
/// 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.
///
import { Component, forwardRef, Input, OnInit } from '@angular/core';
import {
ControlValueAccessor,
FormBuilder,
FormControl,
FormGroup,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
Validator,
Validators
} from '@angular/forms';
import { AlarmCondition } from '@shared/models/device.models';
import { MatDialog } from '@angular/material/dialog';
@Component({
selector: 'tb-alarm-rule-condition',
templateUrl: './alarm-rule-condition.component.html',
styleUrls: [],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => AlarmRuleConditionComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => AlarmRuleConditionComponent),
multi: true,
}
]
})
export class AlarmRuleConditionComponent implements ControlValueAccessor, OnInit, Validator {
@Input()
disabled: boolean;
private modelValue: AlarmCondition;
alarmRuleConditionFormGroup: FormGroup;
private propagateChange = (v: any) => { };
constructor(private dialog: MatDialog,
private fb: FormBuilder) {
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
ngOnInit() {
this.alarmRuleConditionFormGroup = this.fb.group({
condition: [null, Validators.required],
durationUnit: [null],
durationValue: [null]
});
this.alarmRuleConditionFormGroup.valueChanges.subscribe(() => {
this.updateModel();
});
}
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 {
this.modelValue = value;
this.alarmRuleConditionFormGroup.reset(this.modelValue, {emitEvent: false});
}
public validate(c: FormControl) {
return (this.alarmRuleConditionFormGroup.valid) ? null : {
alarmRuleCondition: {
valid: false,
},
};
}
private updateModel() {
if (this.alarmRuleConditionFormGroup.valid) {
const value = this.alarmRuleConditionFormGroup.value;
this.modelValue = {...this.modelValue, ...value};
this.propagateChange(this.modelValue);
} else {
this.propagateChange(null);
}
}
}

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

@ -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.
-->
<div fxLayout="row" [formGroup]="alarmRuleFormGroup">
<tb-alarm-rule-condition
formControlName="condition">
</tb-alarm-rule-condition>
</div>

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

@ -0,0 +1,111 @@
///
/// 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.
///
import { Component, forwardRef, Input, OnInit } from '@angular/core';
import {
ControlValueAccessor,
FormBuilder,
FormControl,
FormGroup,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
Validator,
Validators
} from '@angular/forms';
import { AlarmRule } from '@shared/models/device.models';
import { MatDialog } from '@angular/material/dialog';
@Component({
selector: 'tb-alarm-rule',
templateUrl: './alarm-rule.component.html',
styleUrls: [],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => AlarmRuleComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => AlarmRuleComponent),
multi: true,
}
]
})
export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validator {
@Input()
disabled: boolean;
private modelValue: AlarmRule;
alarmRuleFormGroup: FormGroup;
private propagateChange = (v: any) => { };
constructor(private dialog: MatDialog,
private fb: FormBuilder) {
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
ngOnInit() {
this.alarmRuleFormGroup = this.fb.group({
condition: [null, Validators.required],
alarmDetails: [null]
});
this.alarmRuleFormGroup.valueChanges.subscribe(() => {
this.updateModel();
});
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (this.disabled) {
this.alarmRuleFormGroup.disable({emitEvent: false});
} else {
this.alarmRuleFormGroup.enable({emitEvent: false});
}
}
writeValue(value: AlarmRule): void {
this.modelValue = value;
this.alarmRuleFormGroup.reset(this.modelValue, {emitEvent: false});
}
public validate(c: FormControl) {
return (this.alarmRuleFormGroup.valid) ? null : {
alarmRule: {
valid: false,
},
};
}
private updateModel() {
if (this.alarmRuleFormGroup.valid) {
const value = this.alarmRuleFormGroup.value;
this.modelValue = {...this.modelValue, ...value};
this.propagateChange(this.modelValue);
} else {
this.propagateChange(null);
}
}
}

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

@ -0,0 +1,54 @@
<!--
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.
-->
<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>
<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>
</button>
</div>
<div *ngIf="!disabled" fxFlex fxLayout="row" fxLayoutAlign="start center">
<button mat-icon-button color="primary"
type="button"
(click)="addCreateAlarmRule()"
matTooltip="{{ 'device-profile.add-create-alarm-rule' | translate }}"
matTooltipPosition="above">
<mat-icon>add</mat-icon>
</button>
</div>
</div>

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

@ -0,0 +1,18 @@
/**
* 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 {
}

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

@ -0,0 +1,164 @@
///
/// 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.
///
import { Component, forwardRef, Input, OnInit } from '@angular/core';
import {
AbstractControl,
ControlValueAccessor,
FormArray,
FormBuilder,
FormControl,
FormGroup,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
Validator,
Validators
} from '@angular/forms';
import { AlarmRule } from '@shared/models/device.models';
import { MatDialog } from '@angular/material/dialog';
import { Subscription } from 'rxjs';
import { AlarmSeverity, alarmSeverityTranslations } from '../../../../../shared/models/alarm.models';
@Component({
selector: 'tb-create-alarm-rules',
templateUrl: './create-alarm-rules.component.html',
styleUrls: ['./create-alarm-rules.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CreateAlarmRulesComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => CreateAlarmRulesComponent),
multi: true,
}
]
})
export class CreateAlarmRulesComponent implements ControlValueAccessor, OnInit, Validator {
alarmSeverities = Object.keys(AlarmSeverity);
alarmSeverityEnum = AlarmSeverity;
alarmSeverityTranslationMap = alarmSeverityTranslations;
@Input()
disabled: boolean;
createAlarmRulesFormGroup: FormGroup;
private valueChangeSubscription: Subscription = null;
private propagateChange = (v: any) => { };
constructor(private dialog: MatDialog,
private fb: FormBuilder) {
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
ngOnInit() {
this.createAlarmRulesFormGroup = this.fb.group({
createAlarmRules: this.fb.array([])
});
}
createAlarmRulesFormArray(): FormArray {
return this.createAlarmRulesFormGroup.get('createAlarmRules') as FormArray;
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (this.disabled) {
this.createAlarmRulesFormGroup.disable({emitEvent: false});
} else {
this.createAlarmRulesFormGroup.enable({emitEvent: false});
}
}
writeValue(createAlarmRules: {[severity: string]: AlarmRule}): void {
if (this.valueChangeSubscription) {
this.valueChangeSubscription.unsubscribe();
}
const createAlarmRulesControls: Array<AbstractControl> = [];
if (createAlarmRules) {
Object.keys(createAlarmRules).forEach((severity) => {
const createAlarmRule = createAlarmRules[severity];
if (severity === 'empty') {
severity = null;
}
createAlarmRulesControls.push(this.fb.group({
severity: [severity, Validators.required],
alarmRule: [createAlarmRule, Validators.required]
}));
});
}
this.createAlarmRulesFormGroup.setControl('createAlarmRules', this.fb.array(createAlarmRulesControls));
if (this.disabled) {
this.createAlarmRulesFormGroup.disable({emitEvent: false});
} else {
this.createAlarmRulesFormGroup.enable({emitEvent: false});
}
this.valueChangeSubscription = this.createAlarmRulesFormGroup.valueChanges.subscribe(() => {
this.updateModel();
});
}
public removeCreateAlarmRule(index: number) {
(this.createAlarmRulesFormGroup.get('createAlarmRules') as FormArray).removeAt(index);
}
public addCreateAlarmRule() {
const createAlarmRule: AlarmRule = {
condition: {
condition: []
}
};
const createAlarmRulesArray = this.createAlarmRulesFormGroup.get('createAlarmRules') as FormArray;
createAlarmRulesArray.push(this.fb.group({
severity: [null, Validators.required],
alarmRule: [createAlarmRule, Validators.required]
}));
this.createAlarmRulesFormGroup.updateValueAndValidity();
}
public validate(c: FormControl) {
return (this.createAlarmRulesFormGroup.valid && this.createAlarmRulesFormGroup.get('createAlarmRules').value.length) ? null : {
createAlarmRules: {
valid: false,
},
};
}
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);
}
}
}

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

@ -0,0 +1,65 @@
<!--
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>

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

@ -0,0 +1,99 @@
///
/// 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.
///
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';
export interface DeviceProfileAlarmDialogData {
alarm: DeviceProfileAlarm;
isAdd: boolean;
isReadOnly: boolean;
}
@Component({
selector: 'tb-device-profile-alarm-dialog',
templateUrl: './device-profile-alarm-dialog.component.html',
providers: [{provide: ErrorStateMatcher, useExisting: DeviceProfileAlarmDialogComponent}],
styleUrls: []
})
export class DeviceProfileAlarmDialogComponent extends
DialogComponent<DeviceProfileAlarmDialogComponent, DeviceProfileAlarm> implements OnInit, ErrorStateMatcher {
alarmFormGroup: FormGroup;
isReadOnly = this.data.isReadOnly;
alarm = this.data.alarm;
isAdd = this.data.isAdd;
submitted = false;
constructor(protected store: Store<AppState>,
protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: DeviceProfileAlarmDialogData,
public dialogRef: MatDialogRef<DeviceProfileAlarmDialogComponent, DeviceProfileAlarm>,
@SkipSelf() private errorStateMatcher: ErrorStateMatcher,
public fb: FormBuilder) {
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.alarmFormGroup.reset(this.alarm, {emitEvent: false});
if (this.isReadOnly) {
this.alarmFormGroup.disable({emitEvent: false});
}
}
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
const originalErrorState = this.errorStateMatcher.isErrorState(control, form);
const customErrorState = !!(control && control.invalid && this.submitted);
return originalErrorState || customErrorState;
}
cancel(): void {
this.dialogRef.close(null);
}
save(): void {
this.submitted = true;
if (this.alarmFormGroup.valid) {
this.alarm = {...this.alarm, ...this.alarmFormGroup.value};
this.dialogRef.close(this.alarm);
}
}
}

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

@ -0,0 +1,47 @@
<!--
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.
-->
<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>
<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">
</tb-create-alarm-rules>
<div translate class="tb-small" style="font-weight: 500;">device-profile.clear-alarm-rule</div>
<mat-divider></mat-divider>
</div>
</fieldset>

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

@ -0,0 +1,48 @@
/**
* 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.
*/
@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;
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;
}
}
}
}

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

@ -0,0 +1,144 @@
///
/// 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.
///
import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core';
import {
ControlValueAccessor,
FormBuilder,
FormControl,
FormGroup, NG_VALIDATORS,
NG_VALUE_ACCESSOR, Validator,
Validators
} from '@angular/forms';
import { DeviceProfileAlarm } from '@shared/models/device.models';
import { deepClone } from '@core/utils';
import { MatDialog } from '@angular/material/dialog';
import {
DeviceProfileAlarmDialogComponent,
DeviceProfileAlarmDialogData
} from './device-profile-alarm-dialog.component';
@Component({
selector: 'tb-device-profile-alarm',
templateUrl: './device-profile-alarm.component.html',
styleUrls: ['./device-profile-alarm.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DeviceProfileAlarmComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => DeviceProfileAlarmComponent),
multi: true,
}
]
})
export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit, Validator {
@Input()
disabled: boolean;
@Output()
removeAlarm = new EventEmitter();
private modelValue: DeviceProfileAlarm;
alarmFormGroup: FormGroup;
private propagateChange = (v: any) => { };
constructor(private dialog: MatDialog,
private fb: FormBuilder) {
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
ngOnInit() {
this.alarmFormGroup = this.fb.group({
id: [null, Validators.required],
alarmType: [null, Validators.required],
createRules: [null],
clearRule: [null],
propagate: [null],
propagateRelationTypes: [null]
});
this.alarmFormGroup.valueChanges.subscribe(() => {
this.updateModel();
});
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (this.disabled) {
this.alarmFormGroup.disable({emitEvent: false});
} else {
this.alarmFormGroup.enable({emitEvent: false});
}
}
writeValue(value: DeviceProfileAlarm): void {
this.modelValue = value;
this.alarmFormGroup.reset(this.modelValue, {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 validate(c: FormControl) {
return (this.alarmFormGroup.valid) ? null : {
alarm: {
valid: false,
},
};
}
private updateModel() {
if (this.alarmFormGroup.valid) {
const value = this.alarmFormGroup.value;
this.modelValue = {...this.modelValue, ...value};
this.propagateChange(this.modelValue);
} else {
this.propagateChange(null);
}
}
}

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

@ -0,0 +1,38 @@
<!--
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.
-->
<div fxLayout="column">
<div class="tb-device-profile-alarms">
<div *ngFor="let alarmControl of alarmsFormArray().controls; let $index = index; last as isLast;"
fxLayout="column">
<tb-device-profile-alarm [formControl]="alarmControl"
(removeAlarm)="removeAlarm($index)">
</tb-device-profile-alarm>
</div>
</div>
<div *ngIf="!disabled" fxFlex fxLayout="row" fxLayoutAlign="end center"
style="padding-top: 16px;">
<button mat-raised-button color="primary"
type="button"
(click)="addAlarm()"
matTooltip="{{ 'device-profile.add-alarm-rule' | translate }}"
matTooltipPosition="above">
<mat-icon>add</mat-icon>
<span translate>action.add</span>
</button>
</div>
</div>

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

@ -0,0 +1,29 @@
/**
* 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.
*/
@import '../scss/constants';
:host {
.tb-device-profile-alarms {
max-height: 400px;
overflow-y: auto;
&.mat-padding {
padding: 8px;
@media #{$mat-gt-sm} {
padding: 16px;
}
}
}
}

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

@ -0,0 +1,181 @@
///
/// 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.
///
import { Component, forwardRef, Input, OnInit } from '@angular/core';
import {
AbstractControl,
ControlValueAccessor,
FormArray,
FormBuilder, FormControl,
FormGroup, NG_VALIDATORS,
NG_VALUE_ACCESSOR, Validator,
Validators
} from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@app/core/core.state';
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({
selector: 'tb-device-profile-alarms',
templateUrl: './device-profile-alarms.component.html',
styleUrls: ['./device-profile-alarms.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DeviceProfileAlarmsComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => DeviceProfileAlarmsComponent),
multi: true,
}
]
})
export class DeviceProfileAlarmsComponent implements ControlValueAccessor, OnInit, Validator {
deviceProfileAlarmsFormGroup: FormGroup;
private requiredValue: boolean;
get required(): boolean {
return this.requiredValue;
}
@Input()
set required(value: boolean) {
this.requiredValue = coerceBooleanProperty(value);
}
@Input()
disabled: boolean;
private valueChangeSubscription: Subscription = null;
private propagateChange = (v: any) => { };
constructor(private store: Store<AppState>,
private fb: FormBuilder,
private dialog: MatDialog) {
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
ngOnInit() {
this.deviceProfileAlarmsFormGroup = this.fb.group({
alarms: this.fb.array([])
});
}
alarmsFormArray(): FormArray {
return this.deviceProfileAlarmsFormGroup.get('alarms') as FormArray;
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (this.disabled) {
this.deviceProfileAlarmsFormGroup.disable({emitEvent: false});
} else {
this.deviceProfileAlarmsFormGroup.enable({emitEvent: false});
}
}
writeValue(alarms: Array<DeviceProfileAlarm> | null): void {
if (this.valueChangeSubscription) {
this.valueChangeSubscription.unsubscribe();
}
const alarmsControls: Array<AbstractControl> = [];
if (alarms) {
alarms.forEach((alarm) => {
alarmsControls.push(this.fb.control(alarm, [Validators.required]));
});
}
this.deviceProfileAlarmsFormGroup.setControl('alarms', this.fb.array(alarmsControls));
if (this.disabled) {
this.deviceProfileAlarmsFormGroup.disable({emitEvent: false});
} else {
this.deviceProfileAlarmsFormGroup.enable({emitEvent: false});
}
this.valueChangeSubscription = this.deviceProfileAlarmsFormGroup.valueChanges.subscribe(() => {
this.updateModel();
});
}
public removeAlarm(index: number) {
(this.deviceProfileAlarmsFormGroup.get('alarms') as FormArray).removeAt(index);
}
public addAlarm() {
const alarm: DeviceProfileAlarm = {
id: guid(),
alarmType: '',
createRules: {
empty: {
condition: {
condition: []
}
}
}
};
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) {
return (this.deviceProfileAlarmsFormGroup.valid) ? null : {
alarms: {
valid: false,
},
};
}
private updateModel() {
if (this.deviceProfileAlarmsFormGroup.valid) {
const alarms: Array<DeviceProfileAlarm> = this.deviceProfileAlarmsFormGroup.get('alarms').value;
this.propagateChange(alarms);
} else {
this.propagateChange(null);
}
}
}

12
ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.html

@ -39,5 +39,17 @@
required>
</tb-device-profile-transport-configuration>
</mat-expansion-panel>
<mat-expansion-panel [expanded]="false">
<mat-expansion-panel-header>
<mat-panel-title>
<div>{{'device-profile.alarm-rules' | translate:
{count: deviceProfileDataFormGroup.get('alarms').value ?
deviceProfileDataFormGroup.get('alarms').value.length : 0} }}</div>
</mat-panel-title>
</mat-expansion-panel-header>
<tb-device-profile-alarms
formControlName="alarms">
</tb-device-profile-alarms>
</mat-expansion-panel>
</mat-accordion>
</div>

4
ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.ts

@ -71,7 +71,8 @@ export class DeviceProfileDataComponent implements ControlValueAccessor, OnInit
ngOnInit() {
this.deviceProfileDataFormGroup = this.fb.group({
configuration: [null, Validators.required],
transportConfiguration: [null, Validators.required]
transportConfiguration: [null, Validators.required],
alarms: [null]
});
this.deviceProfileDataFormGroup.valueChanges.subscribe(() => {
this.updateModel();
@ -96,6 +97,7 @@ export class DeviceProfileDataComponent implements ControlValueAccessor, OnInit
deviceTransportTypeConfigurationInfoMap.get(deviceTransportType).hasProfileConfiguration;
this.deviceProfileDataFormGroup.patchValue({configuration: value?.configuration}, {emitEvent: false});
this.deviceProfileDataFormGroup.patchValue({transportConfiguration: value?.transportConfiguration}, {emitEvent: false});
this.deviceProfileDataFormGroup.patchValue({alarms: value?.alarms}, {emitEvent: false});
}
private updateModel() {

23
ui-ngx/src/app/shared/models/device.models.ts

@ -23,6 +23,8 @@ import { EntitySearchQuery } from '@shared/models/relation.models';
import { DeviceProfileId } from '@shared/models/id/device-profile-id';
import { RuleChainId } from '@shared/models/id/rule-chain-id';
import { EntityInfoData } from '@shared/models/entity.models';
import { KeyFilter } from '@shared/models/query/query.models';
import { TimeUnit } from '@shared/models/time/time.models';
export enum DeviceProfileType {
DEFAULT = 'DEFAULT'
@ -198,9 +200,30 @@ export function createDeviceTransportConfiguration(type: DeviceTransportType): D
return transportConfiguration;
}
export interface AlarmCondition {
condition: Array<KeyFilter>;
durationUnit?: TimeUnit;
durationValue?: number;
}
export interface AlarmRule {
condition: AlarmCondition;
alarmDetails?: string;
}
export interface DeviceProfileAlarm {
id: string;
alarmType: string;
createRules: {[severity: string]: AlarmRule};
clearRule?: AlarmRule;
propagate?: boolean;
propagateRelationTypes?: Array<string>;
}
export interface DeviceProfileData {
configuration: DeviceProfileConfiguration;
transportConfiguration: DeviceProfileTransportConfiguration;
alarms?: Array<DeviceProfileAlarm>;
}
export interface DeviceProfile extends BaseData<DeviceProfileId> {

18
ui-ngx/src/app/shared/models/time/time.models.ts

@ -465,3 +465,21 @@ export const defaultTimeIntervals = new Array<TimeInterval>(
value: 30 * DAY
}
);
export enum TimeUnit {
MILLISECONDS = 'MILLISECONDS',
SECONDS = 'SECONDS',
MINUTES = 'MINUTES',
HOURS = 'HOURS',
DAYS = 'DAYS'
}
export const timeUnitTranslationMap = new Map<TimeUnit, string>(
[
[TimeUnit.MILLISECONDS, 'timeunit.milliseconds'],
[TimeUnit.SECONDS, 'timeunit.seconds'],
[TimeUnit.MINUTES, 'timeunit.minutes'],
[TimeUnit.HOURS, 'timeunit.hours'],
[TimeUnit.DAYS, 'timeunit.days']
]
);

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

@ -801,7 +801,20 @@
"attributes-topic-filter-required": "Attributes topic filter is required.",
"rpc-response-topic-filter": "RPC response topic filter",
"rpc-response-topic-filter-required": "RPC response topic filter is required.",
"not-valid-pattern-topic-filter": "Not valid pattern topic filter"
"not-valid-pattern-topic-filter": "Not valid pattern topic filter",
"alarm-rules": "Alarm rules ({{count}})",
"add-alarm-rule": "Add alarm rule",
"edit-alarm-rule": "Edit alarm rule",
"alarm-rule-details": "Alarm rule details",
"alarm-type": "Alarm type",
"alarm-type-required": "Alarm type is required.",
"alarm-type-pattern-hint": "Alarm type pattern, use <code>${metaKeyName}</code> to substitute variables from metadata",
"create-alarm-pattern": "Create <b>{{alarmType}}</b> alarm",
"create-alarm-rules": "Create alarm rules",
"clear-alarm-rule": "Clear alarm rule",
"add-create-alarm-rule": "Add create alarm rule",
"select-alarm-severity": "Select alarm severity",
"alarm-severity-required": "Alarm severity is required."
},
"dialog": {
"close": "Close dialog"
@ -1760,6 +1773,13 @@
"seconds": "Seconds",
"advanced": "Advanced"
},
"timeunit": {
"milliseconds": "Milliseconds",
"seconds": "Seconds",
"minutes": "Minutes",
"hours": "Hours",
"days": "Days"
},
"timewindow": {
"days": "{ days, plural, 1 { day } other {# days } }",
"hours": "{ hours, plural, 0 { hour } 1 {1 hour } other {# hours } }",

Loading…
Cancel
Save