Commit c7b57ca543f36e4691efae2e01d8e262afc04426

Authored by Igor Kulikov
1 parent e1069608

Add Device Wizard improvements

@@ -85,7 +85,7 @@ @@ -85,7 +85,7 @@
85 </mat-step> 85 </mat-step>
86 <mat-step [stepControl]="alarmRulesFormGroup"> 86 <mat-step [stepControl]="alarmRulesFormGroup">
87 <form [formGroup]="alarmRulesFormGroup" style="padding-bottom: 16px;"> 87 <form [formGroup]="alarmRulesFormGroup" style="padding-bottom: 16px;">
88 - <ng-template matStepLabel>{{'device-profile.alarm-rules' | translate: 88 + <ng-template matStepLabel>{{'device-profile.alarm-rules-with-count' | translate:
89 {count: alarmRulesFormGroup.get('alarms').value ? 89 {count: alarmRulesFormGroup.get('alarms').value ?
90 alarmRulesFormGroup.get('alarms').value.length : 0} }}</ng-template> 90 alarmRulesFormGroup.get('alarms').value.length : 0} }}</ng-template>
91 <tb-device-profile-alarms 91 <tb-device-profile-alarms
@@ -20,9 +20,9 @@ import { @@ -20,9 +20,9 @@ import {
20 EventEmitter, 20 EventEmitter,
21 forwardRef, 21 forwardRef,
22 Input, 22 Input,
23 - NgZone, 23 + NgZone, OnChanges,
24 OnInit, 24 OnInit,
25 - Output, 25 + Output, SimpleChanges,
26 ViewChild 26 ViewChild
27 } from '@angular/core'; 27 } from '@angular/core';
28 import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; 28 import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
@@ -55,7 +55,7 @@ import { AddDeviceProfileDialogComponent, AddDeviceProfileDialogData } from './a @@ -55,7 +55,7 @@ import { AddDeviceProfileDialogComponent, AddDeviceProfileDialogData } from './a
55 multi: true 55 multi: true
56 }] 56 }]
57 }) 57 })
58 -export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, OnInit { 58 +export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, OnInit, OnChanges {
59 59
60 selectDeviceProfileFormGroup: FormGroup; 60 selectDeviceProfileFormGroup: FormGroup;
61 61
@@ -168,11 +168,22 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, @@ -168,11 +168,22 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor,
168 ); 168 );
169 } 169 }
170 170
  171 + ngOnChanges(changes: SimpleChanges): void {
  172 + for (const propName of Object.keys(changes)) {
  173 + const change = changes[propName];
  174 + if (!change.firstChange && change.currentValue !== change.previousValue) {
  175 + if (propName === 'transportType') {
  176 + this.writeValue(null);
  177 + }
  178 + }
  179 + }
  180 + }
  181 +
171 selectDefaultDeviceProfileIfNeeded(): void { 182 selectDefaultDeviceProfileIfNeeded(): void {
172 if (this.selectDefaultProfile && !this.modelValue) { 183 if (this.selectDefaultProfile && !this.modelValue) {
173 this.deviceProfileService.getDefaultDeviceProfileInfo().subscribe( 184 this.deviceProfileService.getDefaultDeviceProfileInfo().subscribe(
174 (profile) => { 185 (profile) => {
175 - if (profile) { 186 + if (profile && !this.transportType || (profile.transportType === this.transportType)) {
176 this.selectDeviceProfileFormGroup.get('deviceProfile').patchValue(profile, {emitEvent: false}); 187 this.selectDeviceProfileFormGroup.get('deviceProfile').patchValue(profile, {emitEvent: false});
177 this.updateView(profile); 188 this.updateView(profile);
178 } 189 }
@@ -42,7 +42,7 @@ @@ -42,7 +42,7 @@
42 <mat-expansion-panel [expanded]="true"> 42 <mat-expansion-panel [expanded]="true">
43 <mat-expansion-panel-header> 43 <mat-expansion-panel-header>
44 <mat-panel-title> 44 <mat-panel-title>
45 - <div>{{'device-profile.alarm-rules' | translate: 45 + <div>{{'device-profile.alarm-rules-with-count' | translate:
46 {count: deviceProfileDataFormGroup.get('alarms').value ? 46 {count: deviceProfileDataFormGroup.get('alarms').value ?
47 deviceProfileDataFormGroup.get('alarms').value.length : 0} }}</div> 47 deviceProfileDataFormGroup.get('alarms').value.length : 0} }}</div>
48 </mat-panel-title> 48 </mat-panel-title>
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 limitations under the License. 15 limitations under the License.
16 16
17 --> 17 -->
18 -<div style="min-width: 1000px;"> 18 +<div>
19 <mat-toolbar color="primary"> 19 <mat-toolbar color="primary">
20 <h2 translate>device.add-device-text</h2> 20 <h2 translate>device.add-device-text</h2>
21 <span fxFlex></span> 21 <span fxFlex></span>
@@ -29,7 +29,7 @@ @@ -29,7 +29,7 @@
29 </mat-progress-bar> 29 </mat-progress-bar>
30 <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> 30 <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
31 <div mat-dialog-content> 31 <div mat-dialog-content>
32 - <mat-horizontal-stepper [linear]="true" #addDeviceWizardStepper (selectionChange)="changeStep($event)"> 32 + <mat-horizontal-stepper [linear]="true" [labelPosition]="labelPosition" #addDeviceWizardStepper (selectionChange)="changeStep($event)">
33 <ng-template matStepperIcon="edit"> 33 <ng-template matStepperIcon="edit">
34 <mat-icon>check</mat-icon> 34 <mat-icon>check</mat-icon>
35 </ng-template> 35 </ng-template>
@@ -48,60 +48,60 @@ @@ -48,60 +48,60 @@
48 <mat-label translate>device.label</mat-label> 48 <mat-label translate>device.label</mat-label>
49 <input matInput formControlName="label"> 49 <input matInput formControlName="label">
50 </mat-form-field> 50 </mat-form-field>
51 - <mat-form-field class="mat-block"> 51 + <mat-form-field class="mat-block" style="padding-bottom: 14px;">
52 <mat-label translate>device-profile.transport-type</mat-label> 52 <mat-label translate>device-profile.transport-type</mat-label>
53 <mat-select formControlName="transportType" required> 53 <mat-select formControlName="transportType" required>
54 <mat-option *ngFor="let type of deviceTransportTypes" [value]="type"> 54 <mat-option *ngFor="let type of deviceTransportTypes" [value]="type">
55 {{deviceTransportTypeTranslations.get(type) | translate}} 55 {{deviceTransportTypeTranslations.get(type) | translate}}
56 </mat-option> 56 </mat-option>
57 </mat-select> 57 </mat-select>
  58 + <mat-hint *ngIf="deviceWizardFormGroup.get('transportType').value">
  59 + {{deviceTransportTypeHints.get(deviceWizardFormGroup.get('transportType').value) | translate}}
  60 + </mat-hint>
58 <mat-error *ngIf="deviceWizardFormGroup.get('transportType').hasError('required')"> 61 <mat-error *ngIf="deviceWizardFormGroup.get('transportType').hasError('required')">
59 {{ 'device-profile.transport-type-required' | translate }} 62 {{ 'device-profile.transport-type-required' | translate }}
60 </mat-error> 63 </mat-error>
61 </mat-form-field> 64 </mat-form-field>
62 - <mat-checkbox formControlName="gateway" style="padding-bottom: 16px;">  
63 - {{ 'device.is-gateway' | translate }}  
64 - </mat-checkbox>  
65 - <mat-form-field class="mat-block">  
66 - <mat-label translate>device.description</mat-label>  
67 - <textarea matInput formControlName="description" rows="2"></textarea>  
68 - </mat-form-field>  
69 - </fieldset>  
70 - </form>  
71 - </mat-step>  
72 - <mat-step [stepControl]="profileConfigFormGroup">  
73 - <form [formGroup]="profileConfigFormGroup" style="padding-bottom: 16px;">  
74 - <ng-template matStepLabel>{{ 'device.wizard.profile-configuration' | translate}}</ng-template>  
75 - <mat-radio-group fxLayout="column" fxFlex formControlName="addProfileType">  
76 - <mat-radio-button [value]="0" color="primary">  
77 - <section>  
78 - <span translate>device.wizard.existing-device-profile</span> 65 + <div fxLayout="row" fxLayoutGap="16px">
  66 + <mat-radio-group fxLayout="column" formControlName="addProfileType" fxLayoutAlign="space-around">
  67 + <mat-radio-button [value]="0" color="primary">
  68 + <span translate>device.wizard.existing-device-profile</span>
  69 + </mat-radio-button>
  70 + <mat-radio-button [value]="1" color="primary">
  71 + <span translate>device.wizard.new-device-profile</span>
  72 + </mat-radio-button>
  73 + </mat-radio-group>
  74 + <div fxLayout="column">
79 <tb-device-profile-autocomplete 75 <tb-device-profile-autocomplete
80 - [required]="profileConfigFormGroup.get('addProfileType').value === 0" 76 + [required]="!createProfile"
81 [transportType]="deviceWizardFormGroup.get('transportType').value" 77 [transportType]="deviceWizardFormGroup.get('transportType').value"
82 formControlName="deviceProfileId" 78 formControlName="deviceProfileId"
  79 + (deviceProfileChanged)="$event?.transportType ? deviceWizardFormGroup.get('transportType').patchValue($event?.transportType) : {}"
83 [addNewProfile]="false" 80 [addNewProfile]="false"
  81 + [selectDefaultProfile]="true"
84 [editProfileEnabled]="false"> 82 [editProfileEnabled]="false">
85 </tb-device-profile-autocomplete> 83 </tb-device-profile-autocomplete>
86 - </section>  
87 - </mat-radio-button>  
88 - <mat-radio-button [value]="1" color="primary">  
89 - <section fxLayout="column">  
90 - <span translate>device.wizard.new-device-profile</span>  
91 <mat-form-field fxFlex class="mat-block"> 84 <mat-form-field fxFlex class="mat-block">
92 - <mat-label translate>device-profile.device-profile</mat-label> 85 + <mat-label translate>device-profile.new-device-profile-name</mat-label>
93 <input matInput formControlName="newDeviceProfileTitle" 86 <input matInput formControlName="newDeviceProfileTitle"
94 - [required]="profileConfigFormGroup.get('addProfileType').value === 1">  
95 - <mat-error *ngIf="profileConfigFormGroup.get('newDeviceProfileTitle').hasError('required')">  
96 - {{ 'device-profile.device-profile-required' | translate }} 87 + [required]="createProfile">
  88 + <mat-error *ngIf="deviceWizardFormGroup.get('newDeviceProfileTitle').hasError('required')">
  89 + {{ 'device-profile.new-device-profile-name-required' | translate }}
97 </mat-error> 90 </mat-error>
98 </mat-form-field> 91 </mat-form-field>
99 - </section>  
100 - </mat-radio-button>  
101 - </mat-radio-group> 92 + </div>
  93 + </div>
  94 + <mat-checkbox formControlName="gateway" style="padding-bottom: 16px;">
  95 + {{ 'device.is-gateway' | translate }}
  96 + </mat-checkbox>
  97 + <mat-form-field class="mat-block">
  98 + <mat-label translate>device.description</mat-label>
  99 + <textarea matInput formControlName="description" rows="2"></textarea>
  100 + </mat-form-field>
  101 + </fieldset>
102 </form> 102 </form>
103 </mat-step> 103 </mat-step>
104 - <mat-step [stepControl]="transportConfigFormGroup" *ngIf="createdProfile"> 104 + <mat-step [stepControl]="transportConfigFormGroup" *ngIf="createTransportConfiguration">
105 <form [formGroup]="transportConfigFormGroup" style="padding-bottom: 16px;"> 105 <form [formGroup]="transportConfigFormGroup" style="padding-bottom: 16px;">
106 <ng-template matStepLabel>{{ 'device-profile.transport-configuration' | translate }}</ng-template> 106 <ng-template matStepLabel>{{ 'device-profile.transport-configuration' | translate }}</ng-template>
107 <tb-device-profile-transport-configuration 107 <tb-device-profile-transport-configuration
@@ -110,9 +110,9 @@ @@ -110,9 +110,9 @@
110 </tb-device-profile-transport-configuration> 110 </tb-device-profile-transport-configuration>
111 </form> 111 </form>
112 </mat-step> 112 </mat-step>
113 - <mat-step [stepControl]="alarmRulesFormGroup" *ngIf="createdProfile"> 113 + <mat-step [stepControl]="alarmRulesFormGroup" [optional]="true" *ngIf="createProfile">
114 <form [formGroup]="alarmRulesFormGroup" style="padding-bottom: 16px;"> 114 <form [formGroup]="alarmRulesFormGroup" style="padding-bottom: 16px;">
115 - <ng-template matStepLabel>{{'device-profile.alarm-rules' | translate: 115 + <ng-template matStepLabel>{{'device-profile.alarm-rules-with-count' | translate:
116 {count: alarmRulesFormGroup.get('alarms').value ? 116 {count: alarmRulesFormGroup.get('alarms').value ?
117 alarmRulesFormGroup.get('alarms').value.length : 0} }}</ng-template> 117 alarmRulesFormGroup.get('alarms').value.length : 0} }}</ng-template>
118 <tb-device-profile-alarms 118 <tb-device-profile-alarms
@@ -120,36 +120,50 @@ @@ -120,36 +120,50 @@
120 </tb-device-profile-alarms> 120 </tb-device-profile-alarms>
121 </form> 121 </form>
122 </mat-step> 122 </mat-step>
123 - <mat-step [stepControl]="specificConfigFormGroup">  
124 - <ng-template matStepLabel>{{ 'device.wizard.specific-configuration' | translate }}</ng-template>  
125 - <form [formGroup]="specificConfigFormGroup" style="padding-bottom: 16px;"> 123 + <mat-step [stepControl]="credentialsFormGroup" [optional]="true">
  124 + <ng-template matStepLabel>{{ 'device.credentials' | translate }}</ng-template>
  125 + <form [formGroup]="credentialsFormGroup" style="padding-bottom: 16px;">
  126 + <mat-checkbox style="padding-bottom: 16px;" formControlName="setCredential">{{ 'device.wizard.add-credential' | translate }}</mat-checkbox>
  127 + <tb-device-credentials
  128 + [fxShow]="credentialsFormGroup.get('setCredential').value"
  129 + formControlName="credential">
  130 + </tb-device-credentials>
  131 + </form>
  132 + </mat-step>
  133 + <mat-step [stepControl]="customerFormGroup" [optional]="true">
  134 + <ng-template matStepLabel>{{ 'customer.customer' | translate }}</ng-template>
  135 + <form [formGroup]="customerFormGroup" style="padding-bottom: 16px;">
126 <tb-entity-autocomplete 136 <tb-entity-autocomplete
127 formControlName="customerId" 137 formControlName="customerId"
128 labelText="device.wizard.customer-to-assign-device" 138 labelText="device.wizard.customer-to-assign-device"
129 [entityType]="entityType.CUSTOMER"> 139 [entityType]="entityType.CUSTOMER">
130 </tb-entity-autocomplete> 140 </tb-entity-autocomplete>
131 - <mat-checkbox formControlName="setCredential">{{ 'device.wizard.add-credential' | translate }}</mat-checkbox>  
132 - <tb-device-credentials  
133 - [fxShow]="specificConfigFormGroup.get('setCredential').value"  
134 - formControlName="credential">  
135 - </tb-device-credentials>  
136 </form> 141 </form>
137 </mat-step> 142 </mat-step>
138 </mat-horizontal-stepper> 143 </mat-horizontal-stepper>
139 </div> 144 </div>
140 - <div mat-dialog-actions fxLayout="row wrap" fxLayoutAlign="space-between center">  
141 - <button mat-button *ngIf="selectedIndex > 0"  
142 - [disabled]="(isLoading$ | async)"  
143 - (click)="previousStep()">{{ 'action.back' | translate }}</button>  
144 - <span *ngIf="selectedIndex == 0"></span>  
145 - <div fxLayout="row wrap" fxLayoutGap="20px"> 145 + <div mat-dialog-actions fxLayout="column" fxLayoutAlign="start wrap" fxLayoutGap="8px" style="height: 100px;">
  146 + <div fxFlex fxLayout="row" fxLayoutAlign="end">
  147 + <button mat-raised-button
  148 + *ngIf="showNext"
  149 + [disabled]="(isLoading$ | async)"
  150 + (click)="nextStep()">{{ 'action.next-with-label' | translate:{label: (getFormLabel(this.selectedIndex+1) | translate)} }}</button>
  151 + </div>
  152 + <div fxFlex fxLayout="row">
146 <button mat-button 153 <button mat-button
  154 + color="primary"
147 [disabled]="(isLoading$ | async)" 155 [disabled]="(isLoading$ | async)"
148 (click)="cancel()">{{ 'action.cancel' | translate }}</button> 156 (click)="cancel()">{{ 'action.cancel' | translate }}</button>
149 - <button mat-raised-button  
150 - [disabled]="(isLoading$ | async) || selectedForm.invalid"  
151 - color="primary"  
152 - (click)="nextStep()">{{ nextStepButtonLabel$ | async | translate }}</button> 157 + <span fxFlex></span>
  158 + <div fxLayout="row wrap" fxLayoutGap="8px">
  159 + <button mat-raised-button *ngIf="selectedIndex > 0"
  160 + [disabled]="(isLoading$ | async)"
  161 + (click)="previousStep()">{{ 'action.back' | translate }}</button>
  162 + <button mat-raised-button
  163 + [disabled]="(isLoading$ | async)"
  164 + color="primary"
  165 + (click)="add()">{{ 'action.add' | translate }}</button>
  166 + </div>
153 </div> 167 </div>
154 </div> 168 </div>
155 </div> 169 </div>
@@ -13,25 +13,51 @@ @@ -13,25 +13,51 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 -:host {  
17 - .mat-dialog-content {  
18 - display: flex;  
19 - flex-direction: column;  
20 - overflow: hidden;  
21 16
22 - .mat-stepper-horizontal {  
23 - display: flex;  
24 - flex-direction: column;  
25 - overflow: hidden; 17 +@import "../../../../../scss/constants";
  18 +
  19 +:host-context(.tb-fullscreen-dialog .mat-dialog-container) {
  20 + @media #{$mat-lt-sm} {
  21 + .mat-dialog-content {
  22 + max-height: 75vh;
26 } 23 }
27 } 24 }
28 } 25 }
29 26
30 :host ::ng-deep { 27 :host ::ng-deep {
31 .mat-dialog-content { 28 .mat-dialog-content {
  29 + display: flex;
  30 + flex-direction: column;
  31 + height: 100%;
  32 +
32 .mat-stepper-horizontal { 33 .mat-stepper-horizontal {
  34 + display: flex;
  35 + flex-direction: column;
  36 + height: 100%;
  37 + overflow: hidden;
  38 + @media #{$mat-lt-sm} {
  39 + .mat-step-label {
  40 + white-space: normal;
  41 + overflow: visible;
  42 + .mat-step-text-label {
  43 + overflow: visible;
  44 + }
  45 + }
  46 + }
33 .mat-horizontal-content-container { 47 .mat-horizontal-content-container {
34 - overflow: auto; 48 + height: 450px;
  49 + max-height: 100%;
  50 + width: 100%;;
  51 + overflow-y: auto;
  52 + @media #{$mat-gt-sm} {
  53 + min-width: 800px;
  54 + }
  55 + }
  56 + .mat-horizontal-stepper-content[aria-expanded=true] {
  57 + height: 100%;
  58 + form {
  59 + height: 100%;
  60 + }
35 } 61 }
36 } 62 }
37 } 63 }
@@ -26,7 +26,7 @@ import { @@ -26,7 +26,7 @@ import {
26 createDeviceProfileTransportConfiguration, 26 createDeviceProfileTransportConfiguration,
27 DeviceProfile, 27 DeviceProfile,
28 DeviceProfileType, 28 DeviceProfileType,
29 - DeviceTransportType, 29 + DeviceTransportType, deviceTransportTypeHintMap,
30 deviceTransportTypeTranslationMap 30 deviceTransportTypeTranslationMap
31 } from '@shared/models/device.models'; 31 } from '@shared/models/device.models';
32 import { MatHorizontalStepper } from '@angular/material/stepper'; 32 import { MatHorizontalStepper } from '@angular/material/stepper';
@@ -35,11 +35,13 @@ import { BaseData, HasId } from '@shared/models/base-data'; @@ -35,11 +35,13 @@ import { BaseData, HasId } from '@shared/models/base-data';
35 import { EntityType } from '@shared/models/entity-type.models'; 35 import { EntityType } from '@shared/models/entity-type.models';
36 import { DeviceProfileService } from '@core/http/device-profile.service'; 36 import { DeviceProfileService } from '@core/http/device-profile.service';
37 import { EntityId } from '@shared/models/id/entity-id'; 37 import { EntityId } from '@shared/models/id/entity-id';
38 -import { BehaviorSubject, Observable, of, Subscription } from 'rxjs'; 38 +import { Observable, of, Subscription } from 'rxjs';
39 import { map, mergeMap, tap } from 'rxjs/operators'; 39 import { map, mergeMap, tap } from 'rxjs/operators';
40 import { DeviceService } from '@core/http/device.service'; 40 import { DeviceService } from '@core/http/device.service';
41 import { ErrorStateMatcher } from '@angular/material/core'; 41 import { ErrorStateMatcher } from '@angular/material/core';
42 import { StepperSelectionEvent } from '@angular/cdk/stepper'; 42 import { StepperSelectionEvent } from '@angular/cdk/stepper';
  43 +import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout';
  44 +import { MediaBreakpoints } from '@shared/models/constants';
43 45
44 @Component({ 46 @Component({
45 selector: 'tb-device-wizard', 47 selector: 'tb-device-wizard',
@@ -54,9 +56,10 @@ export class DeviceWizardDialogComponent extends @@ -54,9 +56,10 @@ export class DeviceWizardDialogComponent extends
54 56
55 selectedIndex = 0; 57 selectedIndex = 0;
56 58
57 - nextStepButtonLabel$ = new BehaviorSubject<string>('action.continue'); 59 + showNext = true;
58 60
59 - createdProfile = false; 61 + createProfile = false;
  62 + createTransportConfiguration = false;
60 63
61 entityType = EntityType; 64 entityType = EntityType;
62 65
@@ -64,15 +67,19 @@ export class DeviceWizardDialogComponent extends @@ -64,15 +67,19 @@ export class DeviceWizardDialogComponent extends
64 67
65 deviceTransportTypeTranslations = deviceTransportTypeTranslationMap; 68 deviceTransportTypeTranslations = deviceTransportTypeTranslationMap;
66 69
67 - deviceWizardFormGroup: FormGroup; 70 + deviceTransportTypeHints = deviceTransportTypeHintMap;
68 71
69 - profileConfigFormGroup: FormGroup; 72 + deviceWizardFormGroup: FormGroup;
70 73
71 transportConfigFormGroup: FormGroup; 74 transportConfigFormGroup: FormGroup;
72 75
73 alarmRulesFormGroup: FormGroup; 76 alarmRulesFormGroup: FormGroup;
74 77
75 - specificConfigFormGroup: FormGroup; 78 + credentialsFormGroup: FormGroup;
  79 +
  80 + customerFormGroup: FormGroup;
  81 +
  82 + labelPosition = 'end';
76 83
77 private subscriptions: Subscription[] = []; 84 private subscriptions: Subscription[] = [];
78 85
@@ -83,6 +90,7 @@ export class DeviceWizardDialogComponent extends @@ -83,6 +90,7 @@ export class DeviceWizardDialogComponent extends
83 public dialogRef: MatDialogRef<DeviceWizardDialogComponent, boolean>, 90 public dialogRef: MatDialogRef<DeviceWizardDialogComponent, boolean>,
84 private deviceProfileService: DeviceProfileService, 91 private deviceProfileService: DeviceProfileService,
85 private deviceService: DeviceService, 92 private deviceService: DeviceService,
  93 + private breakpointObserver: BreakpointObserver,
86 private fb: FormBuilder) { 94 private fb: FormBuilder) {
87 super(store, router, dialogRef); 95 super(store, router, dialogRef);
88 this.deviceWizardFormGroup = this.fb.group({ 96 this.deviceWizardFormGroup = this.fb.group({
@@ -90,33 +98,32 @@ export class DeviceWizardDialogComponent extends @@ -90,33 +98,32 @@ export class DeviceWizardDialogComponent extends
90 label: [''], 98 label: [''],
91 gateway: [false], 99 gateway: [false],
92 transportType: [DeviceTransportType.DEFAULT, Validators.required], 100 transportType: [DeviceTransportType.DEFAULT, Validators.required],
93 - description: ['']  
94 - }  
95 - );  
96 -  
97 - this.profileConfigFormGroup = this.fb.group({  
98 addProfileType: [0], 101 addProfileType: [0],
99 deviceProfileId: [null, Validators.required], 102 deviceProfileId: [null, Validators.required],
100 - newDeviceProfileTitle: [{value: null, disabled: true}] 103 + newDeviceProfileTitle: [{value: null, disabled: true}],
  104 + description: ['']
101 } 105 }
102 ); 106 );
103 107
104 - this.subscriptions.push(this.profileConfigFormGroup.get('addProfileType').valueChanges.subscribe( 108 + this.subscriptions.push(this.deviceWizardFormGroup.get('addProfileType').valueChanges.subscribe(
105 (addProfileType: number) => { 109 (addProfileType: number) => {
106 if (addProfileType === 0) { 110 if (addProfileType === 0) {
107 - this.profileConfigFormGroup.get('deviceProfileId').setValidators([Validators.required]);  
108 - this.profileConfigFormGroup.get('deviceProfileId').enable();  
109 - this.profileConfigFormGroup.get('newDeviceProfileTitle').setValidators(null);  
110 - this.profileConfigFormGroup.get('newDeviceProfileTitle').disable();  
111 - this.profileConfigFormGroup.updateValueAndValidity();  
112 - this.createdProfile = false; 111 + this.deviceWizardFormGroup.get('deviceProfileId').setValidators([Validators.required]);
  112 + this.deviceWizardFormGroup.get('deviceProfileId').enable();
  113 + this.deviceWizardFormGroup.get('newDeviceProfileTitle').setValidators(null);
  114 + this.deviceWizardFormGroup.get('newDeviceProfileTitle').disable();
  115 + this.deviceWizardFormGroup.updateValueAndValidity();
  116 + this.createProfile = false;
  117 + this.createTransportConfiguration = false;
113 } else { 118 } else {
114 - this.profileConfigFormGroup.get('deviceProfileId').setValidators(null);  
115 - this.profileConfigFormGroup.get('deviceProfileId').disable();  
116 - this.profileConfigFormGroup.get('newDeviceProfileTitle').setValidators([Validators.required]);  
117 - this.profileConfigFormGroup.get('newDeviceProfileTitle').enable();  
118 - this.profileConfigFormGroup.updateValueAndValidity();  
119 - this.createdProfile = true; 119 + this.deviceWizardFormGroup.get('deviceProfileId').setValidators(null);
  120 + this.deviceWizardFormGroup.get('deviceProfileId').disable();
  121 + this.deviceWizardFormGroup.get('newDeviceProfileTitle').setValidators([Validators.required]);
  122 + this.deviceWizardFormGroup.get('newDeviceProfileTitle').enable();
  123 + this.deviceWizardFormGroup.updateValueAndValidity();
  124 + this.createProfile = true;
  125 + this.createTransportConfiguration = this.deviceWizardFormGroup.get('transportType').value &&
  126 + DeviceTransportType.DEFAULT !== this.deviceWizardFormGroup.get('transportType').value;
120 } 127 }
121 } 128 }
122 )); 129 ));
@@ -135,20 +142,37 @@ export class DeviceWizardDialogComponent extends @@ -135,20 +142,37 @@ export class DeviceWizardDialogComponent extends
135 } 142 }
136 ); 143 );
137 144
138 - this.specificConfigFormGroup = this.fb.group({  
139 - customerId: [null], 145 + this.credentialsFormGroup = this.fb.group({
140 setCredential: [false], 146 setCredential: [false],
141 credential: [{value: null, disabled: true}] 147 credential: [{value: null, disabled: true}]
142 } 148 }
143 ); 149 );
144 150
145 - this.subscriptions.push(this.specificConfigFormGroup.get('setCredential').valueChanges.subscribe((value) => { 151 + this.subscriptions.push(this.credentialsFormGroup.get('setCredential').valueChanges.subscribe((value) => {
146 if (value) { 152 if (value) {
147 - this.specificConfigFormGroup.get('credential').enable(); 153 + this.credentialsFormGroup.get('credential').enable();
148 } else { 154 } else {
149 - this.specificConfigFormGroup.get('credential').disable(); 155 + this.credentialsFormGroup.get('credential').disable();
150 } 156 }
151 })); 157 }));
  158 +
  159 + this.customerFormGroup = this.fb.group({
  160 + customerId: [null]
  161 + }
  162 + );
  163 +
  164 + this.labelPosition = this.breakpointObserver.isMatched(MediaBreakpoints['gt-sm']) ? 'end' : 'bottom';
  165 +
  166 + this.subscriptions.push(this.breakpointObserver
  167 + .observe(MediaBreakpoints['gt-sm'])
  168 + .subscribe((state: BreakpointState) => {
  169 + if (state.matches) {
  170 + this.labelPosition = 'end';
  171 + } else {
  172 + this.labelPosition = 'bottom';
  173 + }
  174 + }
  175 + ));
152 } 176 }
153 177
154 ngOnDestroy() { 178 ngOnDestroy() {
@@ -171,26 +195,28 @@ export class DeviceWizardDialogComponent extends @@ -171,26 +195,28 @@ export class DeviceWizardDialogComponent extends
171 } 195 }
172 196
173 nextStep(): void { 197 nextStep(): void {
174 - if (this.selectedIndex < this.maxStepperIndex) {  
175 - this.addDeviceWizardStepper.next();  
176 - } else {  
177 - this.add();  
178 - } 198 + this.addDeviceWizardStepper.next();
179 } 199 }
180 200
181 - get selectedForm(): FormGroup {  
182 - const index = !this.createdProfile && this.selectedIndex === this.maxStepperIndex ? 4 : this.selectedIndex; 201 + getFormLabel(index: number): string {
  202 + if (index > 0) {
  203 + if (!this.createProfile) {
  204 + index += 2;
  205 + } else if (!this.createTransportConfiguration) {
  206 + index += 1;
  207 + }
  208 + }
183 switch (index) { 209 switch (index) {
184 case 0: 210 case 0:
185 - return this.deviceWizardFormGroup; 211 + return 'device.wizard.device-details';
186 case 1: 212 case 1:
187 - return this.profileConfigFormGroup; 213 + return 'device-profile.transport-configuration';
188 case 2: 214 case 2:
189 - return this.transportConfigFormGroup; 215 + return 'device-profile.alarm-rules';
190 case 3: 216 case 3:
191 - return this.alarmRulesFormGroup; 217 + return 'device.credentials';
192 case 4: 218 case 4:
193 - return this.specificConfigFormGroup; 219 + return 'customer.customer';
194 } 220 }
195 } 221 }
196 222
@@ -201,23 +227,27 @@ export class DeviceWizardDialogComponent extends @@ -201,23 +227,27 @@ export class DeviceWizardDialogComponent extends
201 private deviceProfileTransportTypeChanged(deviceTransportType: DeviceTransportType): void { 227 private deviceProfileTransportTypeChanged(deviceTransportType: DeviceTransportType): void {
202 this.transportConfigFormGroup.patchValue( 228 this.transportConfigFormGroup.patchValue(
203 {transportConfiguration: createDeviceProfileTransportConfiguration(deviceTransportType)}); 229 {transportConfiguration: createDeviceProfileTransportConfiguration(deviceTransportType)});
  230 + this.createTransportConfiguration = this.createProfile && deviceTransportType &&
  231 + DeviceTransportType.DEFAULT !== deviceTransportType;
204 } 232 }
205 233
206 - private add(): void {  
207 - this.creatProfile().pipe(  
208 - mergeMap(profileId => this.createdDevice(profileId)),  
209 - mergeMap(device => this.saveCredential(device))  
210 - ).subscribe(  
211 - (created) => {  
212 - this.dialogRef.close(created);  
213 - }  
214 - ); 234 + add(): void {
  235 + if (this.allValid()) {
  236 + this.createDeviceProfile().pipe(
  237 + mergeMap(profileId => this.createDevice(profileId)),
  238 + mergeMap(device => this.saveCredentials(device))
  239 + ).subscribe(
  240 + (created) => {
  241 + this.dialogRef.close(created);
  242 + }
  243 + );
  244 + }
215 } 245 }
216 246
217 - private creatProfile(): Observable<EntityId> {  
218 - if (this.profileConfigFormGroup.get('addProfileType').value) { 247 + private createDeviceProfile(): Observable<EntityId> {
  248 + if (this.deviceWizardFormGroup.get('addProfileType').value) {
219 const deviceProfile: DeviceProfile = { 249 const deviceProfile: DeviceProfile = {
220 - name: this.profileConfigFormGroup.get('newDeviceProfileTitle').value, 250 + name: this.deviceWizardFormGroup.get('newDeviceProfileTitle').value,
221 type: DeviceProfileType.DEFAULT, 251 type: DeviceProfileType.DEFAULT,
222 transportType: this.deviceWizardFormGroup.get('transportType').value, 252 transportType: this.deviceWizardFormGroup.get('transportType').value,
223 profileData: { 253 profileData: {
@@ -229,11 +259,10 @@ export class DeviceWizardDialogComponent extends @@ -229,11 +259,10 @@ export class DeviceWizardDialogComponent extends
229 return this.deviceProfileService.saveDeviceProfile(deviceProfile).pipe( 259 return this.deviceProfileService.saveDeviceProfile(deviceProfile).pipe(
230 map(profile => profile.id), 260 map(profile => profile.id),
231 tap((profileId) => { 261 tap((profileId) => {
232 - this.profileConfigFormGroup.patchValue({ 262 + this.deviceWizardFormGroup.patchValue({
233 deviceProfileId: profileId, 263 deviceProfileId: profileId,
234 addProfileType: 0 264 addProfileType: 0
235 }); 265 });
236 - this.addDeviceWizardStepper.selectedIndex = 2;  
237 }) 266 })
238 ); 267 );
239 } else { 268 } else {
@@ -241,7 +270,7 @@ export class DeviceWizardDialogComponent extends @@ -241,7 +270,7 @@ export class DeviceWizardDialogComponent extends
241 } 270 }
242 } 271 }
243 272
244 - private createdDevice(profileId: EntityId = this.profileConfigFormGroup.get('deviceProfileId').value): Observable<BaseData<HasId>> { 273 + private createDevice(profileId: EntityId = this.deviceWizardFormGroup.get('deviceProfileId').value): Observable<BaseData<HasId>> {
245 const device = { 274 const device = {
246 name: this.deviceWizardFormGroup.get('name').value, 275 name: this.deviceWizardFormGroup.get('name').value,
247 label: this.deviceWizardFormGroup.get('label').value, 276 label: this.deviceWizardFormGroup.get('label').value,
@@ -252,21 +281,21 @@ export class DeviceWizardDialogComponent extends @@ -252,21 +281,21 @@ export class DeviceWizardDialogComponent extends
252 }, 281 },
253 customerId: null 282 customerId: null
254 }; 283 };
255 - if (this.specificConfigFormGroup.get('customerId').value) { 284 + if (this.customerFormGroup.get('customerId').value) {
256 device.customerId = { 285 device.customerId = {
257 entityType: EntityType.CUSTOMER, 286 entityType: EntityType.CUSTOMER,
258 - id: this.specificConfigFormGroup.get('customerId').value 287 + id: this.customerFormGroup.get('customerId').value
259 }; 288 };
260 } 289 }
261 return this.data.entitiesTableConfig.saveEntity(device); 290 return this.data.entitiesTableConfig.saveEntity(device);
262 } 291 }
263 292
264 - private saveCredential(device: BaseData<HasId>): Observable<boolean> {  
265 - if (this.specificConfigFormGroup.get('setCredential').value) { 293 + private saveCredentials(device: BaseData<HasId>): Observable<boolean> {
  294 + if (this.credentialsFormGroup.get('setCredential').value) {
266 return this.deviceService.getDeviceCredentials(device.id.id).pipe( 295 return this.deviceService.getDeviceCredentials(device.id.id).pipe(
267 mergeMap( 296 mergeMap(
268 (deviceCredentials) => { 297 (deviceCredentials) => {
269 - const deviceCredentialsValue = {...deviceCredentials, ...this.specificConfigFormGroup.value.credential}; 298 + const deviceCredentialsValue = {...deviceCredentials, ...this.credentialsFormGroup.value.credential};
270 return this.deviceService.saveDeviceCredentials(deviceCredentialsValue); 299 return this.deviceService.saveDeviceCredentials(deviceCredentialsValue);
271 } 300 }
272 ), 301 ),
@@ -275,12 +304,28 @@ export class DeviceWizardDialogComponent extends @@ -275,12 +304,28 @@ export class DeviceWizardDialogComponent extends
275 return of(true); 304 return of(true);
276 } 305 }
277 306
  307 + allValid(): boolean {
  308 + if (this.addDeviceWizardStepper.steps.find((item, index) => {
  309 + if (item.stepControl.invalid) {
  310 + item.interacted = true;
  311 + this.addDeviceWizardStepper.selectedIndex = index;
  312 + return true;
  313 + } else {
  314 + return false;
  315 + }
  316 + } )) {
  317 + return false;
  318 + } else {
  319 + return true;
  320 + }
  321 + }
  322 +
278 changeStep($event: StepperSelectionEvent): void { 323 changeStep($event: StepperSelectionEvent): void {
279 this.selectedIndex = $event.selectedIndex; 324 this.selectedIndex = $event.selectedIndex;
280 if (this.selectedIndex === this.maxStepperIndex) { 325 if (this.selectedIndex === this.maxStepperIndex) {
281 - this.nextStepButtonLabel$.next('action.add'); 326 + this.showNext = false;
282 } else { 327 } else {
283 - this.nextStepButtonLabel$.next('action.continue'); 328 + this.showNext = true;
284 } 329 }
285 } 330 }
286 } 331 }
@@ -296,7 +296,7 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev @@ -296,7 +296,7 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
296 name: this.translate.instant('device.add-device-text'), 296 name: this.translate.instant('device.add-device-text'),
297 icon: 'insert_drive_file', 297 icon: 'insert_drive_file',
298 isEnabled: () => true, 298 isEnabled: () => true,
299 - onAction: ($event) => this.config.table.addEntity($event) 299 + onAction: ($event) => this.deviceWizard($event)
300 }, 300 },
301 { 301 {
302 name: this.translate.instant('device.import'), 302 name: this.translate.instant('device.import'),
@@ -304,12 +304,6 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev @@ -304,12 +304,6 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
304 isEnabled: () => true, 304 isEnabled: () => true,
305 onAction: ($event) => this.importDevices($event) 305 onAction: ($event) => this.importDevices($event)
306 }, 306 },
307 - {  
308 - name: this.translate.instant('device.wizard.device-wizard'),  
309 - icon: 'library_add',  
310 - isEnabled: () => true,  
311 - onAction: ($event) => this.deviceWizard($event)  
312 - },  
313 ); 307 );
314 } 308 }
315 if (deviceScope === 'customer') { 309 if (deviceScope === 'customer') {
@@ -72,6 +72,14 @@ export const deviceTransportTypeTranslationMap = new Map<DeviceTransportType, st @@ -72,6 +72,14 @@ export const deviceTransportTypeTranslationMap = new Map<DeviceTransportType, st
72 ] 72 ]
73 ); 73 );
74 74
  75 +export const deviceTransportTypeHintMap = new Map<DeviceTransportType, string>(
  76 + [
  77 + [DeviceTransportType.DEFAULT, 'device-profile.transport-type-default-hint'],
  78 + [DeviceTransportType.MQTT, 'device-profile.transport-type-mqtt-hint'],
  79 + [DeviceTransportType.LWM2M, 'device-profile.transport-type-lwm2m-hint']
  80 + ]
  81 +);
  82 +
75 export const mqttTransportPayloadTypeTranslationMap = new Map<MqttTransportPayloadType, string>( 83 export const mqttTransportPayloadTypeTranslationMap = new Map<MqttTransportPayloadType, string>(
76 [ 84 [
77 [MqttTransportPayloadType.JSON, 'device-profile.mqtt-device-payload-type-json'], 85 [MqttTransportPayloadType.JSON, 'device-profile.mqtt-device-payload-type-json'],
@@ -54,7 +54,8 @@ @@ -54,7 +54,8 @@
54 "share-via": "Share via {{provider}}", 54 "share-via": "Share via {{provider}}",
55 "continue": "Continue", 55 "continue": "Continue",
56 "discard-changes": "Discard Changes", 56 "discard-changes": "Discard Changes",
57 - "download": "Download" 57 + "download": "Download",
  58 + "next-with-label": "Next: {{label}}"
58 }, 59 },
59 "aggregation": { 60 "aggregation": {
60 "aggregation": "Aggregation", 61 "aggregation": "Aggregation",
@@ -760,8 +761,7 @@ @@ -760,8 +761,7 @@
760 "wizard": { 761 "wizard": {
761 "device-wizard": "Device Wizard", 762 "device-wizard": "Device Wizard",
762 "device-details": "Device details", 763 "device-details": "Device details",
763 - "profile-configuration": "Profile configuration",  
764 - "new-device-profile": "New device profile", 764 + "new-device-profile": "Create new device profile",
765 "existing-device-profile": "Select existing device profile", 765 "existing-device-profile": "Select existing device profile",
766 "specific-configuration": "Specific configuration", 766 "specific-configuration": "Specific configuration",
767 "customer-to-assign-device": "Customer to assign the device", 767 "customer-to-assign-device": "Customer to assign the device",
@@ -784,6 +784,8 @@ @@ -784,6 +784,8 @@
784 "set-default": "Make device profile default", 784 "set-default": "Make device profile default",
785 "delete": "Delete device profile", 785 "delete": "Delete device profile",
786 "copyId": "Copy device profile Id", 786 "copyId": "Copy device profile Id",
  787 + "new-device-profile-name": "Device profile name",
  788 + "new-device-profile-name-required": "Device profile name is required.",
787 "name": "Name", 789 "name": "Name",
788 "name-required": "Name is required.", 790 "name-required": "Name is required.",
789 "type": "Profile type", 791 "type": "Profile type",
@@ -792,8 +794,11 @@ @@ -792,8 +794,11 @@
792 "transport-type": "Transport type", 794 "transport-type": "Transport type",
793 "transport-type-required": "Transport type is required.", 795 "transport-type-required": "Transport type is required.",
794 "transport-type-default": "Default", 796 "transport-type-default": "Default",
  797 + "transport-type-default-hint": "Default transport type",
795 "transport-type-mqtt": "MQTT", 798 "transport-type-mqtt": "MQTT",
  799 + "transport-type-mqtt-hint": "MQTT transport type",
796 "transport-type-lwm2m": "LWM2M", 800 "transport-type-lwm2m": "LWM2M",
  801 + "transport-type-lwm2m-hint": "LWM2M transport type",
797 "description": "Description", 802 "description": "Description",
798 "default": "Default", 803 "default": "Default",
799 "profile-configuration": "Profile configuration", 804 "profile-configuration": "Profile configuration",
@@ -824,7 +829,8 @@ @@ -824,7 +829,8 @@
824 "not-valid-multi-character": "Invalid use of a multi-level wildcard character", 829 "not-valid-multi-character": "Invalid use of a multi-level wildcard character",
825 "single-level-wildcards-hint": "<code>[+]</code> is suitable for any topic filter level. Ex.: <b>v1/devices/+/telemetry</b> or <b>+/devices/+/attributes</b>.", 830 "single-level-wildcards-hint": "<code>[+]</code> is suitable for any topic filter level. Ex.: <b>v1/devices/+/telemetry</b> or <b>+/devices/+/attributes</b>.",
826 "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>.", 831 "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>.",
827 - "alarm-rules": "Alarm rules ({{count}})", 832 + "alarm-rules": "Alarm rules",
  833 + "alarm-rules-with-count": "Alarm rules ({{count}})",
828 "no-alarm-rules": "No alarm rules configured", 834 "no-alarm-rules": "No alarm rules configured",
829 "add-alarm-rule": "Add alarm rule", 835 "add-alarm-rule": "Add alarm rule",
830 "edit-alarm-rule": "Edit alarm rule", 836 "edit-alarm-rule": "Edit alarm rule",