From e740e4368a8d5b6e326032c5c1c36068c0065177 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 29 Sep 2020 10:18:32 +0300 Subject: [PATCH 1/4] Added parameter filter for transport type in deviceProfile API --- .../controller/DeviceProfileController.java | 5 ++-- .../dao/device/DeviceProfileService.java | 2 +- .../server/dao/device/DeviceProfileDao.java | 2 +- .../dao/device/DeviceProfileServiceImpl.java | 4 ++-- .../sql/device/DeviceProfileRepository.java | 9 ++++++++ .../dao/sql/device/JpaDeviceProfileDao.java | 23 ++++++++++++++----- .../service/BaseDeviceProfileServiceTest.java | 4 ++-- 7 files changed, 35 insertions(+), 14 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java index b474a0c0f1..efb5c2e6d6 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java @@ -192,10 +192,11 @@ public class DeviceProfileController extends BaseController { @RequestParam int page, @RequestParam(required = false) String textSearch, @RequestParam(required = false) String sortProperty, - @RequestParam(required = false) String sortOrder) throws ThingsboardException { + @RequestParam(required = false) String sortOrder, + @RequestParam(required = false) String transportType) throws ThingsboardException { try { PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - return checkNotNull(deviceProfileService.findDeviceProfileInfos(getTenantId(), pageLink)); + return checkNotNull(deviceProfileService.findDeviceProfileInfos(getTenantId(), pageLink, transportType)); } catch (Exception e) { throw handleException(e); } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProfileService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProfileService.java index e38bac68e5..b4ec680aaf 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProfileService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProfileService.java @@ -37,7 +37,7 @@ public interface DeviceProfileService { PageData findDeviceProfiles(TenantId tenantId, PageLink pageLink); - PageData findDeviceProfileInfos(TenantId tenantId, PageLink pageLink); + PageData findDeviceProfileInfos(TenantId tenantId, PageLink pageLink, String transportType); DeviceProfile findOrCreateDeviceProfile(TenantId tenantId, String profileName); diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java index 267aff358e..907fc511d9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java @@ -32,7 +32,7 @@ public interface DeviceProfileDao extends Dao { PageData findDeviceProfiles(TenantId tenantId, PageLink pageLink); - PageData findDeviceProfileInfos(TenantId tenantId, PageLink pageLink); + PageData findDeviceProfileInfos(TenantId tenantId, PageLink pageLink, String transportType); DeviceProfile findDefaultDeviceProfile(TenantId tenantId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java index b40ad102eb..afd73f4912 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java @@ -178,11 +178,11 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D } @Override - public PageData findDeviceProfileInfos(TenantId tenantId, PageLink pageLink) { + public PageData findDeviceProfileInfos(TenantId tenantId, PageLink pageLink, String transportType) { log.trace("Executing findDeviceProfileInfos tenantId [{}], pageLink [{}]", tenantId, pageLink); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); Validator.validatePageLink(pageLink); - return deviceProfileDao.findDeviceProfileInfos(tenantId, pageLink); + return deviceProfileDao.findDeviceProfileInfos(tenantId, pageLink, transportType); } @Cacheable(cacheNames = DEVICE_PROFILE_CACHE, key = "{#tenantId.id, #name}") diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java index 8116b711d5..f017d24dd7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java @@ -22,6 +22,7 @@ import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileInfo; +import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.dao.model.sql.DeviceProfileEntity; import java.util.UUID; @@ -46,6 +47,14 @@ public interface DeviceProfileRepository extends PagingAndSortingRepository findDeviceProfileInfos(@Param("tenantId") UUID tenantId, + @Param("textSearch") String textSearch, + @Param("transportType") DeviceTransportType transportType, + Pageable pageable); + @Query("SELECT d FROM DeviceProfileEntity d " + "WHERE d.tenantId = :tenantId AND d.isDefault = true") DeviceProfileEntity findByDefaultTrueAndTenantId(@Param("tenantId") UUID tenantId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java index 9399d27304..55d961dd71 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java @@ -15,11 +15,13 @@ */ package org.thingsboard.server.dao.sql.device; +import org.apache.commons.lang.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileInfo; +import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -62,12 +64,21 @@ public class JpaDeviceProfileDao extends JpaAbstractSearchTextDao findDeviceProfileInfos(TenantId tenantId, PageLink pageLink) { - return DaoUtil.pageToPageData( - deviceProfileRepository.findDeviceProfileInfos( - tenantId.getId(), - Objects.toString(pageLink.getTextSearch(), ""), - DaoUtil.toPageable(pageLink))); + public PageData findDeviceProfileInfos(TenantId tenantId, PageLink pageLink, String transportType) { + if (StringUtils.isNotEmpty(transportType)) { + return DaoUtil.pageToPageData( + deviceProfileRepository.findDeviceProfileInfos( + tenantId.getId(), + Objects.toString(pageLink.getTextSearch(), ""), + DeviceTransportType.valueOf(transportType), + DaoUtil.toPageable(pageLink))); + } else { + return DaoUtil.pageToPageData( + deviceProfileRepository.findDeviceProfileInfos( + tenantId.getId(), + Objects.toString(pageLink.getTextSearch(), ""), + DaoUtil.toPageable(pageLink))); + } } @Override diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java index dfaa46e7c1..132e016f5f 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java @@ -260,7 +260,7 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest { pageLink = new PageLink(17); PageData pageData; do { - pageData = deviceProfileService.findDeviceProfileInfos(tenantId, pageLink); + pageData = deviceProfileService.findDeviceProfileInfos(tenantId, pageLink, null); loadedDeviceProfileInfos.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -284,7 +284,7 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest { } pageLink = new PageLink(17); - pageData = deviceProfileService.findDeviceProfileInfos(tenantId, pageLink); + pageData = deviceProfileService.findDeviceProfileInfos(tenantId, pageLink, null); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(1, pageData.getTotalElements()); } From 4f30678554671117705fe53ff2a63112ecf0ab2f Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 29 Sep 2020 10:33:29 +0300 Subject: [PATCH 2/4] UI Moved device credentials for new component --- .../device/device-credentials.component.html | 77 ++++++ .../device/device-credentials.component.ts | 229 ++++++++++++++++++ .../home/components/home-components.module.ts | 7 +- .../device-credentials-dialog.component.html | 62 +---- .../device-credentials-dialog.component.ts | 111 +-------- 5 files changed, 320 insertions(+), 166 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/device/device-credentials.component.html create mode 100644 ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts diff --git a/ui-ngx/src/app/modules/home/components/device/device-credentials.component.html b/ui-ngx/src/app/modules/home/components/device/device-credentials.component.html new file mode 100644 index 0000000000..fa0c7e14a2 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/device/device-credentials.component.html @@ -0,0 +1,77 @@ + +
+ + device.credentials-type + + + {{ credentialTypeNamesMap.get(deviceCredentialsType[credentialsType]) }} + + + + + device.access-token + + + {{ 'device.access-token-required' | translate }} + + + {{ 'device.access-token-invalid' | translate }} + + + + device.rsa-key + + + {{ 'device.rsa-key-required' | translate }} + + +
+ + device.client-id + + + {{ 'device.client-id-pattern' | translate }} + + + + device.user-name + + + {{ 'device.user-name-required' | translate }} + + + + device.password + + + + + {{ 'device.client-id-or-user-name-necessary' | translate }} + +
+
diff --git a/ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts b/ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts new file mode 100644 index 0000000000..298270d153 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts @@ -0,0 +1,229 @@ +/// +/// 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, OnDestroy, OnInit } from '@angular/core'; +import { + ControlValueAccessor, + FormBuilder, + FormControl, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + ValidationErrors, + Validator, + ValidatorFn, + Validators +} from '@angular/forms'; +import { + credentialTypeNames, + DeviceCredentialMQTTBasic, + DeviceCredentials, + DeviceCredentialsType +} from '@shared/models/device.models'; +import { Subscription } from 'rxjs'; +import { isDefinedAndNotNull } from '@core/utils'; +import { distinctUntilChanged } from 'rxjs/operators'; + +@Component({ + selector: 'tb-device-credentials', + templateUrl: './device-credentials.component.html', + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => DeviceCredentialsComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => DeviceCredentialsComponent), + multi: true, + }], + styleUrls: [] +}) +export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit, Validator, OnDestroy { + + deviceCredentialsFormGroup: FormGroup; + + subscriptions: Subscription[] = []; + + @Input() + disabled: boolean; + + deviceCredentials: DeviceCredentials = null; + + submitted = false; + + deviceCredentialsType = DeviceCredentialsType; + + credentialsTypes = Object.keys(DeviceCredentialsType); + + 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'])}) + }); + this.deviceCredentialsFormGroup.get('credentialsBasic').disable(); + this.subscriptions.push( + this.deviceCredentialsFormGroup.valueChanges.pipe(distinctUntilChanged()).subscribe(() => { + this.updateView(); + }) + ); + this.subscriptions.push( + this.deviceCredentialsFormGroup.get('credentialsType').valueChanges.subscribe(() => { + this.credentialsTypeChanged(); + }) + ); + } + + ngOnInit(): void { + if (this.disabled) { + this.deviceCredentialsFormGroup.disable({emitEvent: false}); + } + } + + ngOnDestroy() { + this.subscriptions.forEach(s => s.unsubscribe()); + } + + writeValue(value: DeviceCredentials | null): void { + if (isDefinedAndNotNull(value)) { + this.deviceCredentials = 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; + } + this.deviceCredentialsFormGroup.patchValue({ + credentialsType: value.credentialsType, + credentialsId: value.credentialsId, + credentialsValue, + credentialsBasic + }, {emitEvent: false}); + this.updateValidators(); + } + } + + updateView() { + const deviceCredentialsValue = this.deviceCredentialsFormGroup.value; + if (deviceCredentialsValue.credentialsType === DeviceCredentialsType.MQTT_BASIC) { + deviceCredentialsValue.credentialsValue = JSON.stringify(deviceCredentialsValue.credentialsBasic); + } + delete deviceCredentialsValue.credentialsBasic; + this.propagateChange(deviceCredentialsValue); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void {} + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.deviceCredentialsFormGroup.disable({emitEvent: false}); + } else { + this.deviceCredentialsFormGroup.enable({emitEvent: false}); + this.updateValidators(); + } + } + + public validate(c: FormControl) { + return this.deviceCredentialsFormGroup.valid ? null : { + deviceCredentials: { + valid: false, + }, + }; + } + + credentialsTypeChanged(): void { + this.deviceCredentialsFormGroup.patchValue({ + credentialsId: null, + credentialsValue: null, + credentialsBasic: {clientId: '', userName: '', password: ''} + }); + this.updateValidators(); + } + + updateValidators(): void { + this.hidePassword = true; + const crendetialsType = this.deviceCredentialsFormGroup.get('credentialsType').value as DeviceCredentialsType; + switch (crendetialsType) { + case DeviceCredentialsType.ACCESS_TOKEN: + this.deviceCredentialsFormGroup.get('credentialsId').setValidators([Validators.required, Validators.pattern(/^.{1,20}$/)]); + 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: + 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}); + } + } + + 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]); + if (this.deviceCredentialsFormGroup.get('credentialsBasic.userName').untouched) { + this.deviceCredentialsFormGroup.get('credentialsBasic.userName').markAsTouched({onlySelf: true}); + } + } else { + this.deviceCredentialsFormGroup.get('credentialsBasic.userName').setValidators([]); + } + this.deviceCredentialsFormGroup.get('credentialsBasic.userName').updateValueAndValidity({ + emitEvent: false, + onlySelf: true + }); + } +} diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index d583369016..2bbf2cb01d 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -107,6 +107,7 @@ import { AlarmRuleKeyFiltersDialogComponent } from './profile/alarm/alarm-rule-k import { FilterTextComponent } from './filter/filter-text.component'; import { AddDeviceProfileDialogComponent } from './profile/add-device-profile-dialog.component'; import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomplete.component'; +import { DeviceCredentialsComponent } from './device/device-credentials.component'; @NgModule({ declarations: @@ -196,7 +197,8 @@ import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomp DeviceProfileComponent, DeviceProfileDialogComponent, AddDeviceProfileDialogComponent, - RuleChainAutocompleteComponent + RuleChainAutocompleteComponent, + DeviceCredentialsComponent ], imports: [ CommonModule, @@ -275,7 +277,8 @@ import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomp DeviceProfileComponent, DeviceProfileDialogComponent, AddDeviceProfileDialogComponent, - RuleChainAutocompleteComponent + RuleChainAutocompleteComponent, + DeviceCredentialsComponent ], providers: [ WidgetComponentService, diff --git a/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.html b/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.html index f1b5b442ce..0c99fd7040 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.html @@ -30,65 +30,9 @@
- - device.credentials-type - - - {{ credentialTypeNamesMap.get(deviceCredentialsType[credentialsType]) }} - - - - - device.access-token - - - {{ 'device.access-token-required' | translate }} - - - {{ 'device.access-token-invalid' | translate }} - - - - device.rsa-key - - - {{ 'device.rsa-key-required' | translate }} - - -
- - device.client-id - - - {{ 'device.client-id-pattern' | translate }} - - - - device.user-name - - - {{ 'device.user-name-required' | translate }} - - - - device.password - - - - - {{ 'device.client-id-or-user-name-necessary' | translate }} - -
+ +
diff --git a/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.ts index 73e5ae040f..94083cc10b 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.ts @@ -19,23 +19,9 @@ import { ErrorStateMatcher } from '@angular/material/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { - FormBuilder, - FormControl, - FormGroup, - FormGroupDirective, - NgForm, - ValidationErrors, - ValidatorFn, - Validators -} from '@angular/forms'; +import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm } from '@angular/forms'; import { DeviceService } from '@core/http/device.service'; -import { - credentialTypeNames, - DeviceCredentialMQTTBasic, - DeviceCredentials, - DeviceCredentialsType -} from '@shared/models/device.models'; +import { credentialTypeNames, DeviceCredentials, DeviceCredentialsType } from '@shared/models/device.models'; import { DialogComponent } from '@shared/components/dialog.component'; import { Router } from '@angular/router'; @@ -83,19 +69,10 @@ export class DeviceCredentialsDialogComponent extends ngOnInit(): void { this.deviceCredentialsFormGroup = this.fb.group({ - credentialsType: [DeviceCredentialsType.ACCESS_TOKEN], - credentialsId: [''], - credentialsValue: [''], - credentialsBasic: this.fb.group({ - clientId: ['', [Validators.pattern(/^[A-Za-z0-9]+$/)]], - userName: [''], - password: [''] - }, {validators: this.atLeastOne(Validators.required, ['clientId', 'userName'])}) + credential: [null] }); if (this.isReadOnly) { this.deviceCredentialsFormGroup.disable({emitEvent: false}); - } else { - this.registerDisableOnLoadFormControl(this.deviceCredentialsFormGroup.get('credentialsType')); } this.loadDeviceCredentials(); } @@ -110,82 +87,20 @@ export class DeviceCredentialsDialogComponent extends this.deviceService.getDeviceCredentials(this.data.deviceId).subscribe( (deviceCredentials) => { this.deviceCredentials = deviceCredentials; - let credentialsValue = deviceCredentials.credentialsValue; - let credentialsBasic = {clientId: null, userName: null, password: null}; - if (deviceCredentials.credentialsType === DeviceCredentialsType.MQTT_BASIC) { - credentialsValue = null; - credentialsBasic = JSON.parse(deviceCredentials.credentialsValue) as DeviceCredentialMQTTBasic; - } this.deviceCredentialsFormGroup.patchValue({ - credentialsType: deviceCredentials.credentialsType, - credentialsId: deviceCredentials.credentialsId, - credentialsValue, - credentialsBasic - }); - this.updateValidators(); + credential: deviceCredentials + }, {emitEvent: false}); } ); } - credentialsTypeChanged(): void { - this.deviceCredentialsFormGroup.patchValue({ - credentialsId: null, - credentialsValue: null, - credentialsBasic: {clientId: '', userName: '', password: ''} - }, {emitEvent: true}); - this.updateValidators(); - } - - updateValidators(): void { - this.hidePassword = true; - const crendetialsType = this.deviceCredentialsFormGroup.get('credentialsType').value as DeviceCredentialsType; - switch (crendetialsType) { - case DeviceCredentialsType.ACCESS_TOKEN: - this.deviceCredentialsFormGroup.get('credentialsId').setValidators([Validators.required, Validators.pattern(/^.{1,20}$/)]); - this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity(); - this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([]); - this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity(); - this.deviceCredentialsFormGroup.get('credentialsBasic').disable(); - break; - case DeviceCredentialsType.X509_CERTIFICATE: - this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([Validators.required]); - this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity(); - this.deviceCredentialsFormGroup.get('credentialsId').setValidators([]); - this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity(); - this.deviceCredentialsFormGroup.get('credentialsBasic').disable(); - break; - case DeviceCredentialsType.MQTT_BASIC: - this.deviceCredentialsFormGroup.get('credentialsBasic').enable(); - this.deviceCredentialsFormGroup.get('credentialsBasic').updateValueAndValidity(); - this.deviceCredentialsFormGroup.get('credentialsId').setValidators([]); - this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity(); - this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([]); - this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity(); - } - } - - 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}; - }; - } - cancel(): void { this.dialogRef.close(null); } save(): void { this.submitted = true; - const deviceCredentialsValue = this.deviceCredentialsFormGroup.value; - if (deviceCredentialsValue.credentialsType === DeviceCredentialsType.MQTT_BASIC) { - deviceCredentialsValue.credentialsValue = JSON.stringify(deviceCredentialsValue.credentialsBasic); - } - delete deviceCredentialsValue.credentialsBasic; + const deviceCredentialsValue = this.deviceCredentialsFormGroup.value.credential; this.deviceCredentials = {...this.deviceCredentials, ...deviceCredentialsValue}; this.deviceService.saveDeviceCredentials(this.deviceCredentials).subscribe( (deviceCredentials) => { @@ -193,18 +108,4 @@ export class DeviceCredentialsDialogComponent extends } ); } - - passwordChanged() { - const value = this.deviceCredentialsFormGroup.get('credentialsBasic.password').value; - if (value !== '') { - this.deviceCredentialsFormGroup.get('credentialsBasic.userName').setValidators([Validators.required]); - if (this.deviceCredentialsFormGroup.get('credentialsBasic.userName').untouched) { - this.deviceCredentialsFormGroup.get('credentialsBasic.userName').markAsTouched({onlySelf: true}); - } - this.deviceCredentialsFormGroup.get('credentialsBasic.userName').updateValueAndValidity(); - } else { - this.deviceCredentialsFormGroup.get('credentialsBasic.userName').setValidators([]); - this.deviceCredentialsFormGroup.get('credentialsBasic.userName').updateValueAndValidity(); - } - } } From f2af7d0831b9359cebf76452510901cb26ce40c4 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 29 Sep 2020 15:47:17 +0300 Subject: [PATCH 3/4] UI add device wizard --- .../app/core/http/device-profile.service.ts | 12 +- .../home/components/home-components.module.ts | 3 + .../add-device-profile-dialog.component.ts | 3 +- ...device-profile-autocomplete.component.html | 8 +- .../device-profile-autocomplete.component.ts | 35 ++- .../device-wizard-dialog.component.html | 155 ++++++++++ .../device-wizard-dialog.component.scss | 38 +++ .../wizard/device-wizard-dialog.component.ts | 286 ++++++++++++++++++ .../device-profiles-table-config.resolver.ts | 3 +- .../device/devices-table-config.resolver.ts | 36 ++- .../assets/locale/locale.constant-en_US.json | 12 +- 11 files changed, 562 insertions(+), 29 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts diff --git a/ui-ngx/src/app/core/http/device-profile.service.ts b/ui-ngx/src/app/core/http/device-profile.service.ts index 8cdc8c188b..c535878fd2 100644 --- a/ui-ngx/src/app/core/http/device-profile.service.ts +++ b/ui-ngx/src/app/core/http/device-profile.service.ts @@ -20,7 +20,8 @@ import { PageLink } from '@shared/models/page/page-link'; import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; import { Observable } from 'rxjs'; import { PageData } from '@shared/models/page/page-data'; -import { DeviceProfile, DeviceProfileInfo } from '@shared/models/device.models'; +import { DeviceProfile, DeviceProfileInfo, DeviceTransportType } from '@shared/models/device.models'; +import { isDefinedAndNotNull } from '@core/utils'; @Injectable({ providedIn: 'root' @@ -59,8 +60,13 @@ export class DeviceProfileService { return this.http.get(`/api/deviceProfileInfo/${deviceProfileId}`, defaultHttpOptionsFromConfig(config)); } - public getDeviceProfileInfos(pageLink: PageLink, config?: RequestConfig): Observable> { - return this.http.get>(`/api/deviceProfileInfos${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config)); + public getDeviceProfileInfos(pageLink: PageLink, transportType?: DeviceTransportType, + config?: RequestConfig): Observable> { + let url = `/api/deviceProfileInfos${pageLink.toQuery()}`; + if (isDefinedAndNotNull(transportType)) { + url += `&transportType=${transportType}`; + } + return this.http.get>(url, defaultHttpOptionsFromConfig(config)); } } diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index 2bbf2cb01d..f3d912e16d 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -107,6 +107,7 @@ import { AlarmRuleKeyFiltersDialogComponent } from './profile/alarm/alarm-rule-k import { FilterTextComponent } from './filter/filter-text.component'; import { AddDeviceProfileDialogComponent } from './profile/add-device-profile-dialog.component'; import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomplete.component'; +import { DeviceWizardDialogComponent } from './wizard/device-wizard-dialog.component'; import { DeviceCredentialsComponent } from './device/device-credentials.component'; @NgModule({ @@ -198,6 +199,7 @@ import { DeviceCredentialsComponent } from './device/device-credentials.componen DeviceProfileDialogComponent, AddDeviceProfileDialogComponent, RuleChainAutocompleteComponent, + DeviceWizardDialogComponent, DeviceCredentialsComponent ], imports: [ @@ -278,6 +280,7 @@ import { DeviceCredentialsComponent } from './device/device-credentials.componen DeviceProfileDialogComponent, AddDeviceProfileDialogComponent, RuleChainAutocompleteComponent, + DeviceWizardDialogComponent, DeviceCredentialsComponent ], providers: [ diff --git a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts index 476c69488f..a88b250c7e 100644 --- a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts @@ -46,6 +46,7 @@ import { RuleChainId } from '@shared/models/id/rule-chain-id'; export interface AddDeviceProfileDialogData { deviceProfileName: string; + transportType: DeviceTransportType; } @Component({ @@ -97,7 +98,7 @@ export class AddDeviceProfileDialogComponent extends ); this.transportConfigFormGroup = this.fb.group( { - transportType: [DeviceTransportType.DEFAULT, [Validators.required]], + transportType: [data.transportType ? data.transportType : DeviceTransportType.DEFAULT, [Validators.required]], transportConfiguration: [createDeviceProfileTransportConfiguration(DeviceTransportType.DEFAULT), [Validators.required]] } diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.html index a8af9c2738..9941b1db94 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.html @@ -48,7 +48,7 @@
-
+
device-profile.no-device-profiles-found
@@ -56,10 +56,10 @@ {{ translate.get('device-profile.no-device-profiles-matching', {entity: truncate.transform(searchText, true, 6, '...')}) | async }} + + device-profile.create-new-device-profile + - - device-profile.create-new-device-profile -
diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.ts index c419b7f11c..835c31a477 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.ts @@ -19,7 +19,8 @@ import { ElementRef, EventEmitter, forwardRef, - Input, NgZone, + Input, + NgZone, OnInit, Output, ViewChild @@ -38,14 +39,7 @@ import { TruncatePipe } from '@shared//pipe/truncate.pipe'; import { ENTER } from '@angular/cdk/keycodes'; import { MatDialog } from '@angular/material/dialog'; import { DeviceProfileId } from '@shared/models/id/device-profile-id'; -import { - createDeviceProfileConfiguration, - createDeviceProfileTransportConfiguration, - DeviceProfile, - DeviceProfileInfo, - DeviceProfileType, - DeviceTransportType -} from '@shared/models/device.models'; +import { DeviceProfile, DeviceProfileInfo, DeviceProfileType, DeviceTransportType } from '@shared/models/device.models'; import { DeviceProfileService } from '@core/http/device-profile.service'; import { DeviceProfileDialogComponent, DeviceProfileDialogData } from './device-profile-dialog.component'; import { MatAutocomplete } from '@angular/material/autocomplete'; @@ -76,6 +70,12 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, @Input() editProfileEnabled = true; + @Input() + addNewProfile = true; + + @Input() + transportType: DeviceTransportType = null; + private requiredValue: boolean; get required(): boolean { return this.requiredValue; @@ -183,6 +183,11 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; + if (this.disabled) { + this.selectDeviceProfileFormGroup.disable(); + } else { + this.selectDeviceProfileFormGroup.enable(); + } } writeValue(value: DeviceProfileId | null): void { @@ -244,7 +249,7 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, property: 'name', direction: Direction.ASC }); - return this.deviceProfileService.getDeviceProfileInfos(pageLink, {ignoreLoading: true}).pipe( + return this.deviceProfileService.getDeviceProfileInfos(pageLink, this.transportType, {ignoreLoading: true}).pipe( map(pageData => { let data = pageData.data; if (this.displayAllOnEmpty) { @@ -280,9 +285,12 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, createDeviceProfile($event: Event, profileName: string) { $event.preventDefault(); const deviceProfile: DeviceProfile = { - name: profileName + name: profileName, + transportType: this.transportType } as DeviceProfile; - this.openDeviceProfileDialog(deviceProfile, true); + if (this.addNewProfile) { + this.openDeviceProfileDialog(deviceProfile, true); + } } editDeviceProfile($event: Event) { @@ -312,7 +320,8 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, disableClose: true, panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], data: { - deviceProfileName: deviceProfile.name + deviceProfileName: deviceProfile.name, + transportType: deviceProfile.transportType } }).afterClosed(); } diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html new file mode 100644 index 0000000000..c391bc848a --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html @@ -0,0 +1,155 @@ + +
+ +

device.add-device-text

+ + +
+ + +
+
+ + + check + + +
+ {{ 'device.wizard.device-details' | translate}} +
+ + device.name + + + {{ 'device.name-required' | translate }} + + + + device.label + + + + device-profile.transport-type + + + {{deviceTransportTypeTranslations.get(type) | translate}} + + + + {{ 'device-profile.transport-type-required' | translate }} + + + + {{ 'device.is-gateway' | translate }} + + + device.description + + +
+
+
+ +
+ {{ 'device.wizard.profile-configuration' | translate}} + + +
+ device.wizard.existing-device-profile + + +
+
+ +
+ device.wizard.new-device-profile + + device-profile.device-profile + + + {{ 'device-profile.device-profile-required' | translate }} + + +
+
+
+
+
+ +
+ {{ 'device-profile.transport-configuration' | translate }} + + +
+
+ +
+ {{'device-profile.alarm-rules' | translate: + {count: alarmRulesFormGroup.get('alarms').value ? + alarmRulesFormGroup.get('alarms').value.length : 0} }} + + +
+
+ + {{ 'device.wizard.specific-configuration' | translate }} +
+ + + {{ 'device.wizard.add-credential' | translate }} + + +
+
+
+
+
+ + +
+ + +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss new file mode 100644 index 0000000000..cafcce74b6 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss @@ -0,0 +1,38 @@ +/** + * 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. + */ +:host { + .mat-dialog-content { + display: flex; + flex-direction: column; + overflow: hidden; + + .mat-stepper-horizontal { + display: flex; + flex-direction: column; + overflow: hidden; + } + } +} + +:host ::ng-deep { + .mat-dialog-content { + .mat-stepper-horizontal { + .mat-horizontal-content-container { + overflow: auto; + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts new file mode 100644 index 0000000000..15efa868b2 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts @@ -0,0 +1,286 @@ +/// +/// 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, Inject, OnDestroy, SkipSelf, ViewChild } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Router } from '@angular/router'; +import { + createDeviceProfileConfiguration, + createDeviceProfileTransportConfiguration, + DeviceProfile, + DeviceProfileType, + DeviceTransportType, + deviceTransportTypeTranslationMap +} from '@shared/models/device.models'; +import { MatHorizontalStepper } from '@angular/material/stepper'; +import { AddEntityDialogData } from '@home/models/entity/entity-component.models'; +import { BaseData, HasId } from '@shared/models/base-data'; +import { EntityType } from '@shared/models/entity-type.models'; +import { DeviceProfileService } from '@core/http/device-profile.service'; +import { EntityId } from '@shared/models/id/entity-id'; +import { BehaviorSubject, Observable, of, Subscription } from 'rxjs'; +import { map, mergeMap, tap } from 'rxjs/operators'; +import { DeviceService } from '@core/http/device.service'; +import { ErrorStateMatcher } from '@angular/material/core'; +import { StepperSelectionEvent } from '@angular/cdk/stepper'; + +@Component({ + selector: 'tb-device-wizard', + templateUrl: './device-wizard-dialog.component.html', + providers: [], + styleUrls: ['./device-wizard-dialog.component.scss'] +}) +export class DeviceWizardDialogComponent extends + DialogComponent implements OnDestroy, ErrorStateMatcher { + + @ViewChild('addDeviceWizardStepper', {static: true}) addDeviceWizardStepper: MatHorizontalStepper; + + selectedIndex = 0; + + nextStepButtonLabel$ = new BehaviorSubject('action.continue'); + + createdProfile = false; + + entityType = EntityType; + + deviceTransportTypes = Object.keys(DeviceTransportType); + + deviceTransportTypeTranslations = deviceTransportTypeTranslationMap; + + deviceWizardFormGroup: FormGroup; + + profileConfigFormGroup: FormGroup; + + transportConfigFormGroup: FormGroup; + + alarmRulesFormGroup: FormGroup; + + specificConfigFormGroup: FormGroup; + + private subscriptions: Subscription[] = []; + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: AddEntityDialogData>, + @SkipSelf() private errorStateMatcher: ErrorStateMatcher, + public dialogRef: MatDialogRef, + private deviceProfileService: DeviceProfileService, + private deviceService: DeviceService, + private fb: FormBuilder) { + super(store, router, dialogRef); + this.deviceWizardFormGroup = this.fb.group({ + name: ['', Validators.required], + label: [''], + gateway: [false], + transportType: [DeviceTransportType.DEFAULT, Validators.required], + description: [''] + } + ); + + this.profileConfigFormGroup = this.fb.group({ + addProfileType: [0], + deviceProfileId: [null, Validators.required], + newDeviceProfileTitle: [{value: null, disabled: true}] + } + ); + + this.subscriptions.push(this.profileConfigFormGroup.get('addProfileType').valueChanges.subscribe( + (addProfileType: number) => { + if (addProfileType === 0) { + this.profileConfigFormGroup.get('deviceProfileId').setValidators([Validators.required]); + this.profileConfigFormGroup.get('deviceProfileId').enable(); + this.profileConfigFormGroup.get('newDeviceProfileTitle').setValidators(null); + this.profileConfigFormGroup.get('newDeviceProfileTitle').disable(); + this.profileConfigFormGroup.updateValueAndValidity(); + this.createdProfile = false; + } else { + this.profileConfigFormGroup.get('deviceProfileId').setValidators(null); + this.profileConfigFormGroup.get('deviceProfileId').disable(); + this.profileConfigFormGroup.get('newDeviceProfileTitle').setValidators([Validators.required]); + this.profileConfigFormGroup.get('newDeviceProfileTitle').enable(); + this.profileConfigFormGroup.updateValueAndValidity(); + this.createdProfile = true; + } + } + )); + + this.transportConfigFormGroup = this.fb.group( + { + transportConfiguration: [createDeviceProfileTransportConfiguration(DeviceTransportType.DEFAULT), Validators.required] + } + ); + this.subscriptions.push(this.deviceWizardFormGroup.get('transportType').valueChanges.subscribe((transportType) => { + this.deviceProfileTransportTypeChanged(transportType); + })); + + this.alarmRulesFormGroup = this.fb.group({ + alarms: [null] + } + ); + + this.specificConfigFormGroup = this.fb.group({ + customerId: [null], + setCredential: [false], + credential: [{value: null, disabled: true}] + } + ); + + this.subscriptions.push(this.specificConfigFormGroup.get('setCredential').valueChanges.subscribe((value) => { + if (value) { + this.specificConfigFormGroup.get('credential').enable(); + } else { + this.specificConfigFormGroup.get('credential').disable(); + } + })); + } + + ngOnDestroy() { + super.ngOnDestroy(); + this.subscriptions.forEach(s => s.unsubscribe()); + } + + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + const originalErrorState = this.errorStateMatcher.isErrorState(control, form); + const customErrorState = !!(control && control.invalid); + return originalErrorState || customErrorState; + } + + cancel(): void { + this.dialogRef.close(null); + } + + previousStep(): void { + this.addDeviceWizardStepper.previous(); + } + + nextStep(): void { + if (this.selectedIndex < this.maxStepperIndex) { + this.addDeviceWizardStepper.next(); + } else { + this.add(); + } + } + + get selectedForm(): FormGroup { + const index = !this.createdProfile && this.selectedIndex === this.maxStepperIndex ? 4 : this.selectedIndex; + switch (index) { + case 0: + return this.deviceWizardFormGroup; + case 1: + return this.profileConfigFormGroup; + case 2: + return this.transportConfigFormGroup; + case 3: + return this.alarmRulesFormGroup; + case 4: + return this.specificConfigFormGroup; + } + } + + get maxStepperIndex(): number { + return this.addDeviceWizardStepper?._steps?.length - 1; + } + + private deviceProfileTransportTypeChanged(deviceTransportType: DeviceTransportType): void { + this.transportConfigFormGroup.patchValue( + {transportConfiguration: createDeviceProfileTransportConfiguration(deviceTransportType)}); + } + + private add(): void { + this.creatProfile().pipe( + mergeMap(profileId => this.createdDevice(profileId)), + mergeMap(device => this.saveCredential(device)) + ).subscribe( + (created) => { + this.dialogRef.close(created); + } + ); + } + + private creatProfile(): Observable { + if (this.profileConfigFormGroup.get('addProfileType').value) { + const deviceProfile: DeviceProfile = { + name: this.profileConfigFormGroup.get('newDeviceProfileTitle').value, + type: DeviceProfileType.DEFAULT, + transportType: this.deviceWizardFormGroup.get('transportType').value, + profileData: { + configuration: createDeviceProfileConfiguration(DeviceProfileType.DEFAULT), + transportConfiguration: this.transportConfigFormGroup.get('transportConfiguration').value, + alarms: this.alarmRulesFormGroup.get('alarms').value + } + }; + return this.deviceProfileService.saveDeviceProfile(deviceProfile).pipe( + map(profile => profile.id), + tap((profileId) => { + this.profileConfigFormGroup.patchValue({ + deviceProfileId: profileId, + addProfileType: 0 + }); + this.addDeviceWizardStepper.selectedIndex = 2; + }) + ); + } else { + return of(null); + } + } + + private createdDevice(profileId: EntityId = this.profileConfigFormGroup.get('deviceProfileId').value): Observable> { + const device = { + name: this.deviceWizardFormGroup.get('name').value, + label: this.deviceWizardFormGroup.get('label').value, + deviceProfileId: profileId, + additionalInfo: { + gateway: this.deviceWizardFormGroup.get('gateway').value, + description: this.deviceWizardFormGroup.get('description').value + }, + customerId: null + }; + if (this.specificConfigFormGroup.get('customerId').value) { + device.customerId = { + entityType: EntityType.CUSTOMER, + id: this.specificConfigFormGroup.get('customerId').value + }; + } + return this.data.entitiesTableConfig.saveEntity(device); + } + + private saveCredential(device: BaseData): Observable { + if (this.specificConfigFormGroup.get('setCredential').value) { + return this.deviceService.getDeviceCredentials(device.id.id).pipe( + mergeMap( + (deviceCredentials) => { + const deviceCredentialsValue = {...deviceCredentials, ...this.specificConfigFormGroup.value.credential}; + return this.deviceService.saveDeviceCredentials(deviceCredentialsValue); + } + ), + map(() => true)); + } + return of(true); + } + + changeStep($event: StepperSelectionEvent): void { + this.selectedIndex = $event.selectedIndex; + if (this.selectedIndex === this.maxStepperIndex) { + this.nextStepButtonLabel$.next('action.add'); + } else { + this.nextStepButtonLabel$.next('action.continue'); + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/device-profile/device-profiles-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/device-profile/device-profiles-table-config.resolver.ts index c4927fbe99..d59c7225c5 100644 --- a/ui-ngx/src/app/modules/home/pages/device-profile/device-profiles-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/device-profile/device-profiles-table-config.resolver.ts @@ -114,7 +114,8 @@ export class DeviceProfilesTableConfigResolver implements Resolve> { @@ -221,7 +223,7 @@ export class DevicesTableConfigResolver implements Resolve true, + isEnabled: () => true, onAction: ($event, entity) => this.manageCredentials($event, entity) } ); @@ -243,7 +245,7 @@ export class DevicesTableConfigResolver implements Resolve true, + isEnabled: () => true, onAction: ($event, entity) => this.manageCredentials($event, entity) } ); @@ -253,7 +255,7 @@ export class DevicesTableConfigResolver implements Resolve true, + isEnabled: () => true, onAction: ($event, entity) => this.manageCredentials($event, entity) } ); @@ -301,7 +303,13 @@ export class DevicesTableConfigResolver implements Resolve true, onAction: ($event) => this.importDevices($event) - } + }, + { + name: this.translate.instant('device.wizard.device-wizard'), + icon: 'library_add', + isEnabled: () => true, + onAction: ($event) => this.deviceWizard($event) + }, ); } if (deviceScope === 'customer') { @@ -326,6 +334,23 @@ export class DevicesTableConfigResolver implements Resolve>, + boolean>(DeviceWizardDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + entitiesTableConfig: this.config.table.entitiesTableConfig + } + }).afterClosed().subscribe( + (res) => { + if (res) { + this.config.table.updateData(); + } + } + ); + } + addDevicesToCustomer($event: Event) { if ($event) { $event.stopPropagation(); @@ -480,5 +505,4 @@ export class DevicesTableConfigResolver implements Resolve Date: Fri, 9 Oct 2020 17:19:51 +0300 Subject: [PATCH 4/4] Add Device Wizard improvements --- .../add-device-profile-dialog.component.html | 2 +- .../device-profile-autocomplete.component.ts | 19 +- .../device-profile-data.component.html | 2 +- .../device-wizard-dialog.component.html | 122 ++++++------ .../device-wizard-dialog.component.scss | 46 ++++- .../wizard/device-wizard-dialog.component.ts | 175 +++++++++++------- .../device/devices-table-config.resolver.ts | 8 +- ui-ngx/src/app/shared/models/device.models.ts | 8 + .../assets/locale/locale.constant-en_US.json | 14 +- 9 files changed, 250 insertions(+), 146 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html index d7f119a3b7..df70ea4e8f 100644 --- a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html @@ -85,7 +85,7 @@
- {{'device-profile.alarm-rules' | translate: + {{'device-profile.alarm-rules-with-count' | translate: {count: alarmRulesFormGroup.get('alarms').value ? alarmRulesFormGroup.get('alarms').value.length : 0} }} { - if (profile) { + if (profile && !this.transportType || (profile.transportType === this.transportType)) { this.selectDeviceProfileFormGroup.get('deviceProfile').patchValue(profile, {emitEvent: false}); this.updateView(profile); } diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.html index daae838285..2cc3ccd9e2 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.html @@ -42,7 +42,7 @@ -
{{'device-profile.alarm-rules' | translate: +
{{'device-profile.alarm-rules-with-count' | translate: {count: deviceProfileDataFormGroup.get('alarms').value ? deviceProfileDataFormGroup.get('alarms').value.length : 0} }}
diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html index c391bc848a..6cf1e687e3 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html @@ -15,7 +15,7 @@ limitations under the License. --> -
+

device.add-device-text

@@ -29,7 +29,7 @@
- + check @@ -48,60 +48,60 @@ device.label - + device-profile.transport-type {{deviceTransportTypeTranslations.get(type) | translate}} + + {{deviceTransportTypeHints.get(deviceWizardFormGroup.get('transportType').value) | translate}} + {{ 'device-profile.transport-type-required' | translate }} - - {{ 'device.is-gateway' | translate }} - - - device.description - - - - - - -
- {{ 'device.wizard.profile-configuration' | translate}} - - -
- device.wizard.existing-device-profile +
+ + + device.wizard.existing-device-profile + + + device.wizard.new-device-profile + + +
-
-
- -
- device.wizard.new-device-profile - device-profile.device-profile + device-profile.new-device-profile-name - - {{ 'device-profile.device-profile-required' | translate }} + [required]="createProfile"> + + {{ 'device-profile.new-device-profile-name-required' | translate }} -
-
-
+
+
+ + {{ 'device.is-gateway' | translate }} + + + device.description + + + - +
{{ 'device-profile.transport-configuration' | translate }}
- +
- {{'device-profile.alarm-rules' | translate: + {{'device-profile.alarm-rules-with-count' | translate: {count: alarmRulesFormGroup.get('alarms').value ? alarmRulesFormGroup.get('alarms').value.length : 0} }}
- - {{ 'device.wizard.specific-configuration' | translate }} -
+ + {{ 'device.credentials' | translate }} + + {{ 'device.wizard.add-credential' | translate }} + + + +
+ + {{ 'customer.customer' | translate }} +
- {{ 'device.wizard.add-credential' | translate }} - -
-
- - -
+
+
+ +
+
- + +
+ + +
diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss index cafcce74b6..1426e0f201 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss @@ -13,25 +13,51 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -:host { - .mat-dialog-content { - display: flex; - flex-direction: column; - overflow: hidden; - .mat-stepper-horizontal { - display: flex; - flex-direction: column; - overflow: hidden; +@import "../../../../../scss/constants"; + +:host-context(.tb-fullscreen-dialog .mat-dialog-container) { + @media #{$mat-lt-sm} { + .mat-dialog-content { + max-height: 75vh; } } } :host ::ng-deep { .mat-dialog-content { + display: flex; + flex-direction: column; + height: 100%; + .mat-stepper-horizontal { + display: flex; + flex-direction: column; + height: 100%; + overflow: hidden; + @media #{$mat-lt-sm} { + .mat-step-label { + white-space: normal; + overflow: visible; + .mat-step-text-label { + overflow: visible; + } + } + } .mat-horizontal-content-container { - overflow: auto; + height: 450px; + max-height: 100%; + width: 100%;; + overflow-y: auto; + @media #{$mat-gt-sm} { + min-width: 800px; + } + } + .mat-horizontal-stepper-content[aria-expanded=true] { + height: 100%; + form { + height: 100%; + } } } } diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts index 15efa868b2..9def61c4b6 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts @@ -26,7 +26,7 @@ import { createDeviceProfileTransportConfiguration, DeviceProfile, DeviceProfileType, - DeviceTransportType, + DeviceTransportType, deviceTransportTypeHintMap, deviceTransportTypeTranslationMap } from '@shared/models/device.models'; import { MatHorizontalStepper } from '@angular/material/stepper'; @@ -35,11 +35,13 @@ import { BaseData, HasId } from '@shared/models/base-data'; import { EntityType } from '@shared/models/entity-type.models'; import { DeviceProfileService } from '@core/http/device-profile.service'; import { EntityId } from '@shared/models/id/entity-id'; -import { BehaviorSubject, Observable, of, Subscription } from 'rxjs'; +import { Observable, of, Subscription } from 'rxjs'; import { map, mergeMap, tap } from 'rxjs/operators'; import { DeviceService } from '@core/http/device.service'; import { ErrorStateMatcher } from '@angular/material/core'; import { StepperSelectionEvent } from '@angular/cdk/stepper'; +import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; +import { MediaBreakpoints } from '@shared/models/constants'; @Component({ selector: 'tb-device-wizard', @@ -54,9 +56,10 @@ export class DeviceWizardDialogComponent extends selectedIndex = 0; - nextStepButtonLabel$ = new BehaviorSubject('action.continue'); + showNext = true; - createdProfile = false; + createProfile = false; + createTransportConfiguration = false; entityType = EntityType; @@ -64,15 +67,19 @@ export class DeviceWizardDialogComponent extends deviceTransportTypeTranslations = deviceTransportTypeTranslationMap; - deviceWizardFormGroup: FormGroup; + deviceTransportTypeHints = deviceTransportTypeHintMap; - profileConfigFormGroup: FormGroup; + deviceWizardFormGroup: FormGroup; transportConfigFormGroup: FormGroup; alarmRulesFormGroup: FormGroup; - specificConfigFormGroup: FormGroup; + credentialsFormGroup: FormGroup; + + customerFormGroup: FormGroup; + + labelPosition = 'end'; private subscriptions: Subscription[] = []; @@ -83,6 +90,7 @@ export class DeviceWizardDialogComponent extends public dialogRef: MatDialogRef, private deviceProfileService: DeviceProfileService, private deviceService: DeviceService, + private breakpointObserver: BreakpointObserver, private fb: FormBuilder) { super(store, router, dialogRef); this.deviceWizardFormGroup = this.fb.group({ @@ -90,33 +98,32 @@ export class DeviceWizardDialogComponent extends label: [''], gateway: [false], transportType: [DeviceTransportType.DEFAULT, Validators.required], - description: [''] - } - ); - - this.profileConfigFormGroup = this.fb.group({ addProfileType: [0], deviceProfileId: [null, Validators.required], - newDeviceProfileTitle: [{value: null, disabled: true}] + newDeviceProfileTitle: [{value: null, disabled: true}], + description: [''] } ); - this.subscriptions.push(this.profileConfigFormGroup.get('addProfileType').valueChanges.subscribe( + this.subscriptions.push(this.deviceWizardFormGroup.get('addProfileType').valueChanges.subscribe( (addProfileType: number) => { if (addProfileType === 0) { - this.profileConfigFormGroup.get('deviceProfileId').setValidators([Validators.required]); - this.profileConfigFormGroup.get('deviceProfileId').enable(); - this.profileConfigFormGroup.get('newDeviceProfileTitle').setValidators(null); - this.profileConfigFormGroup.get('newDeviceProfileTitle').disable(); - this.profileConfigFormGroup.updateValueAndValidity(); - this.createdProfile = false; + this.deviceWizardFormGroup.get('deviceProfileId').setValidators([Validators.required]); + this.deviceWizardFormGroup.get('deviceProfileId').enable(); + this.deviceWizardFormGroup.get('newDeviceProfileTitle').setValidators(null); + this.deviceWizardFormGroup.get('newDeviceProfileTitle').disable(); + this.deviceWizardFormGroup.updateValueAndValidity(); + this.createProfile = false; + this.createTransportConfiguration = false; } else { - this.profileConfigFormGroup.get('deviceProfileId').setValidators(null); - this.profileConfigFormGroup.get('deviceProfileId').disable(); - this.profileConfigFormGroup.get('newDeviceProfileTitle').setValidators([Validators.required]); - this.profileConfigFormGroup.get('newDeviceProfileTitle').enable(); - this.profileConfigFormGroup.updateValueAndValidity(); - this.createdProfile = true; + this.deviceWizardFormGroup.get('deviceProfileId').setValidators(null); + this.deviceWizardFormGroup.get('deviceProfileId').disable(); + this.deviceWizardFormGroup.get('newDeviceProfileTitle').setValidators([Validators.required]); + this.deviceWizardFormGroup.get('newDeviceProfileTitle').enable(); + this.deviceWizardFormGroup.updateValueAndValidity(); + this.createProfile = true; + this.createTransportConfiguration = this.deviceWizardFormGroup.get('transportType').value && + DeviceTransportType.DEFAULT !== this.deviceWizardFormGroup.get('transportType').value; } } )); @@ -135,20 +142,37 @@ export class DeviceWizardDialogComponent extends } ); - this.specificConfigFormGroup = this.fb.group({ - customerId: [null], + this.credentialsFormGroup = this.fb.group({ setCredential: [false], credential: [{value: null, disabled: true}] } ); - this.subscriptions.push(this.specificConfigFormGroup.get('setCredential').valueChanges.subscribe((value) => { + this.subscriptions.push(this.credentialsFormGroup.get('setCredential').valueChanges.subscribe((value) => { if (value) { - this.specificConfigFormGroup.get('credential').enable(); + this.credentialsFormGroup.get('credential').enable(); } else { - this.specificConfigFormGroup.get('credential').disable(); + this.credentialsFormGroup.get('credential').disable(); } })); + + this.customerFormGroup = this.fb.group({ + customerId: [null] + } + ); + + this.labelPosition = this.breakpointObserver.isMatched(MediaBreakpoints['gt-sm']) ? 'end' : 'bottom'; + + this.subscriptions.push(this.breakpointObserver + .observe(MediaBreakpoints['gt-sm']) + .subscribe((state: BreakpointState) => { + if (state.matches) { + this.labelPosition = 'end'; + } else { + this.labelPosition = 'bottom'; + } + } + )); } ngOnDestroy() { @@ -171,26 +195,28 @@ export class DeviceWizardDialogComponent extends } nextStep(): void { - if (this.selectedIndex < this.maxStepperIndex) { - this.addDeviceWizardStepper.next(); - } else { - this.add(); - } + this.addDeviceWizardStepper.next(); } - get selectedForm(): FormGroup { - const index = !this.createdProfile && this.selectedIndex === this.maxStepperIndex ? 4 : this.selectedIndex; + getFormLabel(index: number): string { + if (index > 0) { + if (!this.createProfile) { + index += 2; + } else if (!this.createTransportConfiguration) { + index += 1; + } + } switch (index) { case 0: - return this.deviceWizardFormGroup; + return 'device.wizard.device-details'; case 1: - return this.profileConfigFormGroup; + return 'device-profile.transport-configuration'; case 2: - return this.transportConfigFormGroup; + return 'device-profile.alarm-rules'; case 3: - return this.alarmRulesFormGroup; + return 'device.credentials'; case 4: - return this.specificConfigFormGroup; + return 'customer.customer'; } } @@ -201,23 +227,27 @@ export class DeviceWizardDialogComponent extends private deviceProfileTransportTypeChanged(deviceTransportType: DeviceTransportType): void { this.transportConfigFormGroup.patchValue( {transportConfiguration: createDeviceProfileTransportConfiguration(deviceTransportType)}); + this.createTransportConfiguration = this.createProfile && deviceTransportType && + DeviceTransportType.DEFAULT !== deviceTransportType; } - private add(): void { - this.creatProfile().pipe( - mergeMap(profileId => this.createdDevice(profileId)), - mergeMap(device => this.saveCredential(device)) - ).subscribe( - (created) => { - this.dialogRef.close(created); - } - ); + add(): void { + if (this.allValid()) { + this.createDeviceProfile().pipe( + mergeMap(profileId => this.createDevice(profileId)), + mergeMap(device => this.saveCredentials(device)) + ).subscribe( + (created) => { + this.dialogRef.close(created); + } + ); + } } - private creatProfile(): Observable { - if (this.profileConfigFormGroup.get('addProfileType').value) { + private createDeviceProfile(): Observable { + if (this.deviceWizardFormGroup.get('addProfileType').value) { const deviceProfile: DeviceProfile = { - name: this.profileConfigFormGroup.get('newDeviceProfileTitle').value, + name: this.deviceWizardFormGroup.get('newDeviceProfileTitle').value, type: DeviceProfileType.DEFAULT, transportType: this.deviceWizardFormGroup.get('transportType').value, profileData: { @@ -229,11 +259,10 @@ export class DeviceWizardDialogComponent extends return this.deviceProfileService.saveDeviceProfile(deviceProfile).pipe( map(profile => profile.id), tap((profileId) => { - this.profileConfigFormGroup.patchValue({ + this.deviceWizardFormGroup.patchValue({ deviceProfileId: profileId, addProfileType: 0 }); - this.addDeviceWizardStepper.selectedIndex = 2; }) ); } else { @@ -241,7 +270,7 @@ export class DeviceWizardDialogComponent extends } } - private createdDevice(profileId: EntityId = this.profileConfigFormGroup.get('deviceProfileId').value): Observable> { + private createDevice(profileId: EntityId = this.deviceWizardFormGroup.get('deviceProfileId').value): Observable> { const device = { name: this.deviceWizardFormGroup.get('name').value, label: this.deviceWizardFormGroup.get('label').value, @@ -252,21 +281,21 @@ export class DeviceWizardDialogComponent extends }, customerId: null }; - if (this.specificConfigFormGroup.get('customerId').value) { + if (this.customerFormGroup.get('customerId').value) { device.customerId = { entityType: EntityType.CUSTOMER, - id: this.specificConfigFormGroup.get('customerId').value + id: this.customerFormGroup.get('customerId').value }; } return this.data.entitiesTableConfig.saveEntity(device); } - private saveCredential(device: BaseData): Observable { - if (this.specificConfigFormGroup.get('setCredential').value) { + private saveCredentials(device: BaseData): Observable { + if (this.credentialsFormGroup.get('setCredential').value) { return this.deviceService.getDeviceCredentials(device.id.id).pipe( mergeMap( (deviceCredentials) => { - const deviceCredentialsValue = {...deviceCredentials, ...this.specificConfigFormGroup.value.credential}; + const deviceCredentialsValue = {...deviceCredentials, ...this.credentialsFormGroup.value.credential}; return this.deviceService.saveDeviceCredentials(deviceCredentialsValue); } ), @@ -275,12 +304,28 @@ export class DeviceWizardDialogComponent extends return of(true); } + allValid(): boolean { + if (this.addDeviceWizardStepper.steps.find((item, index) => { + if (item.stepControl.invalid) { + item.interacted = true; + this.addDeviceWizardStepper.selectedIndex = index; + return true; + } else { + return false; + } + } )) { + return false; + } else { + return true; + } + } + changeStep($event: StepperSelectionEvent): void { this.selectedIndex = $event.selectedIndex; if (this.selectedIndex === this.maxStepperIndex) { - this.nextStepButtonLabel$.next('action.add'); + this.showNext = false; } else { - this.nextStepButtonLabel$.next('action.continue'); + this.showNext = true; } } } diff --git a/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts index 3a7a7f9027..0aaf8e62e7 100644 --- a/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts @@ -296,7 +296,7 @@ export class DevicesTableConfigResolver implements Resolve true, - onAction: ($event) => this.config.table.addEntity($event) + onAction: ($event) => this.deviceWizard($event) }, { name: this.translate.instant('device.import'), @@ -304,12 +304,6 @@ export class DevicesTableConfigResolver implements Resolve true, onAction: ($event) => this.importDevices($event) }, - { - name: this.translate.instant('device.wizard.device-wizard'), - icon: 'library_add', - isEnabled: () => true, - onAction: ($event) => this.deviceWizard($event) - }, ); } if (deviceScope === 'customer') { diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index addb726ed5..0bbc93eb12 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -72,6 +72,14 @@ export const deviceTransportTypeTranslationMap = new Map( + [ + [DeviceTransportType.DEFAULT, 'device-profile.transport-type-default-hint'], + [DeviceTransportType.MQTT, 'device-profile.transport-type-mqtt-hint'], + [DeviceTransportType.LWM2M, 'device-profile.transport-type-lwm2m-hint'] + ] +); + export const mqttTransportPayloadTypeTranslationMap = new Map( [ [MqttTransportPayloadType.JSON, 'device-profile.mqtt-device-payload-type-json'], diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 2a9edbb6e4..08aa57b61a 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -54,7 +54,8 @@ "share-via": "Share via {{provider}}", "continue": "Continue", "discard-changes": "Discard Changes", - "download": "Download" + "download": "Download", + "next-with-label": "Next: {{label}}" }, "aggregation": { "aggregation": "Aggregation", @@ -760,8 +761,7 @@ "wizard": { "device-wizard": "Device Wizard", "device-details": "Device details", - "profile-configuration": "Profile configuration", - "new-device-profile": "New device profile", + "new-device-profile": "Create new device profile", "existing-device-profile": "Select existing device profile", "specific-configuration": "Specific configuration", "customer-to-assign-device": "Customer to assign the device", @@ -784,6 +784,8 @@ "set-default": "Make device profile default", "delete": "Delete device profile", "copyId": "Copy device profile Id", + "new-device-profile-name": "Device profile name", + "new-device-profile-name-required": "Device profile name is required.", "name": "Name", "name-required": "Name is required.", "type": "Profile type", @@ -792,8 +794,11 @@ "transport-type": "Transport type", "transport-type-required": "Transport type is required.", "transport-type-default": "Default", + "transport-type-default-hint": "Default transport type", "transport-type-mqtt": "MQTT", + "transport-type-mqtt-hint": "MQTT transport type", "transport-type-lwm2m": "LWM2M", + "transport-type-lwm2m-hint": "LWM2M transport type", "description": "Description", "default": "Default", "profile-configuration": "Profile configuration", @@ -824,7 +829,8 @@ "not-valid-multi-character": "Invalid use of a multi-level wildcard character", "single-level-wildcards-hint": "[+] is suitable for any topic filter level. Ex.: v1/devices/+/telemetry or +/devices/+/attributes.", "multi-level-wildcards-hint": "[#] can replace the topic filter itself and must be the last symbol of the topic. Ex.: # or v1/devices/me/#.", - "alarm-rules": "Alarm rules ({{count}})", + "alarm-rules": "Alarm rules", + "alarm-rules-with-count": "Alarm rules ({{count}})", "no-alarm-rules": "No alarm rules configured", "add-alarm-rule": "Add alarm rule", "edit-alarm-rule": "Edit alarm rule",