UI: Added device provision to device profile

This commit is contained in:
Vladyslav Prykhodko 2020-10-04 00:05:52 +03:00
parent 2b1317b10f
commit e8d8382c74
10 changed files with 275 additions and 8 deletions

View File

@ -107,6 +107,7 @@ import { AlarmRuleKeyFiltersDialogComponent } from './profile/alarm/alarm-rule-k
import { FilterTextComponent } from './filter/filter-text.component'; import { FilterTextComponent } from './filter/filter-text.component';
import { AddDeviceProfileDialogComponent } from './profile/add-device-profile-dialog.component'; import { AddDeviceProfileDialogComponent } from './profile/add-device-profile-dialog.component';
import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomplete.component'; import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomplete.component';
import { DeviceProfileProvisionConfigurationComponent } from "./profile/device-profile-provision-configuration.component";
@NgModule({ @NgModule({
declarations: declarations:
@ -196,7 +197,8 @@ import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomp
DeviceProfileComponent, DeviceProfileComponent,
DeviceProfileDialogComponent, DeviceProfileDialogComponent,
AddDeviceProfileDialogComponent, AddDeviceProfileDialogComponent,
RuleChainAutocompleteComponent RuleChainAutocompleteComponent,
DeviceProfileProvisionConfigurationComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,
@ -275,7 +277,8 @@ import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomp
DeviceProfileComponent, DeviceProfileComponent,
DeviceProfileDialogComponent, DeviceProfileDialogComponent,
AddDeviceProfileDialogComponent, AddDeviceProfileDialogComponent,
RuleChainAutocompleteComponent RuleChainAutocompleteComponent,
DeviceProfileProvisionConfigurationComponent
], ],
providers: [ providers: [
WidgetComponentService, WidgetComponentService,

View File

@ -93,6 +93,14 @@
</tb-device-profile-alarms> </tb-device-profile-alarms>
</form> </form>
</mat-step> </mat-step>
<mat-step [stepControl]="provisionConfigurationFormGroup">
<form [formGroup]="provisionConfigurationFormGroup" style="padding-bottom: 16px;">
<ng-template matStepLabel>{{'device-profile.device-provisioning' | translate }}</ng-template>
<tb-device-profile-provision-configuration
formControlName="provisionConfiguration">
</tb-device-profile-provision-configuration>
</form>
</mat-step>
</mat-horizontal-stepper> </mat-horizontal-stepper>
</div> </div>
<div mat-dialog-actions fxLayout="row wrap" fxLayoutAlign="space-between center"> <div mat-dialog-actions fxLayout="row wrap" fxLayoutAlign="space-between center">
@ -107,7 +115,7 @@
<button mat-raised-button <button mat-raised-button
[disabled]="(isLoading$ | async) || selectedForm().invalid" [disabled]="(isLoading$ | async) || selectedForm().invalid"
color="primary" color="primary"
(click)="nextStep()">{{ (selectedIndex === 2 ? 'action.add' : 'action.continue') | translate }}</button> (click)="nextStep()">{{ (selectedIndex === 3 ? 'action.add' : 'action.continue') | translate }}</button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -77,6 +77,8 @@ export class AddDeviceProfileDialogComponent extends
alarmRulesFormGroup: FormGroup; alarmRulesFormGroup: FormGroup;
provisionConfigurationFormGroup: FormGroup;
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
protected router: Router, protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: AddDeviceProfileDialogData, @Inject(MAT_DIALOG_DATA) public data: AddDeviceProfileDialogData,
@ -111,6 +113,12 @@ export class AddDeviceProfileDialogComponent extends
alarms: [null] alarms: [null]
} }
); );
this.provisionConfigurationFormGroup = this.fb.group(
{
provisionConfiguration: [null]
}
)
} }
private deviceProfileTransportTypeChanged() { private deviceProfileTransportTypeChanged() {
@ -131,7 +139,7 @@ export class AddDeviceProfileDialogComponent extends
} }
nextStep() { nextStep() {
if (this.selectedIndex < 2) { if (this.selectedIndex < 3) {
this.addDeviceProfileStepper.next(); this.addDeviceProfileStepper.next();
} else { } else {
this.add(); this.add();
@ -146,6 +154,8 @@ export class AddDeviceProfileDialogComponent extends
return this.transportConfigFormGroup; return this.transportConfigFormGroup;
case 2: case 2:
return this.alarmRulesFormGroup; return this.alarmRulesFormGroup;
case 3:
return this.provisionConfigurationFormGroup;
} }
} }
@ -154,11 +164,17 @@ export class AddDeviceProfileDialogComponent extends
name: this.deviceProfileDetailsFormGroup.get('name').value, name: this.deviceProfileDetailsFormGroup.get('name').value,
type: this.deviceProfileDetailsFormGroup.get('type').value, type: this.deviceProfileDetailsFormGroup.get('type').value,
transportType: this.transportConfigFormGroup.get('transportType').value, transportType: this.transportConfigFormGroup.get('transportType').value,
provisionType: this.provisionConfigurationFormGroup.get('provisionConfiguration').value.type,
provisionDeviceKey: this.provisionConfigurationFormGroup.get('provisionConfiguration').value.provisionDeviceKey,
description: this.deviceProfileDetailsFormGroup.get('description').value, description: this.deviceProfileDetailsFormGroup.get('description').value,
profileData: { profileData: {
configuration: createDeviceProfileConfiguration(DeviceProfileType.DEFAULT), configuration: createDeviceProfileConfiguration(DeviceProfileType.DEFAULT),
transportConfiguration: this.transportConfigFormGroup.get('transportConfiguration').value, transportConfiguration: this.transportConfigFormGroup.get('transportConfiguration').value,
alarms: this.alarmRulesFormGroup.get('alarms').value alarms: this.alarmRulesFormGroup.get('alarms').value,
provisionConfiguration: {
type: this.provisionConfigurationFormGroup.get('provisionConfiguration').value.type,
provisionDeviceSecret: this.provisionConfigurationFormGroup.get('provisionConfiguration').value.provisionDeviceSecret
}
} }
}; };
if (this.deviceProfileDetailsFormGroup.get('defaultRuleChainId').value) { if (this.deviceProfileDetailsFormGroup.get('defaultRuleChainId').value) {

View File

@ -51,5 +51,15 @@
formControlName="alarms"> formControlName="alarms">
</tb-device-profile-alarms> </tb-device-profile-alarms>
</mat-expansion-panel> </mat-expansion-panel>
<mat-expansion-panel [expanded]="true">
<mat-expansion-panel-header>
<mat-panel-title>
<div translate>device-profile.device-provisioning</div>
</mat-panel-title>
</mat-expansion-panel-header>
<tb-device-profile-provision-configuration
formControlName="provisionConfiguration">
</tb-device-profile-provision-configuration>
</mat-expansion-panel>
</mat-accordion> </mat-accordion>
</div> </div>

View File

@ -72,7 +72,8 @@ export class DeviceProfileDataComponent implements ControlValueAccessor, OnInit
this.deviceProfileDataFormGroup = this.fb.group({ this.deviceProfileDataFormGroup = this.fb.group({
configuration: [null, Validators.required], configuration: [null, Validators.required],
transportConfiguration: [null, Validators.required], transportConfiguration: [null, Validators.required],
alarms: [null] alarms: [null],
provisionConfiguration: [null]
}); });
this.deviceProfileDataFormGroup.valueChanges.subscribe(() => { this.deviceProfileDataFormGroup.valueChanges.subscribe(() => {
this.updateModel(); this.updateModel();
@ -98,6 +99,7 @@ export class DeviceProfileDataComponent implements ControlValueAccessor, OnInit
this.deviceProfileDataFormGroup.patchValue({configuration: value?.configuration}, {emitEvent: false}); this.deviceProfileDataFormGroup.patchValue({configuration: value?.configuration}, {emitEvent: false});
this.deviceProfileDataFormGroup.patchValue({transportConfiguration: value?.transportConfiguration}, {emitEvent: false}); this.deviceProfileDataFormGroup.patchValue({transportConfiguration: value?.transportConfiguration}, {emitEvent: false});
this.deviceProfileDataFormGroup.patchValue({alarms: value?.alarms}, {emitEvent: false}); this.deviceProfileDataFormGroup.patchValue({alarms: value?.alarms}, {emitEvent: false});
this.deviceProfileDataFormGroup.patchValue({provisionConfiguration: value?.provisionConfiguration}, {emitEvent: false});
} }
private updateModel() { private updateModel() {

View File

@ -0,0 +1,46 @@
<!--
Copyright © 2016-2020 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div [formGroup]="provisionConfigurationFormGroup">
<mat-form-field class="mat-block">
<mat-label translate>device-profile.provision-strategy</mat-label>
<mat-select formControlName="type" required>
<mat-option *ngFor="let type of deviceProvisionTypes" [value]="type">
{{deviceProvisionTypeTranslateMap.get(type) | translate}}
</mat-option>
</mat-select>
<mat-error *ngIf="provisionConfigurationFormGroup.get('type').hasError('required')">
{{ 'device-profile.provision-strategy-required' | translate }}
</mat-error>
</mat-form-field>
<section *ngIf="provisionConfigurationFormGroup.get('type').value !== deviceProvisionType.DISABLED" fxLayoutGap.gt-xs="8px" fxLayout="row" fxLayout.xs="column">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>device-profile.provision-device-secret</mat-label>
<input matInput formControlName="provisionDeviceSecret" required/>
<mat-error *ngIf="provisionConfigurationFormGroup.get('provisionDeviceSecret').hasError('required')">
{{ 'device-profile.provision-device-secret-required' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>device-profile.provision-device-key</mat-label>
<input matInput formControlName="provisionDeviceKey" required/>
<mat-error *ngIf="provisionConfigurationFormGroup.get('provisionDeviceKey').hasError('required')">
{{ 'device-profile.provision-device-key-required' | translate }}
</mat-error>
</mat-form-field>
</section>
</div>

View File

@ -0,0 +1,140 @@
///
/// Copyright © 2016-2020 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, forwardRef, Input, OnInit } from "@angular/core";
import {
ControlValueAccessor,
FormBuilder,
FormControl,
FormGroup,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
ValidationErrors,
Validator,
Validators
} from "@angular/forms";
import { coerceBooleanProperty } from "@angular/cdk/coercion";
import {
DeviceProvisionConfiguration,
DeviceProvisionType,
deviceProvisionTypeTranslationMap
} from "@shared/models/device.models";
import { isDefinedAndNotNull } from "@core/utils";
@Component({
selector: 'tb-device-profile-provision-configuration',
templateUrl: './device-profile-provision-configuration.component.html',
styleUrls: [],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DeviceProfileProvisionConfigurationComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => DeviceProfileProvisionConfigurationComponent),
multi: true,
}
]
})
export class DeviceProfileProvisionConfigurationComponent implements ControlValueAccessor, OnInit, Validator {
provisionConfigurationFormGroup: FormGroup;
deviceProvisionType = DeviceProvisionType;
deviceProvisionTypes = Object.keys(DeviceProvisionType);
deviceProvisionTypeTranslateMap = deviceProvisionTypeTranslationMap;
private requiredValue: boolean;
get required(): boolean {
return this.requiredValue;
}
@Input()
set required(value: boolean) {
this.requiredValue = coerceBooleanProperty(value);
}
@Input()
disabled: boolean;
private propagateChange = (v: any) => { };
constructor(private fb: FormBuilder) {
}
ngOnInit(): void {
this.provisionConfigurationFormGroup = this.fb.group({
type: [DeviceProvisionType.DISABLED, Validators.required],
provisionDeviceSecret: [{value: null, disabled: true}, Validators.required],
provisionDeviceKey: [{value: null, disabled: true}, Validators.required]
});
this.provisionConfigurationFormGroup.get('type').valueChanges.subscribe((type) => {
if(type === DeviceProvisionType.DISABLED) {
this.provisionConfigurationFormGroup.get('provisionDeviceSecret').disable({emitEvent: false});
this.provisionConfigurationFormGroup.get('provisionDeviceSecret').patchValue(null,{emitEvent: false});
this.provisionConfigurationFormGroup.get('provisionDeviceKey').disable({emitEvent: false});
this.provisionConfigurationFormGroup.get('provisionDeviceKey').patchValue(null);
} else {
this.provisionConfigurationFormGroup.get('provisionDeviceSecret').enable({emitEvent: false});
this.provisionConfigurationFormGroup.get('provisionDeviceKey').enable({emitEvent: false});
}
});
this.provisionConfigurationFormGroup.valueChanges.subscribe(() => {
this.updateModel();
});
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
writeValue(value: DeviceProvisionConfiguration | null): void {
if(isDefinedAndNotNull(value)){
this.provisionConfigurationFormGroup.patchValue(value, {emitEvent: false});
} else {
this.provisionConfigurationFormGroup.patchValue({type: DeviceProvisionType.DISABLED});
}
}
setDisabledState(isDisabled: boolean){
this.disabled = isDisabled;
if(this.disabled){
this.provisionConfigurationFormGroup.disable();
} else {
this.provisionConfigurationFormGroup.enable({emitEvent: false});
}
}
validate(c: FormControl): ValidationErrors | null {
return (this.provisionConfigurationFormGroup.valid) ? null : {
provisionConfiguration: {
valid: false,
},
};
}
private updateModel(): void {
let deviceProvisionConfiguration: DeviceProvisionConfiguration = null;
if (this.provisionConfigurationFormGroup.valid) {
deviceProvisionConfiguration = this.provisionConfigurationFormGroup.getRawValue();
}
this.propagateChange(deviceProvisionConfiguration);
}
}

View File

@ -126,6 +126,10 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
} }
updateForm(entity: DeviceProfile) { updateForm(entity: DeviceProfile) {
if(entity?.profileData?.provisionConfiguration) {
entity.profileData.provisionConfiguration.provisionDeviceKey = entity?.provisionDeviceKey;
}
this.entityForm.patchValue({name: entity.name}); this.entityForm.patchValue({name: entity.name});
this.entityForm.patchValue({type: entity.type}, {emitEvent: false}); this.entityForm.patchValue({type: entity.type}, {emitEvent: false});
this.entityForm.patchValue({transportType: entity.transportType}, {emitEvent: false}); this.entityForm.patchValue({transportType: entity.transportType}, {emitEvent: false});
@ -138,7 +142,11 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
if (formValue.defaultRuleChainId) { if (formValue.defaultRuleChainId) {
formValue.defaultRuleChainId = new RuleChainId(formValue.defaultRuleChainId); formValue.defaultRuleChainId = new RuleChainId(formValue.defaultRuleChainId);
} }
return formValue; formValue.provisionType = formValue.profileData.provisionConfiguration.type;
formValue.provisionDeviceKey = formValue.profileData.provisionConfiguration.provisionDeviceKey;
delete formValue.profileData.provisionConfiguration.provisionDeviceKey;
return super.prepareFormValue(formValue);
} }
onDeviceProfileIdCopied(event) { onDeviceProfileIdCopied(event) {

View File

@ -36,6 +36,12 @@ export enum DeviceTransportType {
LWM2M = 'LWM2M' LWM2M = 'LWM2M'
} }
export enum DeviceProvisionType {
DISABLED = 'DISABLED',
ALLOW_CREATE_NEW_DEVICES = 'ALLOW_CREATE_NEW_DEVICES',
CHECK_PRE_PROVISIONED_DEVICES = 'CHECK_PRE_PROVISIONED_DEVICES'
}
export interface DeviceConfigurationFormInfo { export interface DeviceConfigurationFormInfo {
hasProfileConfiguration: boolean; hasProfileConfiguration: boolean;
hasDeviceConfiguration: boolean; hasDeviceConfiguration: boolean;
@ -67,6 +73,15 @@ export const deviceTransportTypeTranslationMap = new Map<DeviceTransportType, st
] ]
); );
export const deviceProvisionTypeTranslationMap = new Map<DeviceProvisionType, string>(
[
[DeviceProvisionType.DISABLED, 'device-profile.provision-strategy-disabled'],
[DeviceProvisionType.ALLOW_CREATE_NEW_DEVICES, 'device-profile.provision-strategy-created-new'],
[DeviceProvisionType.CHECK_PRE_PROVISIONED_DEVICES, 'device-profile.provision-strategy-check-pre-provisioned']
]
)
export const deviceTransportTypeConfigurationInfoMap = new Map<DeviceTransportType, DeviceConfigurationFormInfo>( export const deviceTransportTypeConfigurationInfoMap = new Map<DeviceTransportType, DeviceConfigurationFormInfo>(
[ [
[ [
@ -125,6 +140,12 @@ export interface DeviceProfileTransportConfiguration extends DeviceProfileTransp
type: DeviceTransportType; type: DeviceTransportType;
} }
export interface DeviceProvisionConfiguration {
type: DeviceProvisionType;
provisionDeviceSecret?: string;
provisionDeviceKey?: string;
}
export function createDeviceProfileConfiguration(type: DeviceProfileType): DeviceProfileConfiguration { export function createDeviceProfileConfiguration(type: DeviceProfileType): DeviceProfileConfiguration {
let configuration: DeviceProfileConfiguration = null; let configuration: DeviceProfileConfiguration = null;
if (type) { if (type) {
@ -220,6 +241,7 @@ export interface DeviceProfileData {
configuration: DeviceProfileConfiguration; configuration: DeviceProfileConfiguration;
transportConfiguration: DeviceProfileTransportConfiguration; transportConfiguration: DeviceProfileTransportConfiguration;
alarms?: Array<DeviceProfileAlarm>; alarms?: Array<DeviceProfileAlarm>;
provisionConfiguration?: DeviceProvisionConfiguration;
} }
export interface DeviceProfile extends BaseData<DeviceProfileId> { export interface DeviceProfile extends BaseData<DeviceProfileId> {
@ -229,6 +251,8 @@ export interface DeviceProfile extends BaseData<DeviceProfileId> {
default?: boolean; default?: boolean;
type: DeviceProfileType; type: DeviceProfileType;
transportType: DeviceTransportType; transportType: DeviceTransportType;
provisionType: DeviceProvisionType;
provisionDeviceKey?: string;
defaultRuleChainId?: RuleChainId; defaultRuleChainId?: RuleChainId;
profileData: DeviceProfileData; profileData: DeviceProfileData;
} }

View File

@ -840,7 +840,17 @@
"alarm-details": "Alarm details", "alarm-details": "Alarm details",
"alarm-rule-condition": "Alarm rule condition", "alarm-rule-condition": "Alarm rule condition",
"enter-alarm-rule-condition-prompt": "Please add alarm rule condition", "enter-alarm-rule-condition-prompt": "Please add alarm rule condition",
"edit-alarm-rule-condition": "Edit alarm rule condition" "edit-alarm-rule-condition": "Edit alarm rule condition",
"device-provisioning": "Device provisioning",
"provision-strategy": "Provision strategy",
"provision-strategy-required": "Provision strategy is required.",
"provision-strategy-disabled": "Disabled",
"provision-strategy-created-new": "Allow create new devices",
"provision-strategy-check-pre-provisioned": "Check pre provisioned devices",
"provision-device-key": "Provision device key",
"provision-device-key-required": "Provision device key is required.",
"provision-device-secret": "Provision device secret",
"provision-device-secret-required": "Provision device secret is required."
}, },
"dialog": { "dialog": {
"close": "Close dialog" "close": "Close dialog"