Commit e8d8382c74c7d76f7050618eae1f36cbff329bc6

Authored by Vladyslav Prykhodko
1 parent 2b1317b1

UI: Added device provision to device profile

... ... @@ -107,6 +107,7 @@ import { AlarmRuleKeyFiltersDialogComponent } from './profile/alarm/alarm-rule-k
107 107 import { FilterTextComponent } from './filter/filter-text.component';
108 108 import { AddDeviceProfileDialogComponent } from './profile/add-device-profile-dialog.component';
109 109 import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomplete.component';
  110 +import { DeviceProfileProvisionConfigurationComponent } from "./profile/device-profile-provision-configuration.component";
110 111
111 112 @NgModule({
112 113 declarations:
... ... @@ -196,7 +197,8 @@ import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomp
196 197 DeviceProfileComponent,
197 198 DeviceProfileDialogComponent,
198 199 AddDeviceProfileDialogComponent,
199   - RuleChainAutocompleteComponent
  200 + RuleChainAutocompleteComponent,
  201 + DeviceProfileProvisionConfigurationComponent
200 202 ],
201 203 imports: [
202 204 CommonModule,
... ... @@ -275,7 +277,8 @@ import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomp
275 277 DeviceProfileComponent,
276 278 DeviceProfileDialogComponent,
277 279 AddDeviceProfileDialogComponent,
278   - RuleChainAutocompleteComponent
  280 + RuleChainAutocompleteComponent,
  281 + DeviceProfileProvisionConfigurationComponent
279 282 ],
280 283 providers: [
281 284 WidgetComponentService,
... ...
... ... @@ -93,6 +93,14 @@
93 93 </tb-device-profile-alarms>
94 94 </form>
95 95 </mat-step>
  96 + <mat-step [stepControl]="provisionConfigurationFormGroup">
  97 + <form [formGroup]="provisionConfigurationFormGroup" style="padding-bottom: 16px;">
  98 + <ng-template matStepLabel>{{'device-profile.device-provisioning' | translate }}</ng-template>
  99 + <tb-device-profile-provision-configuration
  100 + formControlName="provisionConfiguration">
  101 + </tb-device-profile-provision-configuration>
  102 + </form>
  103 + </mat-step>
96 104 </mat-horizontal-stepper>
97 105 </div>
98 106 <div mat-dialog-actions fxLayout="row wrap" fxLayoutAlign="space-between center">
... ... @@ -107,7 +115,7 @@
107 115 <button mat-raised-button
108 116 [disabled]="(isLoading$ | async) || selectedForm().invalid"
109 117 color="primary"
110   - (click)="nextStep()">{{ (selectedIndex === 2 ? 'action.add' : 'action.continue') | translate }}</button>
  118 + (click)="nextStep()">{{ (selectedIndex === 3 ? 'action.add' : 'action.continue') | translate }}</button>
111 119 </div>
112 120 </div>
113 121 </div>
... ...
... ... @@ -77,6 +77,8 @@ export class AddDeviceProfileDialogComponent extends
77 77
78 78 alarmRulesFormGroup: FormGroup;
79 79
  80 + provisionConfigurationFormGroup: FormGroup;
  81 +
80 82 constructor(protected store: Store<AppState>,
81 83 protected router: Router,
82 84 @Inject(MAT_DIALOG_DATA) public data: AddDeviceProfileDialogData,
... ... @@ -111,6 +113,12 @@ export class AddDeviceProfileDialogComponent extends
111 113 alarms: [null]
112 114 }
113 115 );
  116 +
  117 + this.provisionConfigurationFormGroup = this.fb.group(
  118 + {
  119 + provisionConfiguration: [null]
  120 + }
  121 + )
114 122 }
115 123
116 124 private deviceProfileTransportTypeChanged() {
... ... @@ -131,7 +139,7 @@ export class AddDeviceProfileDialogComponent extends
131 139 }
132 140
133 141 nextStep() {
134   - if (this.selectedIndex < 2) {
  142 + if (this.selectedIndex < 3) {
135 143 this.addDeviceProfileStepper.next();
136 144 } else {
137 145 this.add();
... ... @@ -146,6 +154,8 @@ export class AddDeviceProfileDialogComponent extends
146 154 return this.transportConfigFormGroup;
147 155 case 2:
148 156 return this.alarmRulesFormGroup;
  157 + case 3:
  158 + return this.provisionConfigurationFormGroup;
149 159 }
150 160 }
151 161
... ... @@ -154,11 +164,17 @@ export class AddDeviceProfileDialogComponent extends
154 164 name: this.deviceProfileDetailsFormGroup.get('name').value,
155 165 type: this.deviceProfileDetailsFormGroup.get('type').value,
156 166 transportType: this.transportConfigFormGroup.get('transportType').value,
  167 + provisionType: this.provisionConfigurationFormGroup.get('provisionConfiguration').value.type,
  168 + provisionDeviceKey: this.provisionConfigurationFormGroup.get('provisionConfiguration').value.provisionDeviceKey,
157 169 description: this.deviceProfileDetailsFormGroup.get('description').value,
158 170 profileData: {
159 171 configuration: createDeviceProfileConfiguration(DeviceProfileType.DEFAULT),
160 172 transportConfiguration: this.transportConfigFormGroup.get('transportConfiguration').value,
161   - alarms: this.alarmRulesFormGroup.get('alarms').value
  173 + alarms: this.alarmRulesFormGroup.get('alarms').value,
  174 + provisionConfiguration: {
  175 + type: this.provisionConfigurationFormGroup.get('provisionConfiguration').value.type,
  176 + provisionDeviceSecret: this.provisionConfigurationFormGroup.get('provisionConfiguration').value.provisionDeviceSecret
  177 + }
162 178 }
163 179 };
164 180 if (this.deviceProfileDetailsFormGroup.get('defaultRuleChainId').value) {
... ...
... ... @@ -51,5 +51,15 @@
51 51 formControlName="alarms">
52 52 </tb-device-profile-alarms>
53 53 </mat-expansion-panel>
  54 + <mat-expansion-panel [expanded]="true">
  55 + <mat-expansion-panel-header>
  56 + <mat-panel-title>
  57 + <div translate>device-profile.device-provisioning</div>
  58 + </mat-panel-title>
  59 + </mat-expansion-panel-header>
  60 + <tb-device-profile-provision-configuration
  61 + formControlName="provisionConfiguration">
  62 + </tb-device-profile-provision-configuration>
  63 + </mat-expansion-panel>
54 64 </mat-accordion>
55 65 </div>
... ...
... ... @@ -72,7 +72,8 @@ export class DeviceProfileDataComponent implements ControlValueAccessor, OnInit
72 72 this.deviceProfileDataFormGroup = this.fb.group({
73 73 configuration: [null, Validators.required],
74 74 transportConfiguration: [null, Validators.required],
75   - alarms: [null]
  75 + alarms: [null],
  76 + provisionConfiguration: [null]
76 77 });
77 78 this.deviceProfileDataFormGroup.valueChanges.subscribe(() => {
78 79 this.updateModel();
... ... @@ -98,6 +99,7 @@ export class DeviceProfileDataComponent implements ControlValueAccessor, OnInit
98 99 this.deviceProfileDataFormGroup.patchValue({configuration: value?.configuration}, {emitEvent: false});
99 100 this.deviceProfileDataFormGroup.patchValue({transportConfiguration: value?.transportConfiguration}, {emitEvent: false});
100 101 this.deviceProfileDataFormGroup.patchValue({alarms: value?.alarms}, {emitEvent: false});
  102 + this.deviceProfileDataFormGroup.patchValue({provisionConfiguration: value?.provisionConfiguration}, {emitEvent: false});
101 103 }
102 104
103 105 private updateModel() {
... ...
  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 [formGroup]="provisionConfigurationFormGroup">
  19 + <mat-form-field class="mat-block">
  20 + <mat-label translate>device-profile.provision-strategy</mat-label>
  21 + <mat-select formControlName="type" required>
  22 + <mat-option *ngFor="let type of deviceProvisionTypes" [value]="type">
  23 + {{deviceProvisionTypeTranslateMap.get(type) | translate}}
  24 + </mat-option>
  25 + </mat-select>
  26 + <mat-error *ngIf="provisionConfigurationFormGroup.get('type').hasError('required')">
  27 + {{ 'device-profile.provision-strategy-required' | translate }}
  28 + </mat-error>
  29 + </mat-form-field>
  30 + <section *ngIf="provisionConfigurationFormGroup.get('type').value !== deviceProvisionType.DISABLED" fxLayoutGap.gt-xs="8px" fxLayout="row" fxLayout.xs="column">
  31 + <mat-form-field fxFlex class="mat-block">
  32 + <mat-label translate>device-profile.provision-device-secret</mat-label>
  33 + <input matInput formControlName="provisionDeviceSecret" required/>
  34 + <mat-error *ngIf="provisionConfigurationFormGroup.get('provisionDeviceSecret').hasError('required')">
  35 + {{ 'device-profile.provision-device-secret-required' | translate }}
  36 + </mat-error>
  37 + </mat-form-field>
  38 + <mat-form-field fxFlex class="mat-block">
  39 + <mat-label translate>device-profile.provision-device-key</mat-label>
  40 + <input matInput formControlName="provisionDeviceKey" required/>
  41 + <mat-error *ngIf="provisionConfigurationFormGroup.get('provisionDeviceKey').hasError('required')">
  42 + {{ 'device-profile.provision-device-key-required' | translate }}
  43 + </mat-error>
  44 + </mat-form-field>
  45 + </section>
  46 +</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 +
  17 +import { Component, forwardRef, Input, OnInit } from "@angular/core";
  18 +import {
  19 + ControlValueAccessor,
  20 + FormBuilder,
  21 + FormControl,
  22 + FormGroup,
  23 + NG_VALIDATORS,
  24 + NG_VALUE_ACCESSOR,
  25 + ValidationErrors,
  26 + Validator,
  27 + Validators
  28 +} from "@angular/forms";
  29 +import { coerceBooleanProperty } from "@angular/cdk/coercion";
  30 +import {
  31 + DeviceProvisionConfiguration,
  32 + DeviceProvisionType,
  33 + deviceProvisionTypeTranslationMap
  34 +} from "@shared/models/device.models";
  35 +import { isDefinedAndNotNull } from "@core/utils";
  36 +
  37 +@Component({
  38 + selector: 'tb-device-profile-provision-configuration',
  39 + templateUrl: './device-profile-provision-configuration.component.html',
  40 + styleUrls: [],
  41 + providers: [
  42 + {
  43 + provide: NG_VALUE_ACCESSOR,
  44 + useExisting: forwardRef(() => DeviceProfileProvisionConfigurationComponent),
  45 + multi: true
  46 + },
  47 + {
  48 + provide: NG_VALIDATORS,
  49 + useExisting: forwardRef(() => DeviceProfileProvisionConfigurationComponent),
  50 + multi: true,
  51 + }
  52 + ]
  53 +})
  54 +export class DeviceProfileProvisionConfigurationComponent implements ControlValueAccessor, OnInit, Validator {
  55 +
  56 + provisionConfigurationFormGroup: FormGroup;
  57 +
  58 + deviceProvisionType = DeviceProvisionType;
  59 + deviceProvisionTypes = Object.keys(DeviceProvisionType);
  60 + deviceProvisionTypeTranslateMap = deviceProvisionTypeTranslationMap;
  61 +
  62 + private requiredValue: boolean;
  63 + get required(): boolean {
  64 + return this.requiredValue;
  65 + }
  66 + @Input()
  67 + set required(value: boolean) {
  68 + this.requiredValue = coerceBooleanProperty(value);
  69 + }
  70 +
  71 + @Input()
  72 + disabled: boolean;
  73 +
  74 + private propagateChange = (v: any) => { };
  75 +
  76 + constructor(private fb: FormBuilder) {
  77 + }
  78 +
  79 + ngOnInit(): void {
  80 + this.provisionConfigurationFormGroup = this.fb.group({
  81 + type: [DeviceProvisionType.DISABLED, Validators.required],
  82 + provisionDeviceSecret: [{value: null, disabled: true}, Validators.required],
  83 + provisionDeviceKey: [{value: null, disabled: true}, Validators.required]
  84 + });
  85 + this.provisionConfigurationFormGroup.get('type').valueChanges.subscribe((type) => {
  86 + if(type === DeviceProvisionType.DISABLED) {
  87 + this.provisionConfigurationFormGroup.get('provisionDeviceSecret').disable({emitEvent: false});
  88 + this.provisionConfigurationFormGroup.get('provisionDeviceSecret').patchValue(null,{emitEvent: false});
  89 + this.provisionConfigurationFormGroup.get('provisionDeviceKey').disable({emitEvent: false});
  90 + this.provisionConfigurationFormGroup.get('provisionDeviceKey').patchValue(null);
  91 + } else {
  92 + this.provisionConfigurationFormGroup.get('provisionDeviceSecret').enable({emitEvent: false});
  93 + this.provisionConfigurationFormGroup.get('provisionDeviceKey').enable({emitEvent: false});
  94 + }
  95 + });
  96 + this.provisionConfigurationFormGroup.valueChanges.subscribe(() => {
  97 + this.updateModel();
  98 + });
  99 + }
  100 +
  101 + registerOnChange(fn: any): void {
  102 + this.propagateChange = fn;
  103 + }
  104 +
  105 + registerOnTouched(fn: any): void {
  106 + }
  107 +
  108 + writeValue(value: DeviceProvisionConfiguration | null): void {
  109 + if(isDefinedAndNotNull(value)){
  110 + this.provisionConfigurationFormGroup.patchValue(value, {emitEvent: false});
  111 + } else {
  112 + this.provisionConfigurationFormGroup.patchValue({type: DeviceProvisionType.DISABLED});
  113 + }
  114 + }
  115 +
  116 + setDisabledState(isDisabled: boolean){
  117 + this.disabled = isDisabled;
  118 + if(this.disabled){
  119 + this.provisionConfigurationFormGroup.disable();
  120 + } else {
  121 + this.provisionConfigurationFormGroup.enable({emitEvent: false});
  122 + }
  123 + }
  124 +
  125 + validate(c: FormControl): ValidationErrors | null {
  126 + return (this.provisionConfigurationFormGroup.valid) ? null : {
  127 + provisionConfiguration: {
  128 + valid: false,
  129 + },
  130 + };
  131 + }
  132 +
  133 + private updateModel(): void {
  134 + let deviceProvisionConfiguration: DeviceProvisionConfiguration = null;
  135 + if (this.provisionConfigurationFormGroup.valid) {
  136 + deviceProvisionConfiguration = this.provisionConfigurationFormGroup.getRawValue();
  137 + }
  138 + this.propagateChange(deviceProvisionConfiguration);
  139 + }
  140 +}
... ...
... ... @@ -126,6 +126,10 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
126 126 }
127 127
128 128 updateForm(entity: DeviceProfile) {
  129 + if(entity?.profileData?.provisionConfiguration) {
  130 + entity.profileData.provisionConfiguration.provisionDeviceKey = entity?.provisionDeviceKey;
  131 + }
  132 +
129 133 this.entityForm.patchValue({name: entity.name});
130 134 this.entityForm.patchValue({type: entity.type}, {emitEvent: false});
131 135 this.entityForm.patchValue({transportType: entity.transportType}, {emitEvent: false});
... ... @@ -138,7 +142,11 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
138 142 if (formValue.defaultRuleChainId) {
139 143 formValue.defaultRuleChainId = new RuleChainId(formValue.defaultRuleChainId);
140 144 }
141   - return formValue;
  145 + formValue.provisionType = formValue.profileData.provisionConfiguration.type;
  146 + formValue.provisionDeviceKey = formValue.profileData.provisionConfiguration.provisionDeviceKey;
  147 + delete formValue.profileData.provisionConfiguration.provisionDeviceKey;
  148 +
  149 + return super.prepareFormValue(formValue);
142 150 }
143 151
144 152 onDeviceProfileIdCopied(event) {
... ...
... ... @@ -36,6 +36,12 @@ export enum DeviceTransportType {
36 36 LWM2M = 'LWM2M'
37 37 }
38 38
  39 +export enum DeviceProvisionType {
  40 + DISABLED = 'DISABLED',
  41 + ALLOW_CREATE_NEW_DEVICES = 'ALLOW_CREATE_NEW_DEVICES',
  42 + CHECK_PRE_PROVISIONED_DEVICES = 'CHECK_PRE_PROVISIONED_DEVICES'
  43 +}
  44 +
39 45 export interface DeviceConfigurationFormInfo {
40 46 hasProfileConfiguration: boolean;
41 47 hasDeviceConfiguration: boolean;
... ... @@ -67,6 +73,15 @@ export const deviceTransportTypeTranslationMap = new Map<DeviceTransportType, st
67 73 ]
68 74 );
69 75
  76 +
  77 +export const deviceProvisionTypeTranslationMap = new Map<DeviceProvisionType, string>(
  78 + [
  79 + [DeviceProvisionType.DISABLED, 'device-profile.provision-strategy-disabled'],
  80 + [DeviceProvisionType.ALLOW_CREATE_NEW_DEVICES, 'device-profile.provision-strategy-created-new'],
  81 + [DeviceProvisionType.CHECK_PRE_PROVISIONED_DEVICES, 'device-profile.provision-strategy-check-pre-provisioned']
  82 + ]
  83 +)
  84 +
70 85 export const deviceTransportTypeConfigurationInfoMap = new Map<DeviceTransportType, DeviceConfigurationFormInfo>(
71 86 [
72 87 [
... ... @@ -125,6 +140,12 @@ export interface DeviceProfileTransportConfiguration extends DeviceProfileTransp
125 140 type: DeviceTransportType;
126 141 }
127 142
  143 +export interface DeviceProvisionConfiguration {
  144 + type: DeviceProvisionType;
  145 + provisionDeviceSecret?: string;
  146 + provisionDeviceKey?: string;
  147 +}
  148 +
128 149 export function createDeviceProfileConfiguration(type: DeviceProfileType): DeviceProfileConfiguration {
129 150 let configuration: DeviceProfileConfiguration = null;
130 151 if (type) {
... ... @@ -220,6 +241,7 @@ export interface DeviceProfileData {
220 241 configuration: DeviceProfileConfiguration;
221 242 transportConfiguration: DeviceProfileTransportConfiguration;
222 243 alarms?: Array<DeviceProfileAlarm>;
  244 + provisionConfiguration?: DeviceProvisionConfiguration;
223 245 }
224 246
225 247 export interface DeviceProfile extends BaseData<DeviceProfileId> {
... ... @@ -229,6 +251,8 @@ export interface DeviceProfile extends BaseData<DeviceProfileId> {
229 251 default?: boolean;
230 252 type: DeviceProfileType;
231 253 transportType: DeviceTransportType;
  254 + provisionType: DeviceProvisionType;
  255 + provisionDeviceKey?: string;
232 256 defaultRuleChainId?: RuleChainId;
233 257 profileData: DeviceProfileData;
234 258 }
... ...
... ... @@ -840,7 +840,17 @@
840 840 "alarm-details": "Alarm details",
841 841 "alarm-rule-condition": "Alarm rule condition",
842 842 "enter-alarm-rule-condition-prompt": "Please add alarm rule condition",
843   - "edit-alarm-rule-condition": "Edit alarm rule condition"
  843 + "edit-alarm-rule-condition": "Edit alarm rule condition",
  844 + "device-provisioning": "Device provisioning",
  845 + "provision-strategy": "Provision strategy",
  846 + "provision-strategy-required": "Provision strategy is required.",
  847 + "provision-strategy-disabled": "Disabled",
  848 + "provision-strategy-created-new": "Allow create new devices",
  849 + "provision-strategy-check-pre-provisioned": "Check pre provisioned devices",
  850 + "provision-device-key": "Provision device key",
  851 + "provision-device-key-required": "Provision device key is required.",
  852 + "provision-device-secret": "Provision device secret",
  853 + "provision-device-secret-required": "Provision device secret is required."
844 854 },
845 855 "dialog": {
846 856 "close": "Close dialog"
... ...