24 changed files with 1251 additions and 3 deletions
@ -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> |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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> |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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> |
|||
@ -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 { |
|||
|
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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> |
|||
@ -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); |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -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> |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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> |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue