Commit 3dc7fde4dbe67aa39b2a8aca1789e7459c2251d7

Authored by Igor Kulikov
1 parent c4e67215

UI: Add device profile wizard

@@ -105,6 +105,7 @@ import { AlarmRuleComponent } from './profile/alarm/alarm-rule.component'; @@ -105,6 +105,7 @@ import { AlarmRuleComponent } from './profile/alarm/alarm-rule.component';
105 import { AlarmRuleConditionComponent } from './profile/alarm/alarm-rule-condition.component'; 105 import { AlarmRuleConditionComponent } from './profile/alarm/alarm-rule-condition.component';
106 import { AlarmRuleKeyFiltersDialogComponent } from './profile/alarm/alarm-rule-key-filters-dialog.component'; 106 import { AlarmRuleKeyFiltersDialogComponent } from './profile/alarm/alarm-rule-key-filters-dialog.component';
107 import { FilterTextComponent } from './filter/filter-text.component'; 107 import { FilterTextComponent } from './filter/filter-text.component';
  108 +import { AddDeviceProfileDialogComponent } from './profile/add-device-profile-dialog.component';
108 109
109 @NgModule({ 110 @NgModule({
110 declarations: 111 declarations:
@@ -192,7 +193,8 @@ import { FilterTextComponent } from './filter/filter-text.component'; @@ -192,7 +193,8 @@ import { FilterTextComponent } from './filter/filter-text.component';
192 DeviceProfileAlarmsComponent, 193 DeviceProfileAlarmsComponent,
193 DeviceProfileDataComponent, 194 DeviceProfileDataComponent,
194 DeviceProfileComponent, 195 DeviceProfileComponent,
195 - DeviceProfileDialogComponent 196 + DeviceProfileDialogComponent,
  197 + AddDeviceProfileDialogComponent
196 ], 198 ],
197 imports: [ 199 imports: [
198 CommonModule, 200 CommonModule,
@@ -269,7 +271,8 @@ import { FilterTextComponent } from './filter/filter-text.component'; @@ -269,7 +271,8 @@ import { FilterTextComponent } from './filter/filter-text.component';
269 DeviceProfileAlarmsComponent, 271 DeviceProfileAlarmsComponent,
270 DeviceProfileDataComponent, 272 DeviceProfileDataComponent,
271 DeviceProfileComponent, 273 DeviceProfileComponent,
272 - DeviceProfileDialogComponent 274 + DeviceProfileDialogComponent,
  275 + AddDeviceProfileDialogComponent
273 ], 276 ],
274 providers: [ 277 providers: [
275 WidgetComponentService, 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,12 +32,12 @@
32 </mat-slide-toggle> 32 </mat-slide-toggle>
33 </div> 33 </div>
34 <div class="tb-condition-duration" fxFlex fxLayout="row" fxLayoutGap="8px"> 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 <mat-form-field class="mat-block duration-value-field" hideRequiredMarker floatLabel="always"> 37 <mat-form-field class="mat-block duration-value-field" hideRequiredMarker floatLabel="always">
38 <mat-label></mat-label> 38 <mat-label></mat-label>
39 <input type="number" 39 <input type="number"
40 - [required]="enableDuration" 40 + required
41 step="1" 41 step="1"
42 min="1" max="2147483647" matInput 42 min="1" max="2147483647" matInput
43 placeholder="{{ 'device-profile.condition-duration-value' | translate }}" 43 placeholder="{{ 'device-profile.condition-duration-value' | translate }}"
@@ -55,7 +55,7 @@ @@ -55,7 +55,7 @@
55 <mat-form-field class="mat-block duration-unit-field" hideRequiredMarker floatLabel="always"> 55 <mat-form-field class="mat-block duration-unit-field" hideRequiredMarker floatLabel="always">
56 <mat-label></mat-label> 56 <mat-label></mat-label>
57 <mat-select formControlName="durationUnit" 57 <mat-select formControlName="durationUnit"
58 - [required]="enableDuration" 58 + required
59 placeholder="{{ 'device-profile.condition-duration-time-unit' | translate }}"> 59 placeholder="{{ 'device-profile.condition-duration-time-unit' | translate }}">
60 <mat-option *ngFor="let timeUnit of timeUnits" [value]="timeUnit"> 60 <mat-option *ngFor="let timeUnit of timeUnits" [value]="timeUnit">
61 {{ timeUnitTranslations.get(timeUnit) | translate }} 61 {{ timeUnitTranslations.get(timeUnit) | translate }}
@@ -46,7 +46,7 @@ @@ -46,7 +46,7 @@
46 <mat-icon>remove_circle_outline</mat-icon> 46 <mat-icon>remove_circle_outline</mat-icon>
47 </button> 47 </button>
48 </div> 48 </div>
49 - <div *ngIf="disabled && !createAlarmRulesFormArray().controls.length"> 49 + <div *ngIf="!createAlarmRulesFormArray().controls.length">
50 <span translate fxLayoutAlign="center center" 50 <span translate fxLayoutAlign="center center"
51 class="tb-prompt">device-profile.no-create-alarm-rules</span> 51 class="tb-prompt">device-profile.no-create-alarm-rules</span>
52 </div> 52 </div>
@@ -124,6 +124,9 @@ export class CreateAlarmRulesComponent implements ControlValueAccessor, OnInit, @@ -124,6 +124,9 @@ export class CreateAlarmRulesComponent implements ControlValueAccessor, OnInit,
124 this.updateModel(); 124 this.updateModel();
125 }); 125 });
126 this.updateUsedSeverities(); 126 this.updateUsedSeverities();
  127 + if (!this.disabled && !this.createAlarmRulesFormGroup.valid) {
  128 + this.updateModel();
  129 + }
127 } 130 }
128 131
129 public removeCreateAlarmRule(index: number) { 132 public removeCreateAlarmRule(index: number) {
@@ -67,7 +67,7 @@ @@ -67,7 +67,7 @@
67 <mat-icon>remove_circle_outline</mat-icon> 67 <mat-icon>remove_circle_outline</mat-icon>
68 </button> 68 </button>
69 </div> 69 </div>
70 - <div *ngIf="disabled && !alarmFormGroup.get('clearRule').value"> 70 + <div *ngIf="!alarmFormGroup.get('clearRule').value">
71 <span translate fxLayoutAlign="center center" 71 <span translate fxLayoutAlign="center center"
72 class="tb-prompt">device-profile.no-clear-alarm-rule</span> 72 class="tb-prompt">device-profile.no-clear-alarm-rule</span>
73 </div> 73 </div>
@@ -63,7 +63,8 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit @@ -63,7 +63,8 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit
63 63
64 alarmFormGroup: FormGroup; 64 alarmFormGroup: FormGroup;
65 65
66 - private propagateChange = (v: any) => { }; 66 + private propagateChange = null;
  67 + private propagateChangePending = false;
67 68
68 constructor(private dialog: MatDialog, 69 constructor(private dialog: MatDialog,
69 private fb: FormBuilder) { 70 private fb: FormBuilder) {
@@ -71,6 +72,12 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit @@ -71,6 +72,12 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit
71 72
72 registerOnChange(fn: any): void { 73 registerOnChange(fn: any): void {
73 this.propagateChange = fn; 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 registerOnTouched(fn: any): void { 83 registerOnTouched(fn: any): void {
@@ -100,11 +107,15 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit @@ -100,11 +107,15 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit
100 } 107 }
101 108
102 writeValue(value: DeviceProfileAlarm): void { 109 writeValue(value: DeviceProfileAlarm): void {
  110 + this.propagateChangePending = false;
103 this.modelValue = value; 111 this.modelValue = value;
104 if (!this.modelValue.alarmType) { 112 if (!this.modelValue.alarmType) {
105 this.expanded = true; 113 this.expanded = true;
106 } 114 }
107 this.alarmFormGroup.reset(this.modelValue || undefined, {emitEvent: false}); 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 public addClearAlarmRule() { 121 public addClearAlarmRule() {
@@ -160,6 +171,10 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit @@ -160,6 +171,10 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit
160 private updateModel() { 171 private updateModel() {
161 const value = this.alarmFormGroup.value; 172 const value = this.alarmFormGroup.value;
162 this.modelValue = {...this.modelValue, ...value}; 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,6 +25,10 @@
25 </tb-device-profile-alarm> 25 </tb-device-profile-alarm>
26 </div> 26 </div>
27 </div> 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 <div *ngIf="!disabled" fxFlex fxLayout="row" fxLayoutAlign="end center" 32 <div *ngIf="!disabled" fxFlex fxLayout="row" fxLayoutAlign="end center"
29 style="padding-top: 16px;"> 33 style="padding-top: 16px;">
30 <button mat-raised-button color="primary" 34 <button mat-raised-button color="primary"
@@ -162,11 +162,7 @@ export class DeviceProfileAlarmsComponent implements ControlValueAccessor, OnIni @@ -162,11 +162,7 @@ export class DeviceProfileAlarmsComponent implements ControlValueAccessor, OnIni
162 } 162 }
163 163
164 private updateModel() { 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,6 +49,7 @@ import {
49 import { DeviceProfileService } from '@core/http/device-profile.service'; 49 import { DeviceProfileService } from '@core/http/device-profile.service';
50 import { DeviceProfileDialogComponent, DeviceProfileDialogData } from './device-profile-dialog.component'; 50 import { DeviceProfileDialogComponent, DeviceProfileDialogData } from './device-profile-dialog.component';
51 import { MatAutocomplete } from '@angular/material/autocomplete'; 51 import { MatAutocomplete } from '@angular/material/autocomplete';
  52 +import { AddDeviceProfileDialogComponent, AddDeviceProfileDialogData } from './add-device-profile-dialog.component';
52 53
53 @Component({ 54 @Component({
54 selector: 'tb-device-profile-autocomplete', 55 selector: 'tb-device-profile-autocomplete',
@@ -279,15 +280,8 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, @@ -279,15 +280,8 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor,
279 createDeviceProfile($event: Event, profileName: string) { 280 createDeviceProfile($event: Event, profileName: string) {
280 $event.preventDefault(); 281 $event.preventDefault();
281 const deviceProfile: DeviceProfile = { 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 this.openDeviceProfileDialog(deviceProfile, true); 285 this.openDeviceProfileDialog(deviceProfile, true);
292 } 286 }
293 287
@@ -301,15 +295,28 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, @@ -301,15 +295,28 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor,
301 } 295 }
302 296
303 openDeviceProfileDialog(deviceProfile: DeviceProfile, isAdd: boolean) { 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 (savedDeviceProfile) => { 320 (savedDeviceProfile) => {
314 if (!savedDeviceProfile) { 321 if (!savedDeviceProfile) {
315 setTimeout(() => { 322 setTimeout(() => {
@@ -81,7 +81,7 @@ @@ -81,7 +81,7 @@
81 required> 81 required>
82 </tb-device-profile-data> 82 </tb-device-profile-data>
83 <mat-form-field class="mat-block"> 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 <textarea matInput formControlName="description" rows="2"></textarea> 85 <textarea matInput formControlName="description" rows="2"></textarea>
86 </mat-form-field> 86 </mat-form-field>
87 </fieldset> 87 </fieldset>
@@ -35,6 +35,12 @@ import { @@ -35,6 +35,12 @@ import {
35 import { DeviceProfileService } from '@core/http/device-profile.service'; 35 import { DeviceProfileService } from '@core/http/device-profile.service';
36 import { DeviceProfileComponent } from '../../components/profile/device-profile.component'; 36 import { DeviceProfileComponent } from '../../components/profile/device-profile.component';
37 import { DeviceProfileTabsComponent } from './device-profile-tabs.component'; 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 @Injectable() 45 @Injectable()
40 export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableConfig<DeviceProfile>> { 46 export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableConfig<DeviceProfile>> {
@@ -44,7 +50,8 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon @@ -44,7 +50,8 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon
44 constructor(private deviceProfileService: DeviceProfileService, 50 constructor(private deviceProfileService: DeviceProfileService,
45 private translate: TranslateService, 51 private translate: TranslateService,
46 private datePipe: DatePipe, 52 private datePipe: DatePipe,
47 - private dialogService: DialogService) { 53 + private dialogService: DialogService,
  54 + private dialog: MatDialog) {
48 55
49 this.config.entityType = EntityType.DEVICE_PROFILE; 56 this.config.entityType = EntityType.DEVICE_PROFILE;
50 this.config.entityComponent = DeviceProfileComponent; 57 this.config.entityComponent = DeviceProfileComponent;
@@ -92,6 +99,7 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon @@ -92,6 +99,7 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon
92 this.config.onEntityAction = action => this.onDeviceProfileAction(action); 99 this.config.onEntityAction = action => this.onDeviceProfileAction(action);
93 this.config.deleteEnabled = (deviceProfile) => deviceProfile && !deviceProfile.default; 100 this.config.deleteEnabled = (deviceProfile) => deviceProfile && !deviceProfile.default;
94 this.config.entitySelectionEnabled = (deviceProfile) => deviceProfile && !deviceProfile.default; 101 this.config.entitySelectionEnabled = (deviceProfile) => deviceProfile && !deviceProfile.default;
  102 + this.config.addEntity = () => this.addDeviceProfile();
95 } 103 }
96 104
97 resolve(): EntityTableConfig<DeviceProfile> { 105 resolve(): EntityTableConfig<DeviceProfile> {
@@ -100,6 +108,17 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon @@ -100,6 +108,17 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon
100 return this.config; 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 setDefaultDeviceProfile($event: Event, deviceProfile: DeviceProfile) { 122 setDefaultDeviceProfile($event: Event, deviceProfile: DeviceProfile) {
104 if ($event) { 123 if ($event) {
105 $event.stopPropagation(); 124 $event.stopPropagation();
@@ -811,6 +811,7 @@ @@ -811,6 +811,7 @@
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>.", 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 "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>.", 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 "alarm-rules": "Alarm rules ({{count}})", 813 "alarm-rules": "Alarm rules ({{count}})",
  814 + "no-alarm-rules": "No alarm rules configured",
814 "add-alarm-rule": "Add alarm rule", 815 "add-alarm-rule": "Add alarm rule",
815 "edit-alarm-rule": "Edit alarm rule", 816 "edit-alarm-rule": "Edit alarm rule",
816 "alarm-type": "Alarm type", 817 "alarm-type": "Alarm type",