Showing
15 changed files
with
409 additions
and
36 deletions
... | ... | @@ -105,6 +105,7 @@ import { AlarmRuleComponent } from './profile/alarm/alarm-rule.component'; |
105 | 105 | import { AlarmRuleConditionComponent } from './profile/alarm/alarm-rule-condition.component'; |
106 | 106 | import { AlarmRuleKeyFiltersDialogComponent } from './profile/alarm/alarm-rule-key-filters-dialog.component'; |
107 | 107 | import { FilterTextComponent } from './filter/filter-text.component'; |
108 | +import { AddDeviceProfileDialogComponent } from './profile/add-device-profile-dialog.component'; | |
108 | 109 | |
109 | 110 | @NgModule({ |
110 | 111 | declarations: |
... | ... | @@ -192,7 +193,8 @@ import { FilterTextComponent } from './filter/filter-text.component'; |
192 | 193 | DeviceProfileAlarmsComponent, |
193 | 194 | DeviceProfileDataComponent, |
194 | 195 | DeviceProfileComponent, |
195 | - DeviceProfileDialogComponent | |
196 | + DeviceProfileDialogComponent, | |
197 | + AddDeviceProfileDialogComponent | |
196 | 198 | ], |
197 | 199 | imports: [ |
198 | 200 | CommonModule, |
... | ... | @@ -269,7 +271,8 @@ import { FilterTextComponent } from './filter/filter-text.component'; |
269 | 271 | DeviceProfileAlarmsComponent, |
270 | 272 | DeviceProfileDataComponent, |
271 | 273 | DeviceProfileComponent, |
272 | - DeviceProfileDialogComponent | |
274 | + DeviceProfileDialogComponent, | |
275 | + AddDeviceProfileDialogComponent | |
273 | 276 | ], |
274 | 277 | providers: [ |
275 | 278 | WidgetComponentService, | ... | ... |
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2020 The Thingsboard Authors | |
4 | + | |
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | + you may not use this file except in compliance with the License. | |
7 | + You may obtain a copy of the License at | |
8 | + | |
9 | + http://www.apache.org/licenses/LICENSE-2.0 | |
10 | + | |
11 | + Unless required by applicable law or agreed to in writing, software | |
12 | + distributed under the License is distributed on an "AS IS" BASIS, | |
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | + See the License for the specific language governing permissions and | |
15 | + limitations under the License. | |
16 | + | |
17 | +--> | |
18 | +<div style="min-width: 1000px;"> | |
19 | + <mat-toolbar color="primary"> | |
20 | + <h2 translate>device-profile.add</h2> | |
21 | + <span fxFlex></span> | |
22 | + <button mat-icon-button | |
23 | + (click)="cancel()" | |
24 | + type="button"> | |
25 | + <mat-icon class="material-icons">close</mat-icon> | |
26 | + </button> | |
27 | + </mat-toolbar> | |
28 | + <mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> | |
29 | + </mat-progress-bar> | |
30 | + <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> | |
31 | + <div mat-dialog-content> | |
32 | + <mat-horizontal-stepper [linear]="true" #addDeviceProfileStepper (selectionChange)="selectedIndex = $event.selectedIndex"> | |
33 | + <mat-step [stepControl]="deviceProfileDetailsFormGroup"> | |
34 | + <form [formGroup]="deviceProfileDetailsFormGroup" style="padding-bottom: 16px;"> | |
35 | + <ng-template matStepLabel>{{ 'device-profile.device-profile-details' | translate }}</ng-template> | |
36 | + <fieldset [disabled]="isLoading$ | async"> | |
37 | + <mat-form-field class="mat-block"> | |
38 | + <mat-label translate>device-profile.name</mat-label> | |
39 | + <input matInput formControlName="name" required/> | |
40 | + <mat-error *ngIf="deviceProfileDetailsFormGroup.get('name').hasError('required')"> | |
41 | + {{ 'device-profile.name-required' | translate }} | |
42 | + </mat-error> | |
43 | + </mat-form-field> | |
44 | + <tb-entity-autocomplete | |
45 | + labelText="device-profile.default-rule-chain" | |
46 | + [entityType]="entityType.RULE_CHAIN" | |
47 | + formControlName="defaultRuleChainId"> | |
48 | + </tb-entity-autocomplete> | |
49 | + <mat-form-field class="mat-block"> | |
50 | + <mat-label translate>device-profile.type</mat-label> | |
51 | + <mat-select formControlName="type" required> | |
52 | + <mat-option *ngFor="let type of deviceProfileTypes" [value]="type"> | |
53 | + {{deviceProfileTypeTranslations.get(type) | translate}} | |
54 | + </mat-option> | |
55 | + </mat-select> | |
56 | + <mat-error *ngIf="deviceProfileDetailsFormGroup.get('type').hasError('required')"> | |
57 | + {{ 'device-profile.type-required' | translate }} | |
58 | + </mat-error> | |
59 | + </mat-form-field> | |
60 | + <mat-form-field class="mat-block"> | |
61 | + <mat-label translate>device-profile.description</mat-label> | |
62 | + <textarea matInput formControlName="description" rows="2"></textarea> | |
63 | + </mat-form-field> | |
64 | + </fieldset> | |
65 | + </form> | |
66 | + </mat-step> | |
67 | + <mat-step [stepControl]="transportConfigFormGroup"> | |
68 | + <form [formGroup]="transportConfigFormGroup" style="padding-bottom: 16px;"> | |
69 | + <ng-template matStepLabel>{{ 'device-profile.transport-configuration' | translate }}</ng-template> | |
70 | + <mat-form-field class="mat-block"> | |
71 | + <mat-label translate>device-profile.transport-type</mat-label> | |
72 | + <mat-select formControlName="transportType" required> | |
73 | + <mat-option *ngFor="let type of deviceTransportTypes" [value]="type"> | |
74 | + {{deviceTransportTypeTranslations.get(type) | translate}} | |
75 | + </mat-option> | |
76 | + </mat-select> | |
77 | + <mat-error *ngIf="transportConfigFormGroup.get('transportType').hasError('required')"> | |
78 | + {{ 'device-profile.transport-type-required' | translate }} | |
79 | + </mat-error> | |
80 | + </mat-form-field> | |
81 | + <tb-device-profile-transport-configuration | |
82 | + formControlName="transportConfiguration" | |
83 | + required> | |
84 | + </tb-device-profile-transport-configuration> | |
85 | + </form> | |
86 | + </mat-step> | |
87 | + <mat-step [stepControl]="alarmRulesFormGroup"> | |
88 | + <form [formGroup]="alarmRulesFormGroup" style="padding-bottom: 16px;"> | |
89 | + <ng-template matStepLabel>{{'device-profile.alarm-rules' | translate: | |
90 | + {count: alarmRulesFormGroup.get('alarms').value ? | |
91 | + alarmRulesFormGroup.get('alarms').value.length : 0} }}</ng-template> | |
92 | + <tb-device-profile-alarms | |
93 | + formControlName="alarms"> | |
94 | + </tb-device-profile-alarms> | |
95 | + </form> | |
96 | + </mat-step> | |
97 | + </mat-horizontal-stepper> | |
98 | + </div> | |
99 | + <div mat-dialog-actions fxLayout="row wrap" fxLayoutAlign="space-between center"> | |
100 | + <button mat-button *ngIf="selectedIndex > 0" | |
101 | + [disabled]="(isLoading$ | async)" | |
102 | + (click)="previousStep()">{{ 'action.back' | translate }}</button> | |
103 | + <span *ngIf="selectedIndex <= 0"></span> | |
104 | + <div fxLayout="row wrap" fxLayoutGap="20px"> | |
105 | + <button mat-button | |
106 | + [disabled]="(isLoading$ | async)" | |
107 | + (click)="cancel()">{{ 'action.cancel' | translate }}</button> | |
108 | + <button mat-raised-button | |
109 | + [disabled]="(isLoading$ | async) || selectedForm().invalid" | |
110 | + color="primary" | |
111 | + (click)="nextStep()">{{ (selectedIndex === 2 ? 'action.add' : 'action.continue') | translate }}</button> | |
112 | + </div> | |
113 | + </div> | |
114 | +</div> | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2020 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +:host { | |
17 | + .mat-dialog-content { | |
18 | + display: flex; | |
19 | + flex-direction: column; | |
20 | + overflow: hidden; | |
21 | + | |
22 | + .mat-stepper-horizontal { | |
23 | + display: flex; | |
24 | + flex-direction: column; | |
25 | + overflow: hidden; | |
26 | + } | |
27 | + } | |
28 | +} | |
29 | + | |
30 | +:host ::ng-deep { | |
31 | + .mat-dialog-content { | |
32 | + .mat-stepper-horizontal { | |
33 | + .mat-horizontal-content-container { | |
34 | + overflow: auto; | |
35 | + } | |
36 | + } | |
37 | + } | |
38 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2020 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { | |
18 | + AfterViewInit, | |
19 | + Component, | |
20 | + ComponentFactoryResolver, | |
21 | + Inject, | |
22 | + Injector, | |
23 | + SkipSelf, | |
24 | + ViewChild | |
25 | +} from '@angular/core'; | |
26 | +import { ErrorStateMatcher } from '@angular/material/core'; | |
27 | +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; | |
28 | +import { Store } from '@ngrx/store'; | |
29 | +import { AppState } from '@core/core.state'; | |
30 | +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; | |
31 | +import { DialogComponent } from '@shared/components/dialog.component'; | |
32 | +import { Router } from '@angular/router'; | |
33 | +import { | |
34 | + createDeviceProfileConfiguration, | |
35 | + createDeviceProfileTransportConfiguration, | |
36 | + DeviceProfile, | |
37 | + DeviceProfileType, | |
38 | + deviceProfileTypeTranslationMap, | |
39 | + DeviceTransportType, | |
40 | + deviceTransportTypeTranslationMap | |
41 | +} from '@shared/models/device.models'; | |
42 | +import { DeviceProfileService } from '@core/http/device-profile.service'; | |
43 | +import { EntityType } from '@shared/models/entity-type.models'; | |
44 | +import { MatHorizontalStepper } from '@angular/material/stepper'; | |
45 | +import { RuleChainId } from '@shared/models/id/rule-chain-id'; | |
46 | + | |
47 | +export interface AddDeviceProfileDialogData { | |
48 | + deviceProfileName: string; | |
49 | +} | |
50 | + | |
51 | +@Component({ | |
52 | + selector: 'tb-add-device-profile-dialog', | |
53 | + templateUrl: './add-device-profile-dialog.component.html', | |
54 | + providers: [], | |
55 | + styleUrls: ['./add-device-profile-dialog.component.scss'] | |
56 | +}) | |
57 | +export class AddDeviceProfileDialogComponent extends | |
58 | + DialogComponent<AddDeviceProfileDialogComponent, DeviceProfile> implements AfterViewInit { | |
59 | + | |
60 | + @ViewChild('addDeviceProfileStepper', {static: true}) addDeviceProfileStepper: MatHorizontalStepper; | |
61 | + | |
62 | + selectedIndex = 0; | |
63 | + | |
64 | + entityType = EntityType; | |
65 | + | |
66 | + deviceProfileTypes = Object.keys(DeviceProfileType); | |
67 | + | |
68 | + deviceProfileTypeTranslations = deviceProfileTypeTranslationMap; | |
69 | + | |
70 | + deviceTransportTypes = Object.keys(DeviceTransportType); | |
71 | + | |
72 | + deviceTransportTypeTranslations = deviceTransportTypeTranslationMap; | |
73 | + | |
74 | + deviceProfileDetailsFormGroup: FormGroup; | |
75 | + | |
76 | + transportConfigFormGroup: FormGroup; | |
77 | + | |
78 | + alarmRulesFormGroup: FormGroup; | |
79 | + | |
80 | + constructor(protected store: Store<AppState>, | |
81 | + protected router: Router, | |
82 | + @Inject(MAT_DIALOG_DATA) public data: AddDeviceProfileDialogData, | |
83 | + public dialogRef: MatDialogRef<AddDeviceProfileDialogComponent, DeviceProfile>, | |
84 | + private componentFactoryResolver: ComponentFactoryResolver, | |
85 | + private injector: Injector, | |
86 | + @SkipSelf() private errorStateMatcher: ErrorStateMatcher, | |
87 | + private deviceProfileService: DeviceProfileService, | |
88 | + private fb: FormBuilder) { | |
89 | + super(store, router, dialogRef); | |
90 | + this.deviceProfileDetailsFormGroup = this.fb.group( | |
91 | + { | |
92 | + name: [data.deviceProfileName, [Validators.required]], | |
93 | + type: [DeviceProfileType.DEFAULT, [Validators.required]], | |
94 | + defaultRuleChainId: [null, []], | |
95 | + description: ['', []] | |
96 | + } | |
97 | + ); | |
98 | + this.transportConfigFormGroup = this.fb.group( | |
99 | + { | |
100 | + transportType: [DeviceTransportType.DEFAULT, [Validators.required]], | |
101 | + transportConfiguration: [createDeviceProfileTransportConfiguration(DeviceTransportType.DEFAULT), | |
102 | + [Validators.required]] | |
103 | + } | |
104 | + ); | |
105 | + this.transportConfigFormGroup.get('transportType').valueChanges.subscribe(() => { | |
106 | + this.deviceProfileTransportTypeChanged(); | |
107 | + }); | |
108 | + | |
109 | + this.alarmRulesFormGroup = this.fb.group( | |
110 | + { | |
111 | + alarms: [null] | |
112 | + } | |
113 | + ); | |
114 | + } | |
115 | + | |
116 | + private deviceProfileTransportTypeChanged() { | |
117 | + const deviceTransportType: DeviceTransportType = this.transportConfigFormGroup.get('transportType').value; | |
118 | + this.transportConfigFormGroup.patchValue( | |
119 | + {transportConfiguration: createDeviceProfileTransportConfiguration(deviceTransportType)}); | |
120 | + } | |
121 | + | |
122 | + ngAfterViewInit(): void { | |
123 | + } | |
124 | + | |
125 | + cancel(): void { | |
126 | + this.dialogRef.close(null); | |
127 | + } | |
128 | + | |
129 | + previousStep() { | |
130 | + this.addDeviceProfileStepper.previous(); | |
131 | + } | |
132 | + | |
133 | + nextStep() { | |
134 | + if (this.selectedIndex < 2) { | |
135 | + this.addDeviceProfileStepper.next(); | |
136 | + } else { | |
137 | + this.add(); | |
138 | + } | |
139 | + } | |
140 | + | |
141 | + selectedForm(): FormGroup { | |
142 | + switch (this.selectedIndex) { | |
143 | + case 0: | |
144 | + return this.deviceProfileDetailsFormGroup; | |
145 | + case 1: | |
146 | + return this.transportConfigFormGroup; | |
147 | + case 2: | |
148 | + return this.alarmRulesFormGroup; | |
149 | + } | |
150 | + } | |
151 | + | |
152 | + private add(): void { | |
153 | + const deviceProfile: DeviceProfile = { | |
154 | + name: this.deviceProfileDetailsFormGroup.get('name').value, | |
155 | + type: this.deviceProfileDetailsFormGroup.get('type').value, | |
156 | + transportType: this.transportConfigFormGroup.get('transportType').value, | |
157 | + description: this.deviceProfileDetailsFormGroup.get('description').value, | |
158 | + profileData: { | |
159 | + configuration: createDeviceProfileConfiguration(DeviceProfileType.DEFAULT), | |
160 | + transportConfiguration: this.transportConfigFormGroup.get('transportConfiguration').value, | |
161 | + alarms: this.alarmRulesFormGroup.get('alarms').value | |
162 | + } | |
163 | + }; | |
164 | + if (this.deviceProfileDetailsFormGroup.get('defaultRuleChainId').value) { | |
165 | + deviceProfile.defaultRuleChainId = new RuleChainId(this.deviceProfileDetailsFormGroup.get('defaultRuleChainId').value); | |
166 | + } | |
167 | + this.deviceProfileService.saveDeviceProfile(deviceProfile).subscribe( | |
168 | + (savedDeviceProfile) => { | |
169 | + this.dialogRef.close(savedDeviceProfile); | |
170 | + } | |
171 | + ); | |
172 | + } | |
173 | +} | ... | ... |
... | ... | @@ -32,12 +32,12 @@ |
32 | 32 | </mat-slide-toggle> |
33 | 33 | </div> |
34 | 34 | <div class="tb-condition-duration" fxFlex fxLayout="row" fxLayoutGap="8px"> |
35 | - <span style="min-width: 250px;" [fxShow]="!enableDuration"></span> | |
36 | - <div style="min-width: 250px;" fxLayout="row" fxLayoutGap="8px" [fxShow]="enableDuration"> | |
35 | + <span style="min-width: 250px;" *ngIf="!enableDuration"></span> | |
36 | + <div style="min-width: 250px;" fxLayout="row" fxLayoutGap="8px" *ngIf="enableDuration"> | |
37 | 37 | <mat-form-field class="mat-block duration-value-field" hideRequiredMarker floatLabel="always"> |
38 | 38 | <mat-label></mat-label> |
39 | 39 | <input type="number" |
40 | - [required]="enableDuration" | |
40 | + required | |
41 | 41 | step="1" |
42 | 42 | min="1" max="2147483647" matInput |
43 | 43 | placeholder="{{ 'device-profile.condition-duration-value' | translate }}" |
... | ... | @@ -55,7 +55,7 @@ |
55 | 55 | <mat-form-field class="mat-block duration-unit-field" hideRequiredMarker floatLabel="always"> |
56 | 56 | <mat-label></mat-label> |
57 | 57 | <mat-select formControlName="durationUnit" |
58 | - [required]="enableDuration" | |
58 | + required | |
59 | 59 | placeholder="{{ 'device-profile.condition-duration-time-unit' | translate }}"> |
60 | 60 | <mat-option *ngFor="let timeUnit of timeUnits" [value]="timeUnit"> |
61 | 61 | {{ timeUnitTranslations.get(timeUnit) | translate }} | ... | ... |
... | ... | @@ -46,7 +46,7 @@ |
46 | 46 | <mat-icon>remove_circle_outline</mat-icon> |
47 | 47 | </button> |
48 | 48 | </div> |
49 | - <div *ngIf="disabled && !createAlarmRulesFormArray().controls.length"> | |
49 | + <div *ngIf="!createAlarmRulesFormArray().controls.length"> | |
50 | 50 | <span translate fxLayoutAlign="center center" |
51 | 51 | class="tb-prompt">device-profile.no-create-alarm-rules</span> |
52 | 52 | </div> | ... | ... |
... | ... | @@ -124,6 +124,9 @@ export class CreateAlarmRulesComponent implements ControlValueAccessor, OnInit, |
124 | 124 | this.updateModel(); |
125 | 125 | }); |
126 | 126 | this.updateUsedSeverities(); |
127 | + if (!this.disabled && !this.createAlarmRulesFormGroup.valid) { | |
128 | + this.updateModel(); | |
129 | + } | |
127 | 130 | } |
128 | 131 | |
129 | 132 | public removeCreateAlarmRule(index: number) { | ... | ... |
... | ... | @@ -67,7 +67,7 @@ |
67 | 67 | <mat-icon>remove_circle_outline</mat-icon> |
68 | 68 | </button> |
69 | 69 | </div> |
70 | - <div *ngIf="disabled && !alarmFormGroup.get('clearRule').value"> | |
70 | + <div *ngIf="!alarmFormGroup.get('clearRule').value"> | |
71 | 71 | <span translate fxLayoutAlign="center center" |
72 | 72 | class="tb-prompt">device-profile.no-clear-alarm-rule</span> |
73 | 73 | </div> | ... | ... |
... | ... | @@ -63,7 +63,8 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit |
63 | 63 | |
64 | 64 | alarmFormGroup: FormGroup; |
65 | 65 | |
66 | - private propagateChange = (v: any) => { }; | |
66 | + private propagateChange = null; | |
67 | + private propagateChangePending = false; | |
67 | 68 | |
68 | 69 | constructor(private dialog: MatDialog, |
69 | 70 | private fb: FormBuilder) { |
... | ... | @@ -71,6 +72,12 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit |
71 | 72 | |
72 | 73 | registerOnChange(fn: any): void { |
73 | 74 | this.propagateChange = fn; |
75 | + if (this.propagateChangePending) { | |
76 | + this.propagateChangePending = false; | |
77 | + setTimeout(() => { | |
78 | + this.propagateChange(this.modelValue); | |
79 | + }, 0); | |
80 | + } | |
74 | 81 | } |
75 | 82 | |
76 | 83 | registerOnTouched(fn: any): void { |
... | ... | @@ -100,11 +107,15 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit |
100 | 107 | } |
101 | 108 | |
102 | 109 | writeValue(value: DeviceProfileAlarm): void { |
110 | + this.propagateChangePending = false; | |
103 | 111 | this.modelValue = value; |
104 | 112 | if (!this.modelValue.alarmType) { |
105 | 113 | this.expanded = true; |
106 | 114 | } |
107 | 115 | this.alarmFormGroup.reset(this.modelValue || undefined, {emitEvent: false}); |
116 | + if (!this.disabled && !this.alarmFormGroup.valid) { | |
117 | + this.updateModel(); | |
118 | + } | |
108 | 119 | } |
109 | 120 | |
110 | 121 | public addClearAlarmRule() { |
... | ... | @@ -160,6 +171,10 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit |
160 | 171 | private updateModel() { |
161 | 172 | const value = this.alarmFormGroup.value; |
162 | 173 | this.modelValue = {...this.modelValue, ...value}; |
163 | - this.propagateChange(this.modelValue); | |
174 | + if (this.propagateChange) { | |
175 | + this.propagateChange(this.modelValue); | |
176 | + } else { | |
177 | + this.propagateChangePending = true; | |
178 | + } | |
164 | 179 | } |
165 | 180 | } | ... | ... |
... | ... | @@ -25,6 +25,10 @@ |
25 | 25 | </tb-device-profile-alarm> |
26 | 26 | </div> |
27 | 27 | </div> |
28 | + <div *ngIf="!alarmsFormArray().controls.length"> | |
29 | + <span translate fxLayoutAlign="center center" | |
30 | + class="tb-prompt">device-profile.no-alarm-rules</span> | |
31 | + </div> | |
28 | 32 | <div *ngIf="!disabled" fxFlex fxLayout="row" fxLayoutAlign="end center" |
29 | 33 | style="padding-top: 16px;"> |
30 | 34 | <button mat-raised-button color="primary" | ... | ... |
... | ... | @@ -162,11 +162,7 @@ export class DeviceProfileAlarmsComponent implements ControlValueAccessor, OnIni |
162 | 162 | } |
163 | 163 | |
164 | 164 | private updateModel() { |
165 | -// if (this.deviceProfileAlarmsFormGroup.valid) { | |
166 | - const alarms: Array<DeviceProfileAlarm> = this.deviceProfileAlarmsFormGroup.get('alarms').value; | |
167 | - this.propagateChange(alarms); | |
168 | - /* } else { | |
169 | - this.propagateChange(null); | |
170 | - } */ | |
165 | + const alarms: Array<DeviceProfileAlarm> = this.deviceProfileAlarmsFormGroup.get('alarms').value; | |
166 | + this.propagateChange(alarms); | |
171 | 167 | } |
172 | 168 | } | ... | ... |
... | ... | @@ -49,6 +49,7 @@ import { |
49 | 49 | import { DeviceProfileService } from '@core/http/device-profile.service'; |
50 | 50 | import { DeviceProfileDialogComponent, DeviceProfileDialogData } from './device-profile-dialog.component'; |
51 | 51 | import { MatAutocomplete } from '@angular/material/autocomplete'; |
52 | +import { AddDeviceProfileDialogComponent, AddDeviceProfileDialogData } from './add-device-profile-dialog.component'; | |
52 | 53 | |
53 | 54 | @Component({ |
54 | 55 | selector: 'tb-device-profile-autocomplete', |
... | ... | @@ -279,15 +280,8 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, |
279 | 280 | createDeviceProfile($event: Event, profileName: string) { |
280 | 281 | $event.preventDefault(); |
281 | 282 | const deviceProfile: DeviceProfile = { |
282 | - id: null, | |
283 | - name: profileName, | |
284 | - type: DeviceProfileType.DEFAULT, | |
285 | - transportType: DeviceTransportType.DEFAULT, | |
286 | - profileData: { | |
287 | - configuration: createDeviceProfileConfiguration(DeviceProfileType.DEFAULT), | |
288 | - transportConfiguration: createDeviceProfileTransportConfiguration(DeviceTransportType.DEFAULT) | |
289 | - } | |
290 | - }; | |
283 | + name: profileName | |
284 | + } as DeviceProfile; | |
291 | 285 | this.openDeviceProfileDialog(deviceProfile, true); |
292 | 286 | } |
293 | 287 | |
... | ... | @@ -301,15 +295,28 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, |
301 | 295 | } |
302 | 296 | |
303 | 297 | openDeviceProfileDialog(deviceProfile: DeviceProfile, isAdd: boolean) { |
304 | - this.dialog.open<DeviceProfileDialogComponent, DeviceProfileDialogData, | |
305 | - DeviceProfile>(DeviceProfileDialogComponent, { | |
306 | - disableClose: true, | |
307 | - panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], | |
308 | - data: { | |
309 | - isAdd, | |
310 | - deviceProfile | |
311 | - } | |
312 | - }).afterClosed().subscribe( | |
298 | + let deviceProfileObservable: Observable<DeviceProfile>; | |
299 | + if (!isAdd) { | |
300 | + deviceProfileObservable = this.dialog.open<DeviceProfileDialogComponent, DeviceProfileDialogData, | |
301 | + DeviceProfile>(DeviceProfileDialogComponent, { | |
302 | + disableClose: true, | |
303 | + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], | |
304 | + data: { | |
305 | + isAdd: false, | |
306 | + deviceProfile | |
307 | + } | |
308 | + }).afterClosed(); | |
309 | + } else { | |
310 | + deviceProfileObservable = this.dialog.open<AddDeviceProfileDialogComponent, AddDeviceProfileDialogData, | |
311 | + DeviceProfile>(AddDeviceProfileDialogComponent, { | |
312 | + disableClose: true, | |
313 | + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], | |
314 | + data: { | |
315 | + deviceProfileName: deviceProfile.name | |
316 | + } | |
317 | + }).afterClosed(); | |
318 | + } | |
319 | + deviceProfileObservable.subscribe( | |
313 | 320 | (savedDeviceProfile) => { |
314 | 321 | if (!savedDeviceProfile) { |
315 | 322 | setTimeout(() => { | ... | ... |
... | ... | @@ -81,7 +81,7 @@ |
81 | 81 | required> |
82 | 82 | </tb-device-profile-data> |
83 | 83 | <mat-form-field class="mat-block"> |
84 | - <mat-label translate>tenant-profile.description</mat-label> | |
84 | + <mat-label translate>device-profile.description</mat-label> | |
85 | 85 | <textarea matInput formControlName="description" rows="2"></textarea> |
86 | 86 | </mat-form-field> |
87 | 87 | </fieldset> | ... | ... |
... | ... | @@ -35,6 +35,12 @@ import { |
35 | 35 | import { DeviceProfileService } from '@core/http/device-profile.service'; |
36 | 36 | import { DeviceProfileComponent } from '../../components/profile/device-profile.component'; |
37 | 37 | import { DeviceProfileTabsComponent } from './device-profile-tabs.component'; |
38 | +import { Observable } from 'rxjs'; | |
39 | +import { MatDialog } from '@angular/material/dialog'; | |
40 | +import { | |
41 | + AddDeviceProfileDialogComponent, | |
42 | + AddDeviceProfileDialogData | |
43 | +} from '../../components/profile/add-device-profile-dialog.component'; | |
38 | 44 | |
39 | 45 | @Injectable() |
40 | 46 | export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableConfig<DeviceProfile>> { |
... | ... | @@ -44,7 +50,8 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon |
44 | 50 | constructor(private deviceProfileService: DeviceProfileService, |
45 | 51 | private translate: TranslateService, |
46 | 52 | private datePipe: DatePipe, |
47 | - private dialogService: DialogService) { | |
53 | + private dialogService: DialogService, | |
54 | + private dialog: MatDialog) { | |
48 | 55 | |
49 | 56 | this.config.entityType = EntityType.DEVICE_PROFILE; |
50 | 57 | this.config.entityComponent = DeviceProfileComponent; |
... | ... | @@ -92,6 +99,7 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon |
92 | 99 | this.config.onEntityAction = action => this.onDeviceProfileAction(action); |
93 | 100 | this.config.deleteEnabled = (deviceProfile) => deviceProfile && !deviceProfile.default; |
94 | 101 | this.config.entitySelectionEnabled = (deviceProfile) => deviceProfile && !deviceProfile.default; |
102 | + this.config.addEntity = () => this.addDeviceProfile(); | |
95 | 103 | } |
96 | 104 | |
97 | 105 | resolve(): EntityTableConfig<DeviceProfile> { |
... | ... | @@ -100,6 +108,17 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon |
100 | 108 | return this.config; |
101 | 109 | } |
102 | 110 | |
111 | + addDeviceProfile(): Observable<DeviceProfile> { | |
112 | + return this.dialog.open<AddDeviceProfileDialogComponent, AddDeviceProfileDialogData, | |
113 | + DeviceProfile>(AddDeviceProfileDialogComponent, { | |
114 | + disableClose: true, | |
115 | + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], | |
116 | + data: { | |
117 | + deviceProfileName: null | |
118 | + } | |
119 | + }).afterClosed(); | |
120 | + } | |
121 | + | |
103 | 122 | setDefaultDeviceProfile($event: Event, deviceProfile: DeviceProfile) { |
104 | 123 | if ($event) { |
105 | 124 | $event.stopPropagation(); | ... | ... |
... | ... | @@ -811,6 +811,7 @@ |
811 | 811 | "single-level-wildcards-hint": "<code>[+]</code> is suitable for any topic filter level. Ex.: <b>v1/devices/+/telemetry</b> or <b>+/devices/+/attributes</b>.", |
812 | 812 | "multi-level-wildcards-hint": "<code>[#]</code> can replace the topic filter itself and must be the last symbol of the topic. Ex.: <b>#</b> or <b>v1/devices/me/#</b>.", |
813 | 813 | "alarm-rules": "Alarm rules ({{count}})", |
814 | + "no-alarm-rules": "No alarm rules configured", | |
814 | 815 | "add-alarm-rule": "Add alarm rule", |
815 | 816 | "edit-alarm-rule": "Edit alarm rule", |
816 | 817 | "alarm-type": "Alarm type", | ... | ... |