Commit e8d8382c74c7d76f7050618eae1f36cbff329bc6
1 parent
2b1317b1
UI: Added device provision to device profile
Showing
10 changed files
with
275 additions
and
8 deletions
... | ... | @@ -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() { | ... | ... |
ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html
0 → 100644
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> | ... | ... |
ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts
0 → 100644
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" | ... | ... |