Browse Source

Merge pull request #4871 from vvlladd28/improvement/device-credential/select-type

UI: Improvement device credentials
pull/4901/head
Igor Kulikov 5 years ago
committed by GitHub
parent
commit
ccb04f34a1
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 0
      ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m-server.component.html
  2. 10
      ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m-server.component.ts
  3. 25
      ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.html
  4. 0
      ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.scss
  5. 21
      ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.ts
  6. 48
      ui-ngx/src/app/modules/home/components/device/device-credentials-mqtt-basic.component.html
  7. 133
      ui-ngx/src/app/modules/home/components/device/device-credentials-mqtt-basic.component.ts
  8. 85
      ui-ngx/src/app/modules/home/components/device/device-credentials.component.html
  9. 93
      ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts
  10. 46
      ui-ngx/src/app/modules/home/components/device/device-credentials.module.ts
  11. 16
      ui-ngx/src/app/modules/home/components/home-components.module.ts
  12. 4
      ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html
  13. 16
      ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts
  14. 27
      ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.html
  15. 41
      ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.ts
  16. 2
      ui-ngx/src/app/modules/home/pages/device/device.module.ts
  17. 4
      ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts
  18. 14
      ui-ngx/src/app/shared/models/device.models.ts
  19. 3
      ui-ngx/src/assets/locale/locale.constant-en_US.json

0
ui-ngx/src/app/modules/home/components/device/security-config-lwm2m-server.component.html → ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m-server.component.html

10
ui-ngx/src/app/modules/home/components/device/security-config-lwm2m-server.component.ts → ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m-server.component.ts

@ -39,24 +39,24 @@ import { takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
@Component({
selector: 'tb-security-config-lwm2m-server',
templateUrl: './security-config-lwm2m-server.component.html',
selector: 'tb-device-credentials-lwm2m-server',
templateUrl: './device-credentials-lwm2m-server.component.html',
styleUrls: [],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => SecurityConfigLwm2mServerComponent),
useExisting: forwardRef(() => DeviceCredentialsLwm2mServerComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => SecurityConfigLwm2mServerComponent),
useExisting: forwardRef(() => DeviceCredentialsLwm2mServerComponent),
multi: true
}
]
})
export class SecurityConfigLwm2mServerComponent implements OnDestroy, ControlValueAccessor, Validator {
export class DeviceCredentialsLwm2mServerComponent implements OnDestroy, ControlValueAccessor, Validator {
serverFormGroup: FormGroup;
securityConfigLwM2MType = Lwm2mSecurityType;

25
ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.html → ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.html

@ -15,8 +15,8 @@
limitations under the License.
-->
<mat-tab-group>
<mat-tab label="{{ 'device.lwm2m-security-config.client-tab' | translate }}" [formGroup]="lwm2mConfigFormGroup">
<mat-tab-group [formGroup]="lwm2mConfigFormGroup">
<mat-tab label="{{ 'device.lwm2m-security-config.client-tab' | translate }}">
<ng-container formGroupName="client">
<mat-form-field class="mat-block">
<mat-label translate>device.lwm2m-security-config.endpoint</mat-label>
@ -79,7 +79,7 @@
</mat-form-field>
</ng-container>
</mat-tab>
<mat-tab label="{{ 'device.lwm2m-security-config.bootstrap-tab' | translate }}" [formGroup]="lwm2mConfigFormGroup">
<mat-tab label="{{ 'device.lwm2m-security-config.bootstrap-tab' | translate }}">
<div style="padding: 2px;" formGroupName="bootstrap">
<mat-accordion multi="true" class="mat-body-1">
<mat-expansion-panel>
@ -89,9 +89,9 @@
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<tb-security-config-lwm2m-server
<tb-device-credentials-lwm2m-server
formControlName="bootstrapServer">
</tb-security-config-lwm2m-server>
</tb-device-credentials-lwm2m-server>
</ng-template>
</mat-expansion-panel>
<mat-expansion-panel>
@ -101,23 +101,12 @@
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<tb-security-config-lwm2m-server
<tb-device-credentials-lwm2m-server
formControlName="lwm2mServer">
</tb-security-config-lwm2m-server>
</tb-device-credentials-lwm2m-server>
</ng-template>
</mat-expansion-panel>
</mat-accordion>
</div>
</mat-tab>
<mat-tab label="{{ 'device.lwm2m-security-config.config-json-tab' | translate }}">
<ng-template matTabContent>
<tb-json-object-edit
[ngModel]="lwm2mConfigFormGroup.value"
label="{{ 'device.lwm2m-value' | translate }}"
readonly
[required]="true"
[fillHeight]="false">
</tb-json-object-edit>
</ng-template>
</mat-tab>
</mat-tab-group>

0
ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.scss → ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.scss

21
ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.ts → ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.ts

@ -40,24 +40,24 @@ import { takeUntil } from 'rxjs/operators';
import { isDefinedAndNotNull } from '@core/utils';
@Component({
selector: 'tb-security-config-lwm2m',
templateUrl: './security-config-lwm2m.component.html',
styleUrls: ['./security-config-lwm2m.component.scss'],
selector: 'tb-device-credentials-lwm2m',
templateUrl: './device-credentials-lwm2m.component.html',
styleUrls: ['./device-credentials-lwm2m.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => SecurityConfigLwm2mComponent),
useExisting: forwardRef(() => DeviceCredentialsLwm2mComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => SecurityConfigLwm2mComponent),
useExisting: forwardRef(() => DeviceCredentialsLwm2mComponent),
multi: true
}
]
})
export class SecurityConfigLwm2mComponent implements ControlValueAccessor, Validator, OnDestroy {
export class DeviceCredentialsLwm2mComponent implements ControlValueAccessor, Validator, OnDestroy {
lwm2mConfigFormGroup: FormGroup;
securityConfigLwM2MType = Lwm2mSecurityType;
@ -89,6 +89,7 @@ export class SecurityConfigLwm2mComponent implements ControlValueAccessor, Valid
this.lwm2mConfigFormGroup.disable({emitEvent: false});
} else {
this.lwm2mConfigFormGroup.enable({emitEvent: false});
this.securityConfigClientUpdateValidators(this.lwm2mConfigFormGroup.get('client.securityConfigClientMode').value);
}
}
@ -126,21 +127,21 @@ export class SecurityConfigLwm2mComponent implements ControlValueAccessor, Valid
switch (mode) {
case Lwm2mSecurityType.NO_SEC:
this.setValidatorsNoSecX509();
this.lwm2mConfigFormGroup.get('client.cert').disable();
this.lwm2mConfigFormGroup.get('client.cert').disable({emitEvent: false});
break;
case Lwm2mSecurityType.X509:
this.setValidatorsNoSecX509();
this.lwm2mConfigFormGroup.get('client.cert').enable();
this.lwm2mConfigFormGroup.get('client.cert').enable({emitEvent: false});
break;
case Lwm2mSecurityType.PSK:
this.lenMaxKeyClient = LEN_MAX_PSK;
this.setValidatorsPskRpk(mode);
this.lwm2mConfigFormGroup.get('client.identity').enable();
this.lwm2mConfigFormGroup.get('client.identity').enable({emitEvent: false});
break;
case Lwm2mSecurityType.RPK:
this.lenMaxKeyClient = LEN_MAX_PUBLIC_KEY_RPK;
this.setValidatorsPskRpk(mode);
this.lwm2mConfigFormGroup.get('client.identity').disable();
this.lwm2mConfigFormGroup.get('client.identity').disable({emitEvent: false});
break;
}
this.lwm2mConfigFormGroup.get('client.identity').updateValueAndValidity({emitEvent: false});

48
ui-ngx/src/app/modules/home/components/device/device-credentials-mqtt-basic.component.html

@ -0,0 +1,48 @@
<!--
Copyright © 2016-2021 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.
-->
<section [formGroup]="deviceCredentialsMqttFormGroup">
<mat-form-field class="mat-block">
<mat-label translate>device.client-id</mat-label>
<input matInput formControlName="clientId">
<mat-error *ngIf="deviceCredentialsMqttFormGroup.get('clientId').hasError('pattern')">
{{ 'device.client-id-pattern' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>device.user-name</mat-label>
<input matInput formControlName="userName" [required]="!!deviceCredentialsMqttFormGroup.get('password').value">
<mat-error *ngIf="deviceCredentialsMqttFormGroup.get('userName').hasError('required')">
{{ 'device.user-name-required' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>device.password</mat-label>
<input matInput formControlName="password"
autocomplete="new-password"
(ngModelChange)="passwordChanged()"
[type]="hidePassword ? 'password' : 'text'">
<button mat-icon-button matSuffix type="button"
(click)="hidePassword = !hidePassword"
[attr.aria-pressed]="hidePassword">
<mat-icon>{{hidePassword ? 'visibility_off' : 'visibility'}}</mat-icon>
</button>
</mat-form-field>
<tb-error style="margin-top: -12px; display: block;"
[error]="deviceCredentialsMqttFormGroup.hasError('atLeastOne') ?
('device.client-id-or-user-name-necessary' | translate) : ''"></tb-error>
</section>

133
ui-ngx/src/app/modules/home/components/device/device-credentials-mqtt-basic.component.ts

@ -0,0 +1,133 @@
///
/// Copyright © 2016-2021 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, OnDestroy } from '@angular/core';
import {
ControlValueAccessor,
FormBuilder,
FormGroup,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
ValidationErrors,
Validator,
ValidatorFn,
Validators
} from '@angular/forms';
import { Subject } from 'rxjs';
import { DeviceCredentialMQTTBasic } from '@shared/models/device.models';
import { takeUntil } from 'rxjs/operators';
import { isDefinedAndNotNull, isEmptyStr } from '@core/utils';
@Component({
selector: 'tb-device-credentials-mqtt-basic',
templateUrl: './device-credentials-mqtt-basic.component.html',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DeviceCredentialsMqttBasicComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => DeviceCredentialsMqttBasicComponent),
multi: true,
}],
styleUrls: []
})
export class DeviceCredentialsMqttBasicComponent implements ControlValueAccessor, Validator, OnDestroy {
@Input()
disabled: boolean;
deviceCredentialsMqttFormGroup: FormGroup;
hidePassword = true;
private destroy$ = new Subject();
private propagateChange = (v: any) => {};
constructor(public fb: FormBuilder) {
this.deviceCredentialsMqttFormGroup = this.fb.group({
clientId: [null, [Validators.pattern(/^[A-Za-z0-9]+$/)]],
userName: [null],
password: [null]
}, {validators: this.atLeastOne(Validators.required, ['clientId', 'userName'])});
this.deviceCredentialsMqttFormGroup.valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe((value) => {
this.updateView(value);
});
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {}
setDisabledState(isDisabled: boolean) {
this.disabled = isDisabled;
if (this.disabled) {
this.deviceCredentialsMqttFormGroup.disable({emitEvent: false});
} else {
this.deviceCredentialsMqttFormGroup.enable({emitEvent: false});
}
}
validate(): ValidationErrors | null {
return this.deviceCredentialsMqttFormGroup.valid ? null : {
deviceCredentialsMqttBasic: false
};
}
writeValue(mqttBasic: string) {
if (isDefinedAndNotNull(mqttBasic) && !isEmptyStr(mqttBasic)) {
const value = JSON.parse(mqttBasic);
this.deviceCredentialsMqttFormGroup.patchValue(value, {emitEvent: false});
}
}
updateView(value: DeviceCredentialMQTTBasic) {
const formValue = JSON.stringify(value);
this.propagateChange(formValue);
}
passwordChanged() {
const value = this.deviceCredentialsMqttFormGroup.get('password').value;
if (value !== '') {
this.deviceCredentialsMqttFormGroup.get('userName').setValidators([Validators.required]);
} else {
this.deviceCredentialsMqttFormGroup.get('userName').setValidators([]);
}
this.deviceCredentialsMqttFormGroup.get('userName').updateValueAndValidity({emitEvent: false});
}
private atLeastOne(validator: ValidatorFn, controls: string[] = null) {
return (group: FormGroup): ValidationErrors | null => {
if (!controls) {
controls = Object.keys(group.controls);
}
const hasAtLeastOne = group?.controls && controls.some(k => !validator(group.controls[k]));
return hasAtLeastOne ? null : {atLeastOne: true};
};
}
}

85
ui-ngx/src/app/modules/home/components/device/device-credentials.component.html

@ -16,7 +16,7 @@
-->
<section [formGroup]="deviceCredentialsFormGroup">
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" [fxShow]="credentialsTypes?.length > 1">
<mat-label translate>device.credentials-type</mat-label>
<mat-select formControlName="credentialsType">
<mat-option *ngFor="let credentialsType of credentialsTypes" [value]="credentialsType">
@ -24,58 +24,35 @@
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field *ngIf="deviceCredentialsFormGroup.get('credentialsType').value === deviceCredentialsType.ACCESS_TOKEN"
class="mat-block">
<mat-label translate>device.access-token</mat-label>
<input matInput formControlName="credentialsId" required>
<mat-error *ngIf="deviceCredentialsFormGroup.get('credentialsId').hasError('required')">
{{ 'device.access-token-required' | translate }}
</mat-error>
<mat-error *ngIf="deviceCredentialsFormGroup.get('credentialsId').hasError('pattern')">
{{ 'device.access-token-invalid' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field *ngIf="deviceCredentialsFormGroup.get('credentialsType').value === deviceCredentialsType.X509_CERTIFICATE"
class="mat-block">
<mat-label translate>device.rsa-key</mat-label>
<textarea matInput formControlName="credentialsValue" cols="15" rows="5" required></textarea>
<mat-error *ngIf="deviceCredentialsFormGroup.get('credentialsValue').hasError('required')">
{{ 'device.rsa-key-required' | translate }}
</mat-error>
</mat-form-field>
<section *ngIf="deviceCredentialsFormGroup.get('credentialsType').value === deviceCredentialsType.MQTT_BASIC" formGroupName="credentialsBasic">
<mat-form-field class="mat-block">
<mat-label translate>device.client-id</mat-label>
<input matInput formControlName="clientId">
<mat-error *ngIf="deviceCredentialsFormGroup.get('credentialsBasic.clientId').hasError('pattern')">
{{ 'device.client-id-pattern' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>device.user-name</mat-label>
<input matInput formControlName="userName" [required]="!!deviceCredentialsFormGroup.get('credentialsBasic.password').value">
<mat-error *ngIf="deviceCredentialsFormGroup.get('credentialsBasic.userName').hasError('required')">
{{ 'device.user-name-required' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>device.password</mat-label>
<input matInput formControlName="password"
autocomplete="new-password"
(ngModelChange)="passwordChanged()"
[type]="hidePassword ? 'password' : 'text'">
<button mat-icon-button matSuffix type="button"
(click)="hidePassword = !hidePassword"
[attr.aria-pressed]="hidePassword">
<mat-icon>{{hidePassword ? 'visibility_off' : 'visibility'}}</mat-icon>
</button>
</mat-form-field>
<tb-error style="margin-top: -12px; display: block;"
[error]="deviceCredentialsFormGroup.get('credentialsBasic').hasError('atLeastOne') ?
('device.client-id-or-user-name-necessary' | translate) : ''"></tb-error>
</section>
<div *ngIf="deviceCredentialsFormGroup.get('credentialsType').value === deviceCredentialsType.LWM2M_CREDENTIALS">
<tb-security-config-lwm2m formControlName="credentialsValue">
</tb-security-config-lwm2m>
<div [ngSwitch]="deviceCredentialsFormGroup.get('credentialsType').value">
<ng-template [ngSwitchCase]="deviceCredentialsType.ACCESS_TOKEN">
<mat-form-field class="mat-block">
<mat-label translate>device.access-token</mat-label>
<input matInput formControlName="credentialsId" required>
<mat-error *ngIf="deviceCredentialsFormGroup.get('credentialsId').hasError('required')">
{{ 'device.access-token-required' | translate }}
</mat-error>
<mat-error *ngIf="deviceCredentialsFormGroup.get('credentialsId').hasError('pattern')">
{{ 'device.access-token-invalid' | translate }}
</mat-error>
</mat-form-field>
</ng-template>
<ng-template [ngSwitchCase]="deviceCredentialsType.X509_CERTIFICATE">
<mat-form-field class="mat-block">
<mat-label translate>device.rsa-key</mat-label>
<textarea matInput formControlName="credentialsValue" cols="15" rows="5" required></textarea>
<mat-error *ngIf="deviceCredentialsFormGroup.get('credentialsValue').hasError('required')">
{{ 'device.rsa-key-required' | translate }}
</mat-error>
</mat-form-field>
</ng-template>
<ng-template [ngSwitchCase]="deviceCredentialsType.MQTT_BASIC">
<tb-device-credentials-mqtt-basic formControlName="credentialsValue">
</tb-device-credentials-mqtt-basic>
</ng-template>
<ng-template [ngSwitchCase]="deviceCredentialsType.LWM2M_CREDENTIALS">
<tb-device-credentials-lwm2m formControlName="credentialsValue">
</tb-device-credentials-lwm2m>
</ng-template>
</div>
</section>

93
ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts

@ -22,16 +22,15 @@ import {
FormGroup,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
ValidationErrors,
Validator,
ValidatorFn,
Validators
} from '@angular/forms';
import {
credentialTypeNames,
DeviceCredentialMQTTBasic,
credentialTypesByTransportType,
DeviceCredentials,
DeviceCredentialsType
DeviceCredentialsType,
DeviceTransportType
} from '@shared/models/device.models';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@ -58,32 +57,40 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit,
@Input()
disabled: boolean;
private deviceTransportTypeValue = DeviceTransportType.DEFAULT;
get deviceTransportType(): DeviceTransportType {
return this.deviceTransportTypeValue;
}
@Input()
set deviceTransportType(type: DeviceTransportType) {
if (type) {
this.deviceTransportTypeValue = type;
this.credentialsTypes = credentialTypesByTransportType.get(type);
const currentType = this.deviceCredentialsFormGroup.get('credentialsType').value;
if (!this.credentialsTypes.includes(currentType)) {
this.deviceCredentialsFormGroup.get('credentialsType').patchValue(this.credentialsTypes[0], {onlySelf: true});
}
}
}
private destroy$ = new Subject();
deviceCredentialsFormGroup: FormGroup;
deviceCredentialsType = DeviceCredentialsType;
credentialsTypes = Object.values(DeviceCredentialsType);
credentialsTypes = credentialTypesByTransportType.get(DeviceTransportType.DEFAULT);
credentialTypeNamesMap = credentialTypeNames;
hidePassword = true;
private propagateChange = (v: any) => {};
constructor(public fb: FormBuilder) {
this.deviceCredentialsFormGroup = this.fb.group({
credentialsType: [DeviceCredentialsType.ACCESS_TOKEN],
credentialsId: [null],
credentialsValue: [null],
credentialsBasic: this.fb.group({
clientId: [null, [Validators.pattern(/^[A-Za-z0-9]+$/)]],
userName: [null],
password: [null]
}, {validators: this.atLeastOne(Validators.required, ['clientId', 'userName'])})
credentialsValue: [null]
});
this.deviceCredentialsFormGroup.get('credentialsBasic').disable();
this.deviceCredentialsFormGroup.valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe(() => {
@ -109,18 +116,11 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit,
writeValue(value: DeviceCredentials | null): void {
if (isDefinedAndNotNull(value)) {
let credentialsBasic = {clientId: null, userName: null, password: null};
let credentialsValue = null;
if (value.credentialsType === DeviceCredentialsType.MQTT_BASIC) {
credentialsBasic = JSON.parse(value.credentialsValue) as DeviceCredentialMQTTBasic;
} else {
credentialsValue = value.credentialsValue;
}
const credentialsType = this.credentialsTypes.includes(value.credentialsType) ? value.credentialsType : this.credentialsTypes[0];
this.deviceCredentialsFormGroup.patchValue({
credentialsType: value.credentialsType,
credentialsType,
credentialsId: value.credentialsId,
credentialsValue,
credentialsBasic
credentialsValue: value.credentialsValue
}, {emitEvent: false});
this.updateValidators();
}
@ -128,10 +128,6 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit,
updateView() {
const deviceCredentialsValue = this.deviceCredentialsFormGroup.value;
if (deviceCredentialsValue.credentialsType === DeviceCredentialsType.MQTT_BASIC) {
deviceCredentialsValue.credentialsValue = JSON.stringify(deviceCredentialsValue.credentialsBasic);
}
delete deviceCredentialsValue.credentialsBasic;
this.propagateChange(deviceCredentialsValue);
}
@ -163,14 +159,12 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit,
credentialsTypeChanged(): void {
this.deviceCredentialsFormGroup.patchValue({
credentialsId: null,
credentialsValue: null,
credentialsBasic: {clientId: '', userName: '', password: ''}
credentialsValue: null
});
this.updateValidators();
}
updateValidators(): void {
this.hidePassword = true;
const credentialsType = this.deviceCredentialsFormGroup.get('credentialsType').value as DeviceCredentialsType;
switch (credentialsType) {
case DeviceCredentialsType.ACCESS_TOKEN:
@ -178,48 +172,13 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit,
this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity({emitEvent: false});
this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([]);
this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity({emitEvent: false});
this.deviceCredentialsFormGroup.get('credentialsBasic').disable({emitEvent: false});
break;
case DeviceCredentialsType.X509_CERTIFICATE:
case DeviceCredentialsType.LWM2M_CREDENTIALS:
default:
this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([Validators.required]);
this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity({emitEvent: false});
this.deviceCredentialsFormGroup.get('credentialsId').setValidators([]);
this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity({emitEvent: false});
this.deviceCredentialsFormGroup.get('credentialsBasic').disable({emitEvent: false});
break;
case DeviceCredentialsType.MQTT_BASIC:
this.deviceCredentialsFormGroup.get('credentialsBasic').enable({emitEvent: false});
this.deviceCredentialsFormGroup.get('credentialsBasic').updateValueAndValidity({emitEvent: false});
this.deviceCredentialsFormGroup.get('credentialsId').setValidators([]);
this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity({emitEvent: false});
this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([]);
this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity({emitEvent: false});
break;
}
}
private atLeastOne(validator: ValidatorFn, controls: string[] = null) {
return (group: FormGroup): ValidationErrors | null => {
if (!controls) {
controls = Object.keys(group.controls);
}
const hasAtLeastOne = group?.controls && controls.some(k => !validator(group.controls[k]));
return hasAtLeastOne ? null : {atLeastOne: true};
};
}
passwordChanged() {
const value = this.deviceCredentialsFormGroup.get('credentialsBasic.password').value;
if (value !== '') {
this.deviceCredentialsFormGroup.get('credentialsBasic.userName').setValidators([Validators.required]);
} else {
this.deviceCredentialsFormGroup.get('credentialsBasic.userName').setValidators([]);
}
this.deviceCredentialsFormGroup.get('credentialsBasic.userName').updateValueAndValidity({
emitEvent: false,
onlySelf: true
});
}
}

46
ui-ngx/src/app/modules/home/components/device/device-credentials.module.ts

@ -0,0 +1,46 @@
///
/// Copyright © 2016-2021 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 { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SharedModule } from '@shared/shared.module';
import { CopyDeviceCredentialsComponent } from './copy-device-credentials.component';
import { DeviceCredentialsComponent } from './device-credentials.component';
import { DeviceCredentialsLwm2mComponent } from './device-credentials-lwm2m.component';
import { DeviceCredentialsLwm2mServerComponent } from './device-credentials-lwm2m-server.component';
import { DeviceCredentialsMqttBasicComponent } from './device-credentials-mqtt-basic.component';
@NgModule({
declarations: [
CopyDeviceCredentialsComponent,
DeviceCredentialsComponent,
DeviceCredentialsLwm2mComponent,
DeviceCredentialsLwm2mServerComponent,
DeviceCredentialsMqttBasicComponent
],
imports: [
CommonModule,
SharedModule
],
exports: [
CopyDeviceCredentialsComponent,
DeviceCredentialsComponent,
DeviceCredentialsLwm2mComponent,
DeviceCredentialsLwm2mServerComponent,
DeviceCredentialsMqttBasicComponent
]
})
export class DeviceCredentialsModule { }

16
ui-ngx/src/app/modules/home/components/home-components.module.ts

@ -110,7 +110,6 @@ import { RuleChainAutocompleteComponent } from '@home/components/rule-chain/rule
import { DeviceProfileProvisionConfigurationComponent } from '@home/components/profile/device-profile-provision-configuration.component';
import { AlarmScheduleComponent } from '@home/components/profile/alarm/alarm-schedule.component';
import { DeviceWizardDialogComponent } from '@home/components/wizard/device-wizard-dialog.component';
import { DeviceCredentialsComponent } from '@home/components/device/device-credentials.component';
import { AlarmScheduleInfoComponent } from '@home/components/profile/alarm/alarm-schedule-info.component';
import { AlarmScheduleDialogComponent } from '@home/components/profile/alarm/alarm-schedule-dialog.component';
import { EditAlarmDetailsDialogComponent } from '@home/components/profile/alarm/edit-alarm-details-dialog.component';
@ -120,7 +119,6 @@ import { TenantProfileConfigurationComponent } from '@home/components/profile/te
import { SmsProviderConfigurationComponent } from '@home/components/sms/sms-provider-configuration.component';
import { AwsSnsProviderConfigurationComponent } from '@home/components/sms/aws-sns-provider-configuration.component';
import { TwilioSmsProviderConfigurationComponent } from '@home/components/sms/twilio-sms-provider-configuration.component';
import { CopyDeviceCredentialsComponent } from '@home/components/device/copy-device-credentials.component';
import { Lwm2mProfileComponentsModule } from '@home/components/profile/device/lwm2m/lwm2m-profile-components.module';
import { DashboardPageComponent } from '@home/components/dashboard-page/dashboard-page.component';
import { DashboardToolbarComponent } from '@home/components/dashboard-page/dashboard-toolbar.component';
@ -139,11 +137,10 @@ import { EdgeDownlinkTableComponent } from '@home/components/edge/edge-downlink-
import { EdgeDownlinkTableHeaderComponent } from '@home/components/edge/edge-downlink-table-header.component';
import { DisplayWidgetTypesPanelComponent } from '@home/components/dashboard-page/widget-types-panel.component';
import { AlarmDurationPredicateValueComponent } from '@home/components/profile/alarm/alarm-duration-predicate-value.component';
import { SecurityConfigLwm2mComponent } from '@home/components/device/security-config-lwm2m.component';
import { SecurityConfigLwm2mServerComponent } from '@home/components/device/security-config-lwm2m-server.component';
import { DashboardImageDialogComponent } from '@home/components/dashboard-page/dashboard-image-dialog.component';
import { WidgetContainerComponent } from '@home/components/widget/widget-container.component';
import { SnmpDeviceProfileTransportModule } from '@home/components/profile/device/snpm/snmp-device-profile-transport.module';
import { DeviceCredentialsModule } from '@home/components/device/device-credentials.module';
@NgModule({
declarations:
@ -245,10 +242,6 @@ import { SnmpDeviceProfileTransportModule } from '@home/components/profile/devic
AlarmScheduleComponent,
AlarmDurationPredicateValueComponent,
DeviceWizardDialogComponent,
DeviceCredentialsComponent,
CopyDeviceCredentialsComponent,
SecurityConfigLwm2mComponent,
SecurityConfigLwm2mServerComponent,
AlarmScheduleDialogComponent,
EditAlarmDetailsDialogComponent,
SmsProviderConfigurationComponent,
@ -274,7 +267,8 @@ import { SnmpDeviceProfileTransportModule } from '@home/components/profile/devic
SharedHomeComponentsModule,
Lwm2mProfileComponentsModule,
SnmpDeviceProfileTransportModule,
StatesControllerModule
StatesControllerModule,
DeviceCredentialsModule
],
exports: [
EntitiesTableComponent,
@ -353,10 +347,6 @@ import { SnmpDeviceProfileTransportModule } from '@home/components/profile/devic
AddDeviceProfileDialogComponent,
RuleChainAutocompleteComponent,
DeviceWizardDialogComponent,
DeviceCredentialsComponent,
CopyDeviceCredentialsComponent,
SecurityConfigLwm2mComponent,
SecurityConfigLwm2mServerComponent,
AlarmScheduleInfoComponent,
AlarmScheduleComponent,
AlarmScheduleDialogComponent,

4
ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html

@ -64,7 +64,8 @@
[ngClass]="{invisible: deviceWizardFormGroup.get('addProfileType').value !== 0}"
[addNewProfile]="false"
[selectDefaultProfile]="true"
[editProfileEnabled]="false">
[editProfileEnabled]="false"
(deviceProfileChanged)="deviceProfileChanged($event)">
</tb-device-profile-autocomplete>
<mat-form-field fxFlex class="mat-block"
[ngClass]="{invisible: deviceWizardFormGroup.get('addProfileType').value !== 1}">
@ -154,6 +155,7 @@
<mat-checkbox style="padding-bottom: 16px;" formControlName="setCredential">{{ 'device.wizard.add-credentials' | translate }}</mat-checkbox>
<tb-device-credentials
[fxShow]="credentialsFormGroup.get('setCredential').value"
[deviceTransportType]="deviceTransportType"
formControlName="credential">
</tb-device-credentials>
</form>

16
ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts

@ -25,6 +25,7 @@ import {
createDeviceProfileConfiguration,
createDeviceProfileTransportConfiguration,
DeviceProfile,
DeviceProfileInfo,
DeviceProfileType,
DeviceProvisionConfiguration,
DeviceProvisionType,
@ -91,6 +92,7 @@ export class DeviceWizardDialogComponent extends
serviceType = ServiceType.TB_RULE_ENGINE;
private subscriptions: Subscription[] = [];
private currentDeviceProfileTransportType = DeviceTransportType.DEFAULT;
constructor(protected store: Store<AppState>,
protected router: Router,
@ -265,6 +267,20 @@ export class DeviceWizardDialogComponent extends
}
}
get deviceTransportType(): DeviceTransportType {
if (this.deviceWizardFormGroup.get('addProfileType').value) {
return this.transportConfigFormGroup.get('transportType').value;
} else {
return this.currentDeviceProfileTransportType;
}
}
deviceProfileChanged(deviceProfile: DeviceProfileInfo) {
if (deviceProfile) {
this.currentDeviceProfileTransportType = deviceProfile.transportType;
}
}
private createDeviceProfile(): Observable<EntityId> {
if (this.deviceWizardFormGroup.get('addProfileType').value) {
const deviceProvisionConfiguration: DeviceProvisionConfiguration = this.provisionConfigFormGroup.get('provisionConfiguration').value;

27
ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.html

@ -17,7 +17,7 @@
-->
<form [formGroup]="deviceCredentialsFormGroup" (ngSubmit)="save()" style="min-width: 350px;">
<mat-toolbar color="primary">
<h2 translate>device.device-credentials</h2>
<h2>{{ 'device.device-credentials' | translate }}</h2>
<span fxFlex></span>
<button mat-icon-button
(click)="cancel()"
@ -25,15 +25,26 @@
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="(isLoading$ | async) && !loadingCredentials">
</mat-progress-bar>
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<div style="height: 4px;" *ngIf="!(isLoading$ | async) || loadingCredentials"></div>
<div mat-dialog-content>
<fieldset [disabled]="(isLoading$ | async) || isReadOnly">
<tb-device-credentials
formControlName="credential">
</tb-device-credentials>
</fieldset>
<section *ngIf="!loadingCredentials; else loadCredentials">
<fieldset [disabled]="(isLoading$ | async) || isReadOnly">
<tb-device-credentials
[deviceTransportType]="deviceTransportType"
formControlName="credential">
</tb-device-credentials>
</fieldset>
</section>
<ng-template #loadCredentials>
<div fxLayout="column" fxLayoutAlign="center center">
<mat-spinner color="accent" diameter="65" strokeWidth="4" style="margin-bottom: 18px"></mat-spinner>
<span class="mat-subheading-2" style="margin-bottom: 0">
{{ 'device.loading-device-credentials' | translate }}
</span>
</div>
</ng-template>
</div>
<div mat-dialog-actions fxLayoutAlign="end center">
<button mat-button color="primary"

41
ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.ts

@ -21,13 +21,16 @@ import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm } from '@angular/forms';
import { DeviceService } from '@core/http/device.service';
import { credentialTypeNames, DeviceCredentials, DeviceCredentialsType } from '@shared/models/device.models';
import { DeviceCredentials, DeviceProfileInfo, DeviceTransportType } from '@shared/models/device.models';
import { DialogComponent } from '@shared/components/dialog.component';
import { Router } from '@angular/router';
import { DeviceProfileService } from '@core/http/device-profile.service';
import { forkJoin } from 'rxjs';
export interface DeviceCredentialsDialogData {
isReadOnly: boolean;
deviceId: string;
deviceProfileId: string;
}
@Component({
@ -40,25 +43,18 @@ export class DeviceCredentialsDialogComponent extends
DialogComponent<DeviceCredentialsDialogComponent, DeviceCredentials> implements OnInit, ErrorStateMatcher {
deviceCredentialsFormGroup: FormGroup;
deviceTransportType: DeviceTransportType;
isReadOnly: boolean;
loadingCredentials = true;
deviceCredentials: DeviceCredentials;
submitted = false;
deviceCredentialsType = DeviceCredentialsType;
credentialsTypes = Object.keys(DeviceCredentialsType);
credentialTypeNamesMap = credentialTypeNames;
hidePassword = true;
private deviceCredentials: DeviceCredentials;
private submitted = false;
constructor(protected store: Store<AppState>,
protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: DeviceCredentialsDialogData,
private deviceService: DeviceService,
private deviceProfileService: DeviceProfileService,
@SkipSelf() private errorStateMatcher: ErrorStateMatcher,
public dialogRef: MatDialogRef<DeviceCredentialsDialogComponent, DeviceCredentials>,
public fb: FormBuilder) {
@ -84,14 +80,17 @@ export class DeviceCredentialsDialogComponent extends
}
loadDeviceCredentials() {
this.deviceService.getDeviceCredentials(this.data.deviceId).subscribe(
(deviceCredentials) => {
this.deviceCredentials = deviceCredentials;
this.deviceCredentialsFormGroup.patchValue({
credential: deviceCredentials
}, {emitEvent: false});
}
);
const task = [];
task.push(this.deviceService.getDeviceCredentials(this.data.deviceId));
task.push(this.deviceProfileService.getDeviceProfileInfo(this.data.deviceProfileId));
forkJoin(task).subscribe(([deviceCredentials, deviceProfile]: [DeviceCredentials, DeviceProfileInfo]) => {
this.deviceTransportType = deviceProfile.transportType;
this.deviceCredentials = deviceCredentials;
this.deviceCredentialsFormGroup.patchValue({
credential: deviceCredentials
}, {emitEvent: false});
this.loadingCredentials = false;
});
}
cancel(): void {

2
ui-ngx/src/app/modules/home/pages/device/device.module.ts

@ -33,6 +33,7 @@ import { MqttDeviceTransportConfigurationComponent } from './data/mqtt-device-tr
import { CoapDeviceTransportConfigurationComponent } from './data/coap-device-transport-configuration.component';
import { Lwm2mDeviceTransportConfigurationComponent } from './data/lwm2m-device-transport-configuration.component';
import { SnmpDeviceTransportConfigurationComponent } from './data/snmp-device-transport-configuration.component';
import { DeviceCredentialsModule } from '@home/components/device/device-credentials.module';
@NgModule({
declarations: [
@ -55,6 +56,7 @@ import { SnmpDeviceTransportConfigurationComponent } from './data/snmp-device-tr
SharedModule,
HomeComponentsModule,
HomeDialogsModule,
DeviceCredentialsModule,
DeviceRoutingModule
]
})

4
ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts

@ -112,7 +112,8 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
));
};
this.config.onEntityAction = action => this.onDeviceAction(action);
this.config.detailsReadonly = () => (this.config.componentsData.deviceScope === 'customer_user' || this.config.componentsData.deviceScope === 'edge_customer_user');
this.config.detailsReadonly = () =>
(this.config.componentsData.deviceScope === 'customer_user' || this.config.componentsData.deviceScope === 'edge_customer_user');
this.config.headerComponent = DeviceTableHeaderComponent;
@ -528,6 +529,7 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
deviceId: device.id.id,
deviceProfileId: device.deviceProfileId.id,
isReadOnly: this.config.componentsData.deviceScope === 'customer_user' || this.config.componentsData.deviceScope === 'edge_customer_user'
}
}).afterClosed().subscribe(deviceCredentials => {

14
ui-ngx/src/app/shared/models/device.models.ts

@ -682,6 +682,20 @@ export const credentialTypeNames = new Map<DeviceCredentialsType, string>(
]
);
export const credentialTypesByTransportType = new Map<DeviceTransportType, DeviceCredentialsType[]>(
[
[DeviceTransportType.DEFAULT, [
DeviceCredentialsType.ACCESS_TOKEN, DeviceCredentialsType.X509_CERTIFICATE, DeviceCredentialsType.MQTT_BASIC
]],
[DeviceTransportType.MQTT, [
DeviceCredentialsType.ACCESS_TOKEN, DeviceCredentialsType.X509_CERTIFICATE, DeviceCredentialsType.MQTT_BASIC
]],
[DeviceTransportType.COAP, [DeviceCredentialsType.ACCESS_TOKEN, DeviceCredentialsType.X509_CERTIFICATE]],
[DeviceTransportType.LWM2M, [DeviceCredentialsType.LWM2M_CREDENTIALS]],
[DeviceTransportType.SNMP, [DeviceCredentialsType.ACCESS_TOKEN]]
]
);
export interface DeviceCredentials extends BaseData<DeviceCredentialsId> {
deviceId: DeviceId;
credentialsType: DeviceCredentialsType;

3
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -941,13 +941,13 @@
"unassign-devices-title": "Are you sure you want to unassign { count, plural, 1 {1 device} other {# devices} }?",
"unassign-devices-text": "After the confirmation all selected devices will be unassigned and won't be accessible by the customer.",
"device-credentials": "Device Credentials",
"loading-device-credentials": "Loading device credentials...",
"credentials-type": "Credentials type",
"access-token": "Access token",
"access-token-required": "Access token is required.",
"access-token-invalid": "Access token length must be from 1 to 20 characters.",
"rsa-key": "RSA public key",
"rsa-key-required": "RSA public key is required.",
"lwm2m-value": "LwM2M Security config",
"lwm2m-security-config": {
"identity": "Client Identity",
"identity-required": "Client Identity is required.",
@ -971,7 +971,6 @@
"client-secret-key-required": "Client Secret Key is required.",
"client-secret-key-pattern": "Client Secret Key must be hexadecimal format.",
"client-secret-key-length": "Client Secret Key must be {{ count }} characters.",
"config-json-tab": "Json Client Security Config",
"client-public-key": "Client public key",
"client-public-key-hint": "If client public key is empty, the trusted certificate will be used"
},

Loading…
Cancel
Save