Showing
9 changed files
with
250 additions
and
146 deletions
@@ -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", |