Commit 35f6f40ca347f2e4dba0fcfff2ed306ba325bfff
Merge branch 'master' of https://github.com/thingsboard/thingsboard into feature…
…/device-provision-3.2-onlyProfileVersion
Showing
25 changed files
with
1028 additions
and
216 deletions
@@ -192,10 +192,11 @@ public class DeviceProfileController extends BaseController { | @@ -192,10 +192,11 @@ public class DeviceProfileController extends BaseController { | ||
192 | @RequestParam int page, | 192 | @RequestParam int page, |
193 | @RequestParam(required = false) String textSearch, | 193 | @RequestParam(required = false) String textSearch, |
194 | @RequestParam(required = false) String sortProperty, | 194 | @RequestParam(required = false) String sortProperty, |
195 | - @RequestParam(required = false) String sortOrder) throws ThingsboardException { | 195 | + @RequestParam(required = false) String sortOrder, |
196 | + @RequestParam(required = false) String transportType) throws ThingsboardException { | ||
196 | try { | 197 | try { |
197 | PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); | 198 | PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); |
198 | - return checkNotNull(deviceProfileService.findDeviceProfileInfos(getTenantId(), pageLink)); | 199 | + return checkNotNull(deviceProfileService.findDeviceProfileInfos(getTenantId(), pageLink, transportType)); |
199 | } catch (Exception e) { | 200 | } catch (Exception e) { |
200 | throw handleException(e); | 201 | throw handleException(e); |
201 | } | 202 | } |
@@ -37,7 +37,7 @@ public interface DeviceProfileService { | @@ -37,7 +37,7 @@ public interface DeviceProfileService { | ||
37 | 37 | ||
38 | PageData<DeviceProfile> findDeviceProfiles(TenantId tenantId, PageLink pageLink); | 38 | PageData<DeviceProfile> findDeviceProfiles(TenantId tenantId, PageLink pageLink); |
39 | 39 | ||
40 | - PageData<DeviceProfileInfo> findDeviceProfileInfos(TenantId tenantId, PageLink pageLink); | 40 | + PageData<DeviceProfileInfo> findDeviceProfileInfos(TenantId tenantId, PageLink pageLink, String transportType); |
41 | 41 | ||
42 | DeviceProfile findOrCreateDeviceProfile(TenantId tenantId, String profileName); | 42 | DeviceProfile findOrCreateDeviceProfile(TenantId tenantId, String profileName); |
43 | 43 |
@@ -32,7 +32,7 @@ public interface DeviceProfileDao extends Dao<DeviceProfile> { | @@ -32,7 +32,7 @@ public interface DeviceProfileDao extends Dao<DeviceProfile> { | ||
32 | 32 | ||
33 | PageData<DeviceProfile> findDeviceProfiles(TenantId tenantId, PageLink pageLink); | 33 | PageData<DeviceProfile> findDeviceProfiles(TenantId tenantId, PageLink pageLink); |
34 | 34 | ||
35 | - PageData<DeviceProfileInfo> findDeviceProfileInfos(TenantId tenantId, PageLink pageLink); | 35 | + PageData<DeviceProfileInfo> findDeviceProfileInfos(TenantId tenantId, PageLink pageLink, String transportType); |
36 | 36 | ||
37 | DeviceProfile findDefaultDeviceProfile(TenantId tenantId); | 37 | DeviceProfile findDefaultDeviceProfile(TenantId tenantId); |
38 | 38 |
@@ -182,11 +182,11 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D | @@ -182,11 +182,11 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D | ||
182 | } | 182 | } |
183 | 183 | ||
184 | @Override | 184 | @Override |
185 | - public PageData<DeviceProfileInfo> findDeviceProfileInfos(TenantId tenantId, PageLink pageLink) { | 185 | + public PageData<DeviceProfileInfo> findDeviceProfileInfos(TenantId tenantId, PageLink pageLink, String transportType) { |
186 | log.trace("Executing findDeviceProfileInfos tenantId [{}], pageLink [{}]", tenantId, pageLink); | 186 | log.trace("Executing findDeviceProfileInfos tenantId [{}], pageLink [{}]", tenantId, pageLink); |
187 | validateId(tenantId, INCORRECT_TENANT_ID + tenantId); | 187 | validateId(tenantId, INCORRECT_TENANT_ID + tenantId); |
188 | Validator.validatePageLink(pageLink); | 188 | Validator.validatePageLink(pageLink); |
189 | - return deviceProfileDao.findDeviceProfileInfos(tenantId, pageLink); | 189 | + return deviceProfileDao.findDeviceProfileInfos(tenantId, pageLink, transportType); |
190 | } | 190 | } |
191 | 191 | ||
192 | @Cacheable(cacheNames = DEVICE_PROFILE_CACHE, key = "{#tenantId.id, #name}") | 192 | @Cacheable(cacheNames = DEVICE_PROFILE_CACHE, key = "{#tenantId.id, #name}") |
@@ -21,6 +21,7 @@ import org.springframework.data.jpa.repository.Query; | @@ -21,6 +21,7 @@ import org.springframework.data.jpa.repository.Query; | ||
21 | import org.springframework.data.repository.PagingAndSortingRepository; | 21 | import org.springframework.data.repository.PagingAndSortingRepository; |
22 | import org.springframework.data.repository.query.Param; | 22 | import org.springframework.data.repository.query.Param; |
23 | import org.thingsboard.server.common.data.DeviceProfileInfo; | 23 | import org.thingsboard.server.common.data.DeviceProfileInfo; |
24 | +import org.thingsboard.server.common.data.DeviceTransportType; | ||
24 | import org.thingsboard.server.dao.model.sql.DeviceProfileEntity; | 25 | import org.thingsboard.server.dao.model.sql.DeviceProfileEntity; |
25 | 26 | ||
26 | import java.util.UUID; | 27 | import java.util.UUID; |
@@ -45,6 +46,14 @@ public interface DeviceProfileRepository extends PagingAndSortingRepository<Devi | @@ -45,6 +46,14 @@ public interface DeviceProfileRepository extends PagingAndSortingRepository<Devi | ||
45 | @Param("textSearch") String textSearch, | 46 | @Param("textSearch") String textSearch, |
46 | Pageable pageable); | 47 | Pageable pageable); |
47 | 48 | ||
49 | + @Query("SELECT new org.thingsboard.server.common.data.DeviceProfileInfo(d.id, d.name, d.type, d.transportType) " + | ||
50 | + "FROM DeviceProfileEntity d WHERE " + | ||
51 | + "d.tenantId = :tenantId AND d.transportType = :transportType AND LOWER(d.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") | ||
52 | + Page<DeviceProfileInfo> findDeviceProfileInfos(@Param("tenantId") UUID tenantId, | ||
53 | + @Param("textSearch") String textSearch, | ||
54 | + @Param("transportType") DeviceTransportType transportType, | ||
55 | + Pageable pageable); | ||
56 | + | ||
48 | @Query("SELECT d FROM DeviceProfileEntity d " + | 57 | @Query("SELECT d FROM DeviceProfileEntity d " + |
49 | "WHERE d.tenantId = :tenantId AND d.isDefault = true") | 58 | "WHERE d.tenantId = :tenantId AND d.isDefault = true") |
50 | DeviceProfileEntity findByDefaultTrueAndTenantId(@Param("tenantId") UUID tenantId); | 59 | DeviceProfileEntity findByDefaultTrueAndTenantId(@Param("tenantId") UUID tenantId); |
@@ -15,11 +15,13 @@ | @@ -15,11 +15,13 @@ | ||
15 | */ | 15 | */ |
16 | package org.thingsboard.server.dao.sql.device; | 16 | package org.thingsboard.server.dao.sql.device; |
17 | 17 | ||
18 | +import org.apache.commons.lang.StringUtils; | ||
18 | import org.springframework.beans.factory.annotation.Autowired; | 19 | import org.springframework.beans.factory.annotation.Autowired; |
19 | import org.springframework.data.repository.CrudRepository; | 20 | import org.springframework.data.repository.CrudRepository; |
20 | import org.springframework.stereotype.Component; | 21 | import org.springframework.stereotype.Component; |
21 | import org.thingsboard.server.common.data.DeviceProfile; | 22 | import org.thingsboard.server.common.data.DeviceProfile; |
22 | import org.thingsboard.server.common.data.DeviceProfileInfo; | 23 | import org.thingsboard.server.common.data.DeviceProfileInfo; |
24 | +import org.thingsboard.server.common.data.DeviceTransportType; | ||
23 | import org.thingsboard.server.common.data.id.TenantId; | 25 | import org.thingsboard.server.common.data.id.TenantId; |
24 | import org.thingsboard.server.common.data.page.PageData; | 26 | import org.thingsboard.server.common.data.page.PageData; |
25 | import org.thingsboard.server.common.data.page.PageLink; | 27 | import org.thingsboard.server.common.data.page.PageLink; |
@@ -62,12 +64,21 @@ public class JpaDeviceProfileDao extends JpaAbstractSearchTextDao<DeviceProfileE | @@ -62,12 +64,21 @@ public class JpaDeviceProfileDao extends JpaAbstractSearchTextDao<DeviceProfileE | ||
62 | } | 64 | } |
63 | 65 | ||
64 | @Override | 66 | @Override |
65 | - public PageData<DeviceProfileInfo> findDeviceProfileInfos(TenantId tenantId, PageLink pageLink) { | ||
66 | - return DaoUtil.pageToPageData( | ||
67 | - deviceProfileRepository.findDeviceProfileInfos( | ||
68 | - tenantId.getId(), | ||
69 | - Objects.toString(pageLink.getTextSearch(), ""), | ||
70 | - DaoUtil.toPageable(pageLink))); | 67 | + public PageData<DeviceProfileInfo> findDeviceProfileInfos(TenantId tenantId, PageLink pageLink, String transportType) { |
68 | + if (StringUtils.isNotEmpty(transportType)) { | ||
69 | + return DaoUtil.pageToPageData( | ||
70 | + deviceProfileRepository.findDeviceProfileInfos( | ||
71 | + tenantId.getId(), | ||
72 | + Objects.toString(pageLink.getTextSearch(), ""), | ||
73 | + DeviceTransportType.valueOf(transportType), | ||
74 | + DaoUtil.toPageable(pageLink))); | ||
75 | + } else { | ||
76 | + return DaoUtil.pageToPageData( | ||
77 | + deviceProfileRepository.findDeviceProfileInfos( | ||
78 | + tenantId.getId(), | ||
79 | + Objects.toString(pageLink.getTextSearch(), ""), | ||
80 | + DaoUtil.toPageable(pageLink))); | ||
81 | + } | ||
71 | } | 82 | } |
72 | 83 | ||
73 | @Override | 84 | @Override |
@@ -260,7 +260,7 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest { | @@ -260,7 +260,7 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest { | ||
260 | pageLink = new PageLink(17); | 260 | pageLink = new PageLink(17); |
261 | PageData<DeviceProfileInfo> pageData; | 261 | PageData<DeviceProfileInfo> pageData; |
262 | do { | 262 | do { |
263 | - pageData = deviceProfileService.findDeviceProfileInfos(tenantId, pageLink); | 263 | + pageData = deviceProfileService.findDeviceProfileInfos(tenantId, pageLink, null); |
264 | loadedDeviceProfileInfos.addAll(pageData.getData()); | 264 | loadedDeviceProfileInfos.addAll(pageData.getData()); |
265 | if (pageData.hasNext()) { | 265 | if (pageData.hasNext()) { |
266 | pageLink = pageLink.nextPageLink(); | 266 | pageLink = pageLink.nextPageLink(); |
@@ -284,7 +284,7 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest { | @@ -284,7 +284,7 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest { | ||
284 | } | 284 | } |
285 | 285 | ||
286 | pageLink = new PageLink(17); | 286 | pageLink = new PageLink(17); |
287 | - pageData = deviceProfileService.findDeviceProfileInfos(tenantId, pageLink); | 287 | + pageData = deviceProfileService.findDeviceProfileInfos(tenantId, pageLink, null); |
288 | Assert.assertFalse(pageData.hasNext()); | 288 | Assert.assertFalse(pageData.hasNext()); |
289 | Assert.assertEquals(1, pageData.getTotalElements()); | 289 | Assert.assertEquals(1, pageData.getTotalElements()); |
290 | } | 290 | } |
@@ -20,7 +20,8 @@ import { PageLink } from '@shared/models/page/page-link'; | @@ -20,7 +20,8 @@ import { PageLink } from '@shared/models/page/page-link'; | ||
20 | import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; | 20 | import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; |
21 | import { Observable } from 'rxjs'; | 21 | import { Observable } from 'rxjs'; |
22 | import { PageData } from '@shared/models/page/page-data'; | 22 | import { PageData } from '@shared/models/page/page-data'; |
23 | -import { DeviceProfile, DeviceProfileInfo } from '@shared/models/device.models'; | 23 | +import { DeviceProfile, DeviceProfileInfo, DeviceTransportType } from '@shared/models/device.models'; |
24 | +import { isDefinedAndNotNull } from '@core/utils'; | ||
24 | 25 | ||
25 | @Injectable({ | 26 | @Injectable({ |
26 | providedIn: 'root' | 27 | providedIn: 'root' |
@@ -59,8 +60,13 @@ export class DeviceProfileService { | @@ -59,8 +60,13 @@ export class DeviceProfileService { | ||
59 | return this.http.get<DeviceProfileInfo>(`/api/deviceProfileInfo/${deviceProfileId}`, defaultHttpOptionsFromConfig(config)); | 60 | return this.http.get<DeviceProfileInfo>(`/api/deviceProfileInfo/${deviceProfileId}`, defaultHttpOptionsFromConfig(config)); |
60 | } | 61 | } |
61 | 62 | ||
62 | - public getDeviceProfileInfos(pageLink: PageLink, config?: RequestConfig): Observable<PageData<DeviceProfileInfo>> { | ||
63 | - return this.http.get<PageData<DeviceProfileInfo>>(`/api/deviceProfileInfos${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config)); | 63 | + public getDeviceProfileInfos(pageLink: PageLink, transportType?: DeviceTransportType, |
64 | + config?: RequestConfig): Observable<PageData<DeviceProfileInfo>> { | ||
65 | + let url = `/api/deviceProfileInfos${pageLink.toQuery()}`; | ||
66 | + if (isDefinedAndNotNull(transportType)) { | ||
67 | + url += `&transportType=${transportType}`; | ||
68 | + } | ||
69 | + return this.http.get<PageData<DeviceProfileInfo>>(url, defaultHttpOptionsFromConfig(config)); | ||
64 | } | 70 | } |
65 | 71 | ||
66 | } | 72 | } |
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2020 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | +<section [formGroup]="deviceCredentialsFormGroup"> | ||
19 | + <mat-form-field class="mat-block"> | ||
20 | + <mat-label translate>device.credentials-type</mat-label> | ||
21 | + <mat-select formControlName="credentialsType"> | ||
22 | + <mat-option *ngFor="let credentialsType of credentialsTypes" [value]="credentialsType"> | ||
23 | + {{ credentialTypeNamesMap.get(deviceCredentialsType[credentialsType]) }} | ||
24 | + </mat-option> | ||
25 | + </mat-select> | ||
26 | + </mat-form-field> | ||
27 | + <mat-form-field *ngIf="deviceCredentialsFormGroup.get('credentialsType').value === deviceCredentialsType.ACCESS_TOKEN" | ||
28 | + class="mat-block"> | ||
29 | + <mat-label translate>device.access-token</mat-label> | ||
30 | + <input matInput formControlName="credentialsId" required> | ||
31 | + <mat-error *ngIf="deviceCredentialsFormGroup.get('credentialsId').hasError('required')"> | ||
32 | + {{ 'device.access-token-required' | translate }} | ||
33 | + </mat-error> | ||
34 | + <mat-error *ngIf="deviceCredentialsFormGroup.get('credentialsId').hasError('pattern')"> | ||
35 | + {{ 'device.access-token-invalid' | translate }} | ||
36 | + </mat-error> | ||
37 | + </mat-form-field> | ||
38 | + <mat-form-field *ngIf="deviceCredentialsFormGroup.get('credentialsType').value === deviceCredentialsType.X509_CERTIFICATE" | ||
39 | + class="mat-block"> | ||
40 | + <mat-label translate>device.rsa-key</mat-label> | ||
41 | + <textarea matInput formControlName="credentialsValue" cols="15" rows="5" required></textarea> | ||
42 | + <mat-error *ngIf="deviceCredentialsFormGroup.get('credentialsValue').hasError('required')"> | ||
43 | + {{ 'device.rsa-key-required' | translate }} | ||
44 | + </mat-error> | ||
45 | + </mat-form-field> | ||
46 | + <section *ngIf="deviceCredentialsFormGroup.get('credentialsType').value === deviceCredentialsType.MQTT_BASIC" formGroupName="credentialsBasic"> | ||
47 | + <mat-form-field class="mat-block"> | ||
48 | + <mat-label translate>device.client-id</mat-label> | ||
49 | + <input matInput formControlName="clientId"> | ||
50 | + <mat-error *ngIf="deviceCredentialsFormGroup.get('credentialsBasic.clientId').hasError('pattern')"> | ||
51 | + {{ 'device.client-id-pattern' | translate }} | ||
52 | + </mat-error> | ||
53 | + </mat-form-field> | ||
54 | + <mat-form-field class="mat-block"> | ||
55 | + <mat-label translate>device.user-name</mat-label> | ||
56 | + <input matInput formControlName="userName" [required]="!!deviceCredentialsFormGroup.get('credentialsBasic.password').value"> | ||
57 | + <mat-error *ngIf="deviceCredentialsFormGroup.get('credentialsBasic.userName').hasError('required')"> | ||
58 | + {{ 'device.user-name-required' | translate }} | ||
59 | + </mat-error> | ||
60 | + </mat-form-field> | ||
61 | + <mat-form-field class="mat-block"> | ||
62 | + <mat-label translate>device.password</mat-label> | ||
63 | + <input matInput formControlName="password" | ||
64 | + autocomplete="new-password" | ||
65 | + (ngModelChange)="passwordChanged()" | ||
66 | + [type]="hidePassword ? 'password' : 'text'"> | ||
67 | + <button mat-icon-button matSuffix type="button" | ||
68 | + (click)="hidePassword = !hidePassword" | ||
69 | + [attr.aria-pressed]="hidePassword"> | ||
70 | + <mat-icon>{{hidePassword ? 'visibility_off' : 'visibility'}}</mat-icon> | ||
71 | + </button> | ||
72 | + </mat-form-field> | ||
73 | + <mat-error *ngIf="deviceCredentialsFormGroup.get('credentialsBasic').hasError('atLeastOne')"> | ||
74 | + {{ 'device.client-id-or-user-name-necessary' | translate }} | ||
75 | + </mat-error> | ||
76 | + </section> | ||
77 | +</section> |
1 | +/// | ||
2 | +/// Copyright © 2016-2020 The Thingsboard Authors | ||
3 | +/// | ||
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +/// you may not use this file except in compliance with the License. | ||
6 | +/// You may obtain a copy of the License at | ||
7 | +/// | ||
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +/// | ||
10 | +/// Unless required by applicable law or agreed to in writing, software | ||
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +/// See the License for the specific language governing permissions and | ||
14 | +/// limitations under the License. | ||
15 | +/// | ||
16 | + | ||
17 | +import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core'; | ||
18 | +import { | ||
19 | + ControlValueAccessor, | ||
20 | + FormBuilder, | ||
21 | + FormControl, | ||
22 | + FormGroup, | ||
23 | + NG_VALIDATORS, | ||
24 | + NG_VALUE_ACCESSOR, | ||
25 | + ValidationErrors, | ||
26 | + Validator, | ||
27 | + ValidatorFn, | ||
28 | + Validators | ||
29 | +} from '@angular/forms'; | ||
30 | +import { | ||
31 | + credentialTypeNames, | ||
32 | + DeviceCredentialMQTTBasic, | ||
33 | + DeviceCredentials, | ||
34 | + DeviceCredentialsType | ||
35 | +} from '@shared/models/device.models'; | ||
36 | +import { Subscription } from 'rxjs'; | ||
37 | +import { isDefinedAndNotNull } from '@core/utils'; | ||
38 | +import { distinctUntilChanged } from 'rxjs/operators'; | ||
39 | + | ||
40 | +@Component({ | ||
41 | + selector: 'tb-device-credentials', | ||
42 | + templateUrl: './device-credentials.component.html', | ||
43 | + providers: [ | ||
44 | + { | ||
45 | + provide: NG_VALUE_ACCESSOR, | ||
46 | + useExisting: forwardRef(() => DeviceCredentialsComponent), | ||
47 | + multi: true | ||
48 | + }, | ||
49 | + { | ||
50 | + provide: NG_VALIDATORS, | ||
51 | + useExisting: forwardRef(() => DeviceCredentialsComponent), | ||
52 | + multi: true, | ||
53 | + }], | ||
54 | + styleUrls: [] | ||
55 | +}) | ||
56 | +export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit, Validator, OnDestroy { | ||
57 | + | ||
58 | + deviceCredentialsFormGroup: FormGroup; | ||
59 | + | ||
60 | + subscriptions: Subscription[] = []; | ||
61 | + | ||
62 | + @Input() | ||
63 | + disabled: boolean; | ||
64 | + | ||
65 | + deviceCredentials: DeviceCredentials = null; | ||
66 | + | ||
67 | + submitted = false; | ||
68 | + | ||
69 | + deviceCredentialsType = DeviceCredentialsType; | ||
70 | + | ||
71 | + credentialsTypes = Object.keys(DeviceCredentialsType); | ||
72 | + | ||
73 | + credentialTypeNamesMap = credentialTypeNames; | ||
74 | + | ||
75 | + hidePassword = true; | ||
76 | + | ||
77 | + private propagateChange = (v: any) => {}; | ||
78 | + | ||
79 | + constructor(public fb: FormBuilder) { | ||
80 | + this.deviceCredentialsFormGroup = this.fb.group({ | ||
81 | + credentialsType: [DeviceCredentialsType.ACCESS_TOKEN], | ||
82 | + credentialsId: [null], | ||
83 | + credentialsValue: [null], | ||
84 | + credentialsBasic: this.fb.group({ | ||
85 | + clientId: [null, [Validators.pattern(/^[A-Za-z0-9]+$/)]], | ||
86 | + userName: [null], | ||
87 | + password: [null] | ||
88 | + }, {validators: this.atLeastOne(Validators.required, ['clientId', 'userName'])}) | ||
89 | + }); | ||
90 | + this.deviceCredentialsFormGroup.get('credentialsBasic').disable(); | ||
91 | + this.subscriptions.push( | ||
92 | + this.deviceCredentialsFormGroup.valueChanges.pipe(distinctUntilChanged()).subscribe(() => { | ||
93 | + this.updateView(); | ||
94 | + }) | ||
95 | + ); | ||
96 | + this.subscriptions.push( | ||
97 | + this.deviceCredentialsFormGroup.get('credentialsType').valueChanges.subscribe(() => { | ||
98 | + this.credentialsTypeChanged(); | ||
99 | + }) | ||
100 | + ); | ||
101 | + } | ||
102 | + | ||
103 | + ngOnInit(): void { | ||
104 | + if (this.disabled) { | ||
105 | + this.deviceCredentialsFormGroup.disable({emitEvent: false}); | ||
106 | + } | ||
107 | + } | ||
108 | + | ||
109 | + ngOnDestroy() { | ||
110 | + this.subscriptions.forEach(s => s.unsubscribe()); | ||
111 | + } | ||
112 | + | ||
113 | + writeValue(value: DeviceCredentials | null): void { | ||
114 | + if (isDefinedAndNotNull(value)) { | ||
115 | + this.deviceCredentials = value; | ||
116 | + let credentialsBasic = {clientId: null, userName: null, password: null}; | ||
117 | + let credentialsValue = null; | ||
118 | + if (value.credentialsType === DeviceCredentialsType.MQTT_BASIC) { | ||
119 | + credentialsBasic = JSON.parse(value.credentialsValue) as DeviceCredentialMQTTBasic; | ||
120 | + } else { | ||
121 | + credentialsValue = value.credentialsValue; | ||
122 | + } | ||
123 | + this.deviceCredentialsFormGroup.patchValue({ | ||
124 | + credentialsType: value.credentialsType, | ||
125 | + credentialsId: value.credentialsId, | ||
126 | + credentialsValue, | ||
127 | + credentialsBasic | ||
128 | + }, {emitEvent: false}); | ||
129 | + this.updateValidators(); | ||
130 | + } | ||
131 | + } | ||
132 | + | ||
133 | + updateView() { | ||
134 | + const deviceCredentialsValue = this.deviceCredentialsFormGroup.value; | ||
135 | + if (deviceCredentialsValue.credentialsType === DeviceCredentialsType.MQTT_BASIC) { | ||
136 | + deviceCredentialsValue.credentialsValue = JSON.stringify(deviceCredentialsValue.credentialsBasic); | ||
137 | + } | ||
138 | + delete deviceCredentialsValue.credentialsBasic; | ||
139 | + this.propagateChange(deviceCredentialsValue); | ||
140 | + } | ||
141 | + | ||
142 | + registerOnChange(fn: any): void { | ||
143 | + this.propagateChange = fn; | ||
144 | + } | ||
145 | + | ||
146 | + registerOnTouched(fn: any): void {} | ||
147 | + | ||
148 | + setDisabledState(isDisabled: boolean): void { | ||
149 | + this.disabled = isDisabled; | ||
150 | + if (this.disabled) { | ||
151 | + this.deviceCredentialsFormGroup.disable({emitEvent: false}); | ||
152 | + } else { | ||
153 | + this.deviceCredentialsFormGroup.enable({emitEvent: false}); | ||
154 | + this.updateValidators(); | ||
155 | + } | ||
156 | + } | ||
157 | + | ||
158 | + public validate(c: FormControl) { | ||
159 | + return this.deviceCredentialsFormGroup.valid ? null : { | ||
160 | + deviceCredentials: { | ||
161 | + valid: false, | ||
162 | + }, | ||
163 | + }; | ||
164 | + } | ||
165 | + | ||
166 | + credentialsTypeChanged(): void { | ||
167 | + this.deviceCredentialsFormGroup.patchValue({ | ||
168 | + credentialsId: null, | ||
169 | + credentialsValue: null, | ||
170 | + credentialsBasic: {clientId: '', userName: '', password: ''} | ||
171 | + }); | ||
172 | + this.updateValidators(); | ||
173 | + } | ||
174 | + | ||
175 | + updateValidators(): void { | ||
176 | + this.hidePassword = true; | ||
177 | + const crendetialsType = this.deviceCredentialsFormGroup.get('credentialsType').value as DeviceCredentialsType; | ||
178 | + switch (crendetialsType) { | ||
179 | + case DeviceCredentialsType.ACCESS_TOKEN: | ||
180 | + this.deviceCredentialsFormGroup.get('credentialsId').setValidators([Validators.required, Validators.pattern(/^.{1,20}$/)]); | ||
181 | + this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity({emitEvent: false}); | ||
182 | + this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([]); | ||
183 | + this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity({emitEvent: false}); | ||
184 | + this.deviceCredentialsFormGroup.get('credentialsBasic').disable({emitEvent: false}); | ||
185 | + break; | ||
186 | + case DeviceCredentialsType.X509_CERTIFICATE: | ||
187 | + this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([Validators.required]); | ||
188 | + this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity({emitEvent: false}); | ||
189 | + this.deviceCredentialsFormGroup.get('credentialsId').setValidators([]); | ||
190 | + this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity({emitEvent: false}); | ||
191 | + this.deviceCredentialsFormGroup.get('credentialsBasic').disable({emitEvent: false}); | ||
192 | + break; | ||
193 | + case DeviceCredentialsType.MQTT_BASIC: | ||
194 | + this.deviceCredentialsFormGroup.get('credentialsBasic').enable({emitEvent: false}); | ||
195 | + this.deviceCredentialsFormGroup.get('credentialsBasic').updateValueAndValidity({emitEvent: false}); | ||
196 | + this.deviceCredentialsFormGroup.get('credentialsId').setValidators([]); | ||
197 | + this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity({emitEvent: false}); | ||
198 | + this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([]); | ||
199 | + this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity({emitEvent: false}); | ||
200 | + } | ||
201 | + } | ||
202 | + | ||
203 | + private atLeastOne(validator: ValidatorFn, controls: string[] = null) { | ||
204 | + return (group: FormGroup): ValidationErrors | null => { | ||
205 | + if (!controls) { | ||
206 | + controls = Object.keys(group.controls); | ||
207 | + } | ||
208 | + const hasAtLeastOne = group?.controls && controls.some(k => !validator(group.controls[k])); | ||
209 | + | ||
210 | + return hasAtLeastOne ? null : {atLeastOne: true}; | ||
211 | + }; | ||
212 | + } | ||
213 | + | ||
214 | + passwordChanged() { | ||
215 | + const value = this.deviceCredentialsFormGroup.get('credentialsBasic.password').value; | ||
216 | + if (value !== '') { | ||
217 | + this.deviceCredentialsFormGroup.get('credentialsBasic.userName').setValidators([Validators.required]); | ||
218 | + if (this.deviceCredentialsFormGroup.get('credentialsBasic.userName').untouched) { | ||
219 | + this.deviceCredentialsFormGroup.get('credentialsBasic.userName').markAsTouched({onlySelf: true}); | ||
220 | + } | ||
221 | + } else { | ||
222 | + this.deviceCredentialsFormGroup.get('credentialsBasic.userName').setValidators([]); | ||
223 | + } | ||
224 | + this.deviceCredentialsFormGroup.get('credentialsBasic.userName').updateValueAndValidity({ | ||
225 | + emitEvent: false, | ||
226 | + onlySelf: true | ||
227 | + }); | ||
228 | + } | ||
229 | +} |
@@ -109,6 +109,8 @@ import { AddDeviceProfileDialogComponent } from './profile/add-device-profile-di | @@ -109,6 +109,8 @@ import { AddDeviceProfileDialogComponent } from './profile/add-device-profile-di | ||
109 | import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomplete.component'; | 109 | import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomplete.component'; |
110 | import { DeviceProfileProvisionConfigurationComponent } from "./profile/device-profile-provision-configuration.component"; | 110 | import { DeviceProfileProvisionConfigurationComponent } from "./profile/device-profile-provision-configuration.component"; |
111 | import { AlarmScheduleComponent } from './profile/alarm/alarm-schedule.component'; | 111 | import { AlarmScheduleComponent } from './profile/alarm/alarm-schedule.component'; |
112 | +import { DeviceWizardDialogComponent } from './wizard/device-wizard-dialog.component'; | ||
113 | +import { DeviceCredentialsComponent } from './device/device-credentials.component'; | ||
112 | 114 | ||
113 | @NgModule({ | 115 | @NgModule({ |
114 | declarations: | 116 | declarations: |
@@ -200,7 +202,9 @@ import { AlarmScheduleComponent } from './profile/alarm/alarm-schedule.component | @@ -200,7 +202,9 @@ import { AlarmScheduleComponent } from './profile/alarm/alarm-schedule.component | ||
200 | AddDeviceProfileDialogComponent, | 202 | AddDeviceProfileDialogComponent, |
201 | RuleChainAutocompleteComponent, | 203 | RuleChainAutocompleteComponent, |
202 | DeviceProfileProvisionConfigurationComponent, | 204 | DeviceProfileProvisionConfigurationComponent, |
203 | - AlarmScheduleComponent | 205 | + AlarmScheduleComponent, |
206 | + DeviceWizardDialogComponent, | ||
207 | + DeviceCredentialsComponent | ||
204 | ], | 208 | ], |
205 | imports: [ | 209 | imports: [ |
206 | CommonModule, | 210 | CommonModule, |
@@ -280,6 +284,8 @@ import { AlarmScheduleComponent } from './profile/alarm/alarm-schedule.component | @@ -280,6 +284,8 @@ import { AlarmScheduleComponent } from './profile/alarm/alarm-schedule.component | ||
280 | DeviceProfileDialogComponent, | 284 | DeviceProfileDialogComponent, |
281 | AddDeviceProfileDialogComponent, | 285 | AddDeviceProfileDialogComponent, |
282 | RuleChainAutocompleteComponent, | 286 | RuleChainAutocompleteComponent, |
287 | + DeviceWizardDialogComponent, | ||
288 | + DeviceCredentialsComponent, | ||
283 | DeviceProfileProvisionConfigurationComponent, | 289 | DeviceProfileProvisionConfigurationComponent, |
284 | AlarmScheduleComponent | 290 | AlarmScheduleComponent |
285 | ], | 291 | ], |
@@ -85,7 +85,7 @@ | @@ -85,7 +85,7 @@ | ||
85 | </mat-step> | 85 | </mat-step> |
86 | <mat-step [stepControl]="alarmRulesFormGroup"> | 86 | <mat-step [stepControl]="alarmRulesFormGroup"> |
87 | <form [formGroup]="alarmRulesFormGroup" style="padding-bottom: 16px;"> | 87 | <form [formGroup]="alarmRulesFormGroup" style="padding-bottom: 16px;"> |
88 | - <ng-template matStepLabel>{{'device-profile.alarm-rules' | translate: | 88 | + <ng-template matStepLabel>{{'device-profile.alarm-rules-with-count' | translate: |
89 | {count: alarmRulesFormGroup.get('alarms').value ? | 89 | {count: alarmRulesFormGroup.get('alarms').value ? |
90 | alarmRulesFormGroup.get('alarms').value.length : 0} }}</ng-template> | 90 | alarmRulesFormGroup.get('alarms').value.length : 0} }}</ng-template> |
91 | <tb-device-profile-alarms | 91 | <tb-device-profile-alarms |
@@ -46,6 +46,7 @@ import { RuleChainId } from '@shared/models/id/rule-chain-id'; | @@ -46,6 +46,7 @@ import { RuleChainId } from '@shared/models/id/rule-chain-id'; | ||
46 | 46 | ||
47 | export interface AddDeviceProfileDialogData { | 47 | export interface AddDeviceProfileDialogData { |
48 | deviceProfileName: string; | 48 | deviceProfileName: string; |
49 | + transportType: DeviceTransportType; | ||
49 | } | 50 | } |
50 | 51 | ||
51 | @Component({ | 52 | @Component({ |
@@ -99,7 +100,7 @@ export class AddDeviceProfileDialogComponent extends | @@ -99,7 +100,7 @@ export class AddDeviceProfileDialogComponent extends | ||
99 | ); | 100 | ); |
100 | this.transportConfigFormGroup = this.fb.group( | 101 | this.transportConfigFormGroup = this.fb.group( |
101 | { | 102 | { |
102 | - transportType: [DeviceTransportType.DEFAULT, [Validators.required]], | 103 | + transportType: [data.transportType ? data.transportType : DeviceTransportType.DEFAULT, [Validators.required]], |
103 | transportConfiguration: [createDeviceProfileTransportConfiguration(DeviceTransportType.DEFAULT), | 104 | transportConfiguration: [createDeviceProfileTransportConfiguration(DeviceTransportType.DEFAULT), |
104 | [Validators.required]] | 105 | [Validators.required]] |
105 | } | 106 | } |
@@ -48,7 +48,7 @@ | @@ -48,7 +48,7 @@ | ||
48 | </mat-option> | 48 | </mat-option> |
49 | <mat-option *ngIf="!(filteredDeviceProfiles | async)?.length" [value]="null" class="tb-not-found"> | 49 | <mat-option *ngIf="!(filteredDeviceProfiles | async)?.length" [value]="null" class="tb-not-found"> |
50 | <div class="tb-not-found-content" (click)="$event.stopPropagation()"> | 50 | <div class="tb-not-found-content" (click)="$event.stopPropagation()"> |
51 | - <div *ngIf="!textIsNotEmpty(searchText); else searchNotEmpty"> | 51 | + <div *ngIf="!textIsNotEmpty(searchText) || !addNewProfile; else searchNotEmpty"> |
52 | <span translate>device-profile.no-device-profiles-found</span> | 52 | <span translate>device-profile.no-device-profiles-found</span> |
53 | </div> | 53 | </div> |
54 | <ng-template #searchNotEmpty> | 54 | <ng-template #searchNotEmpty> |
@@ -56,10 +56,10 @@ | @@ -56,10 +56,10 @@ | ||
56 | {{ translate.get('device-profile.no-device-profiles-matching', | 56 | {{ translate.get('device-profile.no-device-profiles-matching', |
57 | {entity: truncate.transform(searchText, true, 6, '...')}) | async }} | 57 | {entity: truncate.transform(searchText, true, 6, '...')}) | async }} |
58 | </span> | 58 | </span> |
59 | + <span> | ||
60 | + <a translate (click)="createDeviceProfile($event, searchText)">device-profile.create-new-device-profile</a> | ||
61 | + </span> | ||
59 | </ng-template> | 62 | </ng-template> |
60 | - <span> | ||
61 | - <a translate (click)="createDeviceProfile($event, searchText)">device-profile.create-new-device-profile</a> | ||
62 | - </span> | ||
63 | </div> | 63 | </div> |
64 | </mat-option> | 64 | </mat-option> |
65 | </mat-autocomplete> | 65 | </mat-autocomplete> |
@@ -19,9 +19,10 @@ import { | @@ -19,9 +19,10 @@ import { | ||
19 | ElementRef, | 19 | ElementRef, |
20 | EventEmitter, | 20 | EventEmitter, |
21 | forwardRef, | 21 | forwardRef, |
22 | - Input, NgZone, | 22 | + Input, |
23 | + NgZone, OnChanges, | ||
23 | OnInit, | 24 | OnInit, |
24 | - Output, | 25 | + Output, SimpleChanges, |
25 | ViewChild | 26 | ViewChild |
26 | } from '@angular/core'; | 27 | } from '@angular/core'; |
27 | import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; | 28 | import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; |
@@ -38,14 +39,7 @@ import { TruncatePipe } from '@shared//pipe/truncate.pipe'; | @@ -38,14 +39,7 @@ import { TruncatePipe } from '@shared//pipe/truncate.pipe'; | ||
38 | import { ENTER } from '@angular/cdk/keycodes'; | 39 | import { ENTER } from '@angular/cdk/keycodes'; |
39 | import { MatDialog } from '@angular/material/dialog'; | 40 | import { MatDialog } from '@angular/material/dialog'; |
40 | import { DeviceProfileId } from '@shared/models/id/device-profile-id'; | 41 | import { DeviceProfileId } from '@shared/models/id/device-profile-id'; |
41 | -import { | ||
42 | - createDeviceProfileConfiguration, | ||
43 | - createDeviceProfileTransportConfiguration, | ||
44 | - DeviceProfile, | ||
45 | - DeviceProfileInfo, | ||
46 | - DeviceProfileType, | ||
47 | - DeviceTransportType | ||
48 | -} from '@shared/models/device.models'; | 42 | +import { DeviceProfile, DeviceProfileInfo, DeviceProfileType, DeviceTransportType } from '@shared/models/device.models'; |
49 | import { DeviceProfileService } from '@core/http/device-profile.service'; | 43 | import { DeviceProfileService } from '@core/http/device-profile.service'; |
50 | import { DeviceProfileDialogComponent, DeviceProfileDialogData } from './device-profile-dialog.component'; | 44 | import { DeviceProfileDialogComponent, DeviceProfileDialogData } from './device-profile-dialog.component'; |
51 | import { MatAutocomplete } from '@angular/material/autocomplete'; | 45 | import { MatAutocomplete } from '@angular/material/autocomplete'; |
@@ -61,7 +55,7 @@ import { AddDeviceProfileDialogComponent, AddDeviceProfileDialogData } from './a | @@ -61,7 +55,7 @@ import { AddDeviceProfileDialogComponent, AddDeviceProfileDialogData } from './a | ||
61 | multi: true | 55 | multi: true |
62 | }] | 56 | }] |
63 | }) | 57 | }) |
64 | -export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, OnInit { | 58 | +export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, OnInit, OnChanges { |
65 | 59 | ||
66 | selectDeviceProfileFormGroup: FormGroup; | 60 | selectDeviceProfileFormGroup: FormGroup; |
67 | 61 | ||
@@ -76,6 +70,12 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, | @@ -76,6 +70,12 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, | ||
76 | @Input() | 70 | @Input() |
77 | editProfileEnabled = true; | 71 | editProfileEnabled = true; |
78 | 72 | ||
73 | + @Input() | ||
74 | + addNewProfile = true; | ||
75 | + | ||
76 | + @Input() | ||
77 | + transportType: DeviceTransportType = null; | ||
78 | + | ||
79 | private requiredValue: boolean; | 79 | private requiredValue: boolean; |
80 | get required(): boolean { | 80 | get required(): boolean { |
81 | return this.requiredValue; | 81 | return this.requiredValue; |
@@ -168,11 +168,22 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, | @@ -168,11 +168,22 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, | ||
168 | ); | 168 | ); |
169 | } | 169 | } |
170 | 170 | ||
171 | + ngOnChanges(changes: SimpleChanges): void { | ||
172 | + for (const propName of Object.keys(changes)) { | ||
173 | + const change = changes[propName]; | ||
174 | + if (!change.firstChange && change.currentValue !== change.previousValue) { | ||
175 | + if (propName === 'transportType') { | ||
176 | + this.writeValue(null); | ||
177 | + } | ||
178 | + } | ||
179 | + } | ||
180 | + } | ||
181 | + | ||
171 | selectDefaultDeviceProfileIfNeeded(): void { | 182 | selectDefaultDeviceProfileIfNeeded(): void { |
172 | if (this.selectDefaultProfile && !this.modelValue) { | 183 | if (this.selectDefaultProfile && !this.modelValue) { |
173 | this.deviceProfileService.getDefaultDeviceProfileInfo().subscribe( | 184 | this.deviceProfileService.getDefaultDeviceProfileInfo().subscribe( |
174 | (profile) => { | 185 | (profile) => { |
175 | - if (profile) { | 186 | + if (profile && !this.transportType || (profile.transportType === this.transportType)) { |
176 | this.selectDeviceProfileFormGroup.get('deviceProfile').patchValue(profile, {emitEvent: false}); | 187 | this.selectDeviceProfileFormGroup.get('deviceProfile').patchValue(profile, {emitEvent: false}); |
177 | this.updateView(profile); | 188 | this.updateView(profile); |
178 | } | 189 | } |
@@ -183,6 +194,11 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, | @@ -183,6 +194,11 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, | ||
183 | 194 | ||
184 | setDisabledState(isDisabled: boolean): void { | 195 | setDisabledState(isDisabled: boolean): void { |
185 | this.disabled = isDisabled; | 196 | this.disabled = isDisabled; |
197 | + if (this.disabled) { | ||
198 | + this.selectDeviceProfileFormGroup.disable(); | ||
199 | + } else { | ||
200 | + this.selectDeviceProfileFormGroup.enable(); | ||
201 | + } | ||
186 | } | 202 | } |
187 | 203 | ||
188 | writeValue(value: DeviceProfileId | null): void { | 204 | writeValue(value: DeviceProfileId | null): void { |
@@ -244,7 +260,7 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, | @@ -244,7 +260,7 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, | ||
244 | property: 'name', | 260 | property: 'name', |
245 | direction: Direction.ASC | 261 | direction: Direction.ASC |
246 | }); | 262 | }); |
247 | - return this.deviceProfileService.getDeviceProfileInfos(pageLink, {ignoreLoading: true}).pipe( | 263 | + return this.deviceProfileService.getDeviceProfileInfos(pageLink, this.transportType, {ignoreLoading: true}).pipe( |
248 | map(pageData => { | 264 | map(pageData => { |
249 | let data = pageData.data; | 265 | let data = pageData.data; |
250 | if (this.displayAllOnEmpty) { | 266 | if (this.displayAllOnEmpty) { |
@@ -280,9 +296,12 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, | @@ -280,9 +296,12 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, | ||
280 | createDeviceProfile($event: Event, profileName: string) { | 296 | createDeviceProfile($event: Event, profileName: string) { |
281 | $event.preventDefault(); | 297 | $event.preventDefault(); |
282 | const deviceProfile: DeviceProfile = { | 298 | const deviceProfile: DeviceProfile = { |
283 | - name: profileName | 299 | + name: profileName, |
300 | + transportType: this.transportType | ||
284 | } as DeviceProfile; | 301 | } as DeviceProfile; |
285 | - this.openDeviceProfileDialog(deviceProfile, true); | 302 | + if (this.addNewProfile) { |
303 | + this.openDeviceProfileDialog(deviceProfile, true); | ||
304 | + } | ||
286 | } | 305 | } |
287 | 306 | ||
288 | editDeviceProfile($event: Event) { | 307 | editDeviceProfile($event: Event) { |
@@ -312,7 +331,8 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, | @@ -312,7 +331,8 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, | ||
312 | disableClose: true, | 331 | disableClose: true, |
313 | panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], | 332 | panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], |
314 | data: { | 333 | data: { |
315 | - deviceProfileName: deviceProfile.name | 334 | + deviceProfileName: deviceProfile.name, |
335 | + transportType: deviceProfile.transportType | ||
316 | } | 336 | } |
317 | }).afterClosed(); | 337 | }).afterClosed(); |
318 | } | 338 | } |
@@ -42,7 +42,7 @@ | @@ -42,7 +42,7 @@ | ||
42 | <mat-expansion-panel [expanded]="true"> | 42 | <mat-expansion-panel [expanded]="true"> |
43 | <mat-expansion-panel-header> | 43 | <mat-expansion-panel-header> |
44 | <mat-panel-title> | 44 | <mat-panel-title> |
45 | - <div>{{'device-profile.alarm-rules' | translate: | 45 | + <div>{{'device-profile.alarm-rules-with-count' | translate: |
46 | {count: deviceProfileDataFormGroup.get('alarms').value ? | 46 | {count: deviceProfileDataFormGroup.get('alarms').value ? |
47 | deviceProfileDataFormGroup.get('alarms').value.length : 0} }}</div> | 47 | deviceProfileDataFormGroup.get('alarms').value.length : 0} }}</div> |
48 | </mat-panel-title> | 48 | </mat-panel-title> |
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2020 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | +<div> | ||
19 | + <mat-toolbar color="primary"> | ||
20 | + <h2 translate>device.add-device-text</h2> | ||
21 | + <span fxFlex></span> | ||
22 | + <button mat-icon-button | ||
23 | + (click)="cancel()" | ||
24 | + type="button"> | ||
25 | + <mat-icon class="material-icons">close</mat-icon> | ||
26 | + </button> | ||
27 | + </mat-toolbar> | ||
28 | + <mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> | ||
29 | + </mat-progress-bar> | ||
30 | + <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> | ||
31 | + <div mat-dialog-content> | ||
32 | + <mat-horizontal-stepper [linear]="true" [labelPosition]="labelPosition" #addDeviceWizardStepper (selectionChange)="changeStep($event)"> | ||
33 | + <ng-template matStepperIcon="edit"> | ||
34 | + <mat-icon>check</mat-icon> | ||
35 | + </ng-template> | ||
36 | + <mat-step [stepControl]="deviceWizardFormGroup"> | ||
37 | + <form [formGroup]="deviceWizardFormGroup" style="padding-bottom: 16px;"> | ||
38 | + <ng-template matStepLabel>{{ 'device.wizard.device-details' | translate}}</ng-template> | ||
39 | + <fieldset [disabled]="isLoading$ | async"> | ||
40 | + <mat-form-field class="mat-block"> | ||
41 | + <mat-label translate>device.name</mat-label> | ||
42 | + <input matInput formControlName="name" required> | ||
43 | + <mat-error *ngIf="deviceWizardFormGroup.get('name').hasError('required')"> | ||
44 | + {{ 'device.name-required' | translate }} | ||
45 | + </mat-error> | ||
46 | + </mat-form-field> | ||
47 | + <mat-form-field class="mat-block"> | ||
48 | + <mat-label translate>device.label</mat-label> | ||
49 | + <input matInput formControlName="label"> | ||
50 | + </mat-form-field> | ||
51 | + <mat-form-field class="mat-block" style="padding-bottom: 14px;"> | ||
52 | + <mat-label translate>device-profile.transport-type</mat-label> | ||
53 | + <mat-select formControlName="transportType" required> | ||
54 | + <mat-option *ngFor="let type of deviceTransportTypes" [value]="type"> | ||
55 | + {{deviceTransportTypeTranslations.get(type) | translate}} | ||
56 | + </mat-option> | ||
57 | + </mat-select> | ||
58 | + <mat-hint *ngIf="deviceWizardFormGroup.get('transportType').value"> | ||
59 | + {{deviceTransportTypeHints.get(deviceWizardFormGroup.get('transportType').value) | translate}} | ||
60 | + </mat-hint> | ||
61 | + <mat-error *ngIf="deviceWizardFormGroup.get('transportType').hasError('required')"> | ||
62 | + {{ 'device-profile.transport-type-required' | translate }} | ||
63 | + </mat-error> | ||
64 | + </mat-form-field> | ||
65 | + <div fxLayout="row" fxLayoutGap="16px"> | ||
66 | + <mat-radio-group fxLayout="column" formControlName="addProfileType" fxLayoutAlign="space-around"> | ||
67 | + <mat-radio-button [value]="0" color="primary"> | ||
68 | + <span translate>device.wizard.existing-device-profile</span> | ||
69 | + </mat-radio-button> | ||
70 | + <mat-radio-button [value]="1" color="primary"> | ||
71 | + <span translate>device.wizard.new-device-profile</span> | ||
72 | + </mat-radio-button> | ||
73 | + </mat-radio-group> | ||
74 | + <div fxLayout="column"> | ||
75 | + <tb-device-profile-autocomplete | ||
76 | + [required]="!createProfile" | ||
77 | + [transportType]="deviceWizardFormGroup.get('transportType').value" | ||
78 | + formControlName="deviceProfileId" | ||
79 | + (deviceProfileChanged)="$event?.transportType ? deviceWizardFormGroup.get('transportType').patchValue($event?.transportType) : {}" | ||
80 | + [addNewProfile]="false" | ||
81 | + [selectDefaultProfile]="true" | ||
82 | + [editProfileEnabled]="false"> | ||
83 | + </tb-device-profile-autocomplete> | ||
84 | + <mat-form-field fxFlex class="mat-block"> | ||
85 | + <mat-label translate>device-profile.new-device-profile-name</mat-label> | ||
86 | + <input matInput formControlName="newDeviceProfileTitle" | ||
87 | + [required]="createProfile"> | ||
88 | + <mat-error *ngIf="deviceWizardFormGroup.get('newDeviceProfileTitle').hasError('required')"> | ||
89 | + {{ 'device-profile.new-device-profile-name-required' | translate }} | ||
90 | + </mat-error> | ||
91 | + </mat-form-field> | ||
92 | + </div> | ||
93 | + </div> | ||
94 | + <mat-checkbox formControlName="gateway" style="padding-bottom: 16px;"> | ||
95 | + {{ 'device.is-gateway' | translate }} | ||
96 | + </mat-checkbox> | ||
97 | + <mat-form-field class="mat-block"> | ||
98 | + <mat-label translate>device.description</mat-label> | ||
99 | + <textarea matInput formControlName="description" rows="2"></textarea> | ||
100 | + </mat-form-field> | ||
101 | + </fieldset> | ||
102 | + </form> | ||
103 | + </mat-step> | ||
104 | + <mat-step [stepControl]="transportConfigFormGroup" *ngIf="createTransportConfiguration"> | ||
105 | + <form [formGroup]="transportConfigFormGroup" style="padding-bottom: 16px;"> | ||
106 | + <ng-template matStepLabel>{{ 'device-profile.transport-configuration' | translate }}</ng-template> | ||
107 | + <tb-device-profile-transport-configuration | ||
108 | + formControlName="transportConfiguration" | ||
109 | + required> | ||
110 | + </tb-device-profile-transport-configuration> | ||
111 | + </form> | ||
112 | + </mat-step> | ||
113 | + <mat-step [stepControl]="alarmRulesFormGroup" [optional]="true" *ngIf="createProfile"> | ||
114 | + <form [formGroup]="alarmRulesFormGroup" style="padding-bottom: 16px;"> | ||
115 | + <ng-template matStepLabel>{{'device-profile.alarm-rules-with-count' | translate: | ||
116 | + {count: alarmRulesFormGroup.get('alarms').value ? | ||
117 | + alarmRulesFormGroup.get('alarms').value.length : 0} }}</ng-template> | ||
118 | + <tb-device-profile-alarms | ||
119 | + formControlName="alarms"> | ||
120 | + </tb-device-profile-alarms> | ||
121 | + </form> | ||
122 | + </mat-step> | ||
123 | + <mat-step [stepControl]="credentialsFormGroup" [optional]="true"> | ||
124 | + <ng-template matStepLabel>{{ 'device.credentials' | translate }}</ng-template> | ||
125 | + <form [formGroup]="credentialsFormGroup" style="padding-bottom: 16px;"> | ||
126 | + <mat-checkbox style="padding-bottom: 16px;" formControlName="setCredential">{{ 'device.wizard.add-credential' | translate }}</mat-checkbox> | ||
127 | + <tb-device-credentials | ||
128 | + [fxShow]="credentialsFormGroup.get('setCredential').value" | ||
129 | + formControlName="credential"> | ||
130 | + </tb-device-credentials> | ||
131 | + </form> | ||
132 | + </mat-step> | ||
133 | + <mat-step [stepControl]="customerFormGroup" [optional]="true"> | ||
134 | + <ng-template matStepLabel>{{ 'customer.customer' | translate }}</ng-template> | ||
135 | + <form [formGroup]="customerFormGroup" style="padding-bottom: 16px;"> | ||
136 | + <tb-entity-autocomplete | ||
137 | + formControlName="customerId" | ||
138 | + labelText="device.wizard.customer-to-assign-device" | ||
139 | + [entityType]="entityType.CUSTOMER"> | ||
140 | + </tb-entity-autocomplete> | ||
141 | + </form> | ||
142 | + </mat-step> | ||
143 | + </mat-horizontal-stepper> | ||
144 | + </div> | ||
145 | + <div mat-dialog-actions fxLayout="column" fxLayoutAlign="start wrap" fxLayoutGap="8px" style="height: 100px;"> | ||
146 | + <div fxFlex fxLayout="row" fxLayoutAlign="end"> | ||
147 | + <button mat-raised-button | ||
148 | + *ngIf="showNext" | ||
149 | + [disabled]="(isLoading$ | async)" | ||
150 | + (click)="nextStep()">{{ 'action.next-with-label' | translate:{label: (getFormLabel(this.selectedIndex+1) | translate)} }}</button> | ||
151 | + </div> | ||
152 | + <div fxFlex fxLayout="row"> | ||
153 | + <button mat-button | ||
154 | + color="primary" | ||
155 | + [disabled]="(isLoading$ | async)" | ||
156 | + (click)="cancel()">{{ 'action.cancel' | translate }}</button> | ||
157 | + <span fxFlex></span> | ||
158 | + <div fxLayout="row wrap" fxLayoutGap="8px"> | ||
159 | + <button mat-raised-button *ngIf="selectedIndex > 0" | ||
160 | + [disabled]="(isLoading$ | async)" | ||
161 | + (click)="previousStep()">{{ 'action.back' | translate }}</button> | ||
162 | + <button mat-raised-button | ||
163 | + [disabled]="(isLoading$ | async)" | ||
164 | + color="primary" | ||
165 | + (click)="add()">{{ 'action.add' | translate }}</button> | ||
166 | + </div> | ||
167 | + </div> | ||
168 | + </div> | ||
169 | +</div> |
1 | +/** | ||
2 | + * Copyright © 2016-2020 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +@import "../../../../../scss/constants"; | ||
18 | + | ||
19 | +:host-context(.tb-fullscreen-dialog .mat-dialog-container) { | ||
20 | + @media #{$mat-lt-sm} { | ||
21 | + .mat-dialog-content { | ||
22 | + max-height: 75vh; | ||
23 | + } | ||
24 | + } | ||
25 | +} | ||
26 | + | ||
27 | +:host ::ng-deep { | ||
28 | + .mat-dialog-content { | ||
29 | + display: flex; | ||
30 | + flex-direction: column; | ||
31 | + height: 100%; | ||
32 | + | ||
33 | + .mat-stepper-horizontal { | ||
34 | + display: flex; | ||
35 | + flex-direction: column; | ||
36 | + height: 100%; | ||
37 | + overflow: hidden; | ||
38 | + @media #{$mat-lt-sm} { | ||
39 | + .mat-step-label { | ||
40 | + white-space: normal; | ||
41 | + overflow: visible; | ||
42 | + .mat-step-text-label { | ||
43 | + overflow: visible; | ||
44 | + } | ||
45 | + } | ||
46 | + } | ||
47 | + .mat-horizontal-content-container { | ||
48 | + height: 450px; | ||
49 | + max-height: 100%; | ||
50 | + width: 100%;; | ||
51 | + overflow-y: auto; | ||
52 | + @media #{$mat-gt-sm} { | ||
53 | + min-width: 800px; | ||
54 | + } | ||
55 | + } | ||
56 | + .mat-horizontal-stepper-content[aria-expanded=true] { | ||
57 | + height: 100%; | ||
58 | + form { | ||
59 | + height: 100%; | ||
60 | + } | ||
61 | + } | ||
62 | + } | ||
63 | + } | ||
64 | +} |
1 | +/// | ||
2 | +/// Copyright © 2016-2020 The Thingsboard Authors | ||
3 | +/// | ||
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +/// you may not use this file except in compliance with the License. | ||
6 | +/// You may obtain a copy of the License at | ||
7 | +/// | ||
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +/// | ||
10 | +/// Unless required by applicable law or agreed to in writing, software | ||
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +/// See the License for the specific language governing permissions and | ||
14 | +/// limitations under the License. | ||
15 | +/// | ||
16 | + | ||
17 | +import { Component, Inject, OnDestroy, SkipSelf, ViewChild } from '@angular/core'; | ||
18 | +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; | ||
19 | +import { Store } from '@ngrx/store'; | ||
20 | +import { AppState } from '@core/core.state'; | ||
21 | +import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; | ||
22 | +import { DialogComponent } from '@shared/components/dialog.component'; | ||
23 | +import { Router } from '@angular/router'; | ||
24 | +import { | ||
25 | + createDeviceProfileConfiguration, | ||
26 | + createDeviceProfileTransportConfiguration, | ||
27 | + DeviceProfile, | ||
28 | + DeviceProfileType, | ||
29 | + DeviceTransportType, deviceTransportTypeHintMap, | ||
30 | + deviceTransportTypeTranslationMap | ||
31 | +} from '@shared/models/device.models'; | ||
32 | +import { MatHorizontalStepper } from '@angular/material/stepper'; | ||
33 | +import { AddEntityDialogData } from '@home/models/entity/entity-component.models'; | ||
34 | +import { BaseData, HasId } from '@shared/models/base-data'; | ||
35 | +import { EntityType } from '@shared/models/entity-type.models'; | ||
36 | +import { DeviceProfileService } from '@core/http/device-profile.service'; | ||
37 | +import { EntityId } from '@shared/models/id/entity-id'; | ||
38 | +import { Observable, of, Subscription } from 'rxjs'; | ||
39 | +import { map, mergeMap, tap } from 'rxjs/operators'; | ||
40 | +import { DeviceService } from '@core/http/device.service'; | ||
41 | +import { ErrorStateMatcher } from '@angular/material/core'; | ||
42 | +import { StepperSelectionEvent } from '@angular/cdk/stepper'; | ||
43 | +import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; | ||
44 | +import { MediaBreakpoints } from '@shared/models/constants'; | ||
45 | + | ||
46 | +@Component({ | ||
47 | + selector: 'tb-device-wizard', | ||
48 | + templateUrl: './device-wizard-dialog.component.html', | ||
49 | + providers: [], | ||
50 | + styleUrls: ['./device-wizard-dialog.component.scss'] | ||
51 | +}) | ||
52 | +export class DeviceWizardDialogComponent extends | ||
53 | + DialogComponent<DeviceWizardDialogComponent, boolean> implements OnDestroy, ErrorStateMatcher { | ||
54 | + | ||
55 | + @ViewChild('addDeviceWizardStepper', {static: true}) addDeviceWizardStepper: MatHorizontalStepper; | ||
56 | + | ||
57 | + selectedIndex = 0; | ||
58 | + | ||
59 | + showNext = true; | ||
60 | + | ||
61 | + createProfile = false; | ||
62 | + createTransportConfiguration = false; | ||
63 | + | ||
64 | + entityType = EntityType; | ||
65 | + | ||
66 | + deviceTransportTypes = Object.keys(DeviceTransportType); | ||
67 | + | ||
68 | + deviceTransportTypeTranslations = deviceTransportTypeTranslationMap; | ||
69 | + | ||
70 | + deviceTransportTypeHints = deviceTransportTypeHintMap; | ||
71 | + | ||
72 | + deviceWizardFormGroup: FormGroup; | ||
73 | + | ||
74 | + transportConfigFormGroup: FormGroup; | ||
75 | + | ||
76 | + alarmRulesFormGroup: FormGroup; | ||
77 | + | ||
78 | + credentialsFormGroup: FormGroup; | ||
79 | + | ||
80 | + customerFormGroup: FormGroup; | ||
81 | + | ||
82 | + labelPosition = 'end'; | ||
83 | + | ||
84 | + private subscriptions: Subscription[] = []; | ||
85 | + | ||
86 | + constructor(protected store: Store<AppState>, | ||
87 | + protected router: Router, | ||
88 | + @Inject(MAT_DIALOG_DATA) public data: AddEntityDialogData<BaseData<EntityId>>, | ||
89 | + @SkipSelf() private errorStateMatcher: ErrorStateMatcher, | ||
90 | + public dialogRef: MatDialogRef<DeviceWizardDialogComponent, boolean>, | ||
91 | + private deviceProfileService: DeviceProfileService, | ||
92 | + private deviceService: DeviceService, | ||
93 | + private breakpointObserver: BreakpointObserver, | ||
94 | + private fb: FormBuilder) { | ||
95 | + super(store, router, dialogRef); | ||
96 | + this.deviceWizardFormGroup = this.fb.group({ | ||
97 | + name: ['', Validators.required], | ||
98 | + label: [''], | ||
99 | + gateway: [false], | ||
100 | + transportType: [DeviceTransportType.DEFAULT, Validators.required], | ||
101 | + addProfileType: [0], | ||
102 | + deviceProfileId: [null, Validators.required], | ||
103 | + newDeviceProfileTitle: [{value: null, disabled: true}], | ||
104 | + description: [''] | ||
105 | + } | ||
106 | + ); | ||
107 | + | ||
108 | + this.subscriptions.push(this.deviceWizardFormGroup.get('addProfileType').valueChanges.subscribe( | ||
109 | + (addProfileType: number) => { | ||
110 | + if (addProfileType === 0) { | ||
111 | + this.deviceWizardFormGroup.get('deviceProfileId').setValidators([Validators.required]); | ||
112 | + this.deviceWizardFormGroup.get('deviceProfileId').enable(); | ||
113 | + this.deviceWizardFormGroup.get('newDeviceProfileTitle').setValidators(null); | ||
114 | + this.deviceWizardFormGroup.get('newDeviceProfileTitle').disable(); | ||
115 | + this.deviceWizardFormGroup.updateValueAndValidity(); | ||
116 | + this.createProfile = false; | ||
117 | + this.createTransportConfiguration = false; | ||
118 | + } else { | ||
119 | + this.deviceWizardFormGroup.get('deviceProfileId').setValidators(null); | ||
120 | + this.deviceWizardFormGroup.get('deviceProfileId').disable(); | ||
121 | + this.deviceWizardFormGroup.get('newDeviceProfileTitle').setValidators([Validators.required]); | ||
122 | + this.deviceWizardFormGroup.get('newDeviceProfileTitle').enable(); | ||
123 | + this.deviceWizardFormGroup.updateValueAndValidity(); | ||
124 | + this.createProfile = true; | ||
125 | + this.createTransportConfiguration = this.deviceWizardFormGroup.get('transportType').value && | ||
126 | + DeviceTransportType.DEFAULT !== this.deviceWizardFormGroup.get('transportType').value; | ||
127 | + } | ||
128 | + } | ||
129 | + )); | ||
130 | + | ||
131 | + this.transportConfigFormGroup = this.fb.group( | ||
132 | + { | ||
133 | + transportConfiguration: [createDeviceProfileTransportConfiguration(DeviceTransportType.DEFAULT), Validators.required] | ||
134 | + } | ||
135 | + ); | ||
136 | + this.subscriptions.push(this.deviceWizardFormGroup.get('transportType').valueChanges.subscribe((transportType) => { | ||
137 | + this.deviceProfileTransportTypeChanged(transportType); | ||
138 | + })); | ||
139 | + | ||
140 | + this.alarmRulesFormGroup = this.fb.group({ | ||
141 | + alarms: [null] | ||
142 | + } | ||
143 | + ); | ||
144 | + | ||
145 | + this.credentialsFormGroup = this.fb.group({ | ||
146 | + setCredential: [false], | ||
147 | + credential: [{value: null, disabled: true}] | ||
148 | + } | ||
149 | + ); | ||
150 | + | ||
151 | + this.subscriptions.push(this.credentialsFormGroup.get('setCredential').valueChanges.subscribe((value) => { | ||
152 | + if (value) { | ||
153 | + this.credentialsFormGroup.get('credential').enable(); | ||
154 | + } else { | ||
155 | + this.credentialsFormGroup.get('credential').disable(); | ||
156 | + } | ||
157 | + })); | ||
158 | + | ||
159 | + this.customerFormGroup = this.fb.group({ | ||
160 | + customerId: [null] | ||
161 | + } | ||
162 | + ); | ||
163 | + | ||
164 | + this.labelPosition = this.breakpointObserver.isMatched(MediaBreakpoints['gt-sm']) ? 'end' : 'bottom'; | ||
165 | + | ||
166 | + this.subscriptions.push(this.breakpointObserver | ||
167 | + .observe(MediaBreakpoints['gt-sm']) | ||
168 | + .subscribe((state: BreakpointState) => { | ||
169 | + if (state.matches) { | ||
170 | + this.labelPosition = 'end'; | ||
171 | + } else { | ||
172 | + this.labelPosition = 'bottom'; | ||
173 | + } | ||
174 | + } | ||
175 | + )); | ||
176 | + } | ||
177 | + | ||
178 | + ngOnDestroy() { | ||
179 | + super.ngOnDestroy(); | ||
180 | + this.subscriptions.forEach(s => s.unsubscribe()); | ||
181 | + } | ||
182 | + | ||
183 | + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { | ||
184 | + const originalErrorState = this.errorStateMatcher.isErrorState(control, form); | ||
185 | + const customErrorState = !!(control && control.invalid); | ||
186 | + return originalErrorState || customErrorState; | ||
187 | + } | ||
188 | + | ||
189 | + cancel(): void { | ||
190 | + this.dialogRef.close(null); | ||
191 | + } | ||
192 | + | ||
193 | + previousStep(): void { | ||
194 | + this.addDeviceWizardStepper.previous(); | ||
195 | + } | ||
196 | + | ||
197 | + nextStep(): void { | ||
198 | + this.addDeviceWizardStepper.next(); | ||
199 | + } | ||
200 | + | ||
201 | + getFormLabel(index: number): string { | ||
202 | + if (index > 0) { | ||
203 | + if (!this.createProfile) { | ||
204 | + index += 2; | ||
205 | + } else if (!this.createTransportConfiguration) { | ||
206 | + index += 1; | ||
207 | + } | ||
208 | + } | ||
209 | + switch (index) { | ||
210 | + case 0: | ||
211 | + return 'device.wizard.device-details'; | ||
212 | + case 1: | ||
213 | + return 'device-profile.transport-configuration'; | ||
214 | + case 2: | ||
215 | + return 'device-profile.alarm-rules'; | ||
216 | + case 3: | ||
217 | + return 'device.credentials'; | ||
218 | + case 4: | ||
219 | + return 'customer.customer'; | ||
220 | + } | ||
221 | + } | ||
222 | + | ||
223 | + get maxStepperIndex(): number { | ||
224 | + return this.addDeviceWizardStepper?._steps?.length - 1; | ||
225 | + } | ||
226 | + | ||
227 | + private deviceProfileTransportTypeChanged(deviceTransportType: DeviceTransportType): void { | ||
228 | + this.transportConfigFormGroup.patchValue( | ||
229 | + {transportConfiguration: createDeviceProfileTransportConfiguration(deviceTransportType)}); | ||
230 | + this.createTransportConfiguration = this.createProfile && deviceTransportType && | ||
231 | + DeviceTransportType.DEFAULT !== deviceTransportType; | ||
232 | + } | ||
233 | + | ||
234 | + add(): void { | ||
235 | + if (this.allValid()) { | ||
236 | + this.createDeviceProfile().pipe( | ||
237 | + mergeMap(profileId => this.createDevice(profileId)), | ||
238 | + mergeMap(device => this.saveCredentials(device)) | ||
239 | + ).subscribe( | ||
240 | + (created) => { | ||
241 | + this.dialogRef.close(created); | ||
242 | + } | ||
243 | + ); | ||
244 | + } | ||
245 | + } | ||
246 | + | ||
247 | + private createDeviceProfile(): Observable<EntityId> { | ||
248 | + if (this.deviceWizardFormGroup.get('addProfileType').value) { | ||
249 | + const deviceProfile: DeviceProfile = { | ||
250 | + name: this.deviceWizardFormGroup.get('newDeviceProfileTitle').value, | ||
251 | + type: DeviceProfileType.DEFAULT, | ||
252 | + transportType: this.deviceWizardFormGroup.get('transportType').value, | ||
253 | + profileData: { | ||
254 | + configuration: createDeviceProfileConfiguration(DeviceProfileType.DEFAULT), | ||
255 | + transportConfiguration: this.transportConfigFormGroup.get('transportConfiguration').value, | ||
256 | + alarms: this.alarmRulesFormGroup.get('alarms').value | ||
257 | + } | ||
258 | + }; | ||
259 | + return this.deviceProfileService.saveDeviceProfile(deviceProfile).pipe( | ||
260 | + map(profile => profile.id), | ||
261 | + tap((profileId) => { | ||
262 | + this.deviceWizardFormGroup.patchValue({ | ||
263 | + deviceProfileId: profileId, | ||
264 | + addProfileType: 0 | ||
265 | + }); | ||
266 | + }) | ||
267 | + ); | ||
268 | + } else { | ||
269 | + return of(null); | ||
270 | + } | ||
271 | + } | ||
272 | + | ||
273 | + private createDevice(profileId: EntityId = this.deviceWizardFormGroup.get('deviceProfileId').value): Observable<BaseData<HasId>> { | ||
274 | + const device = { | ||
275 | + name: this.deviceWizardFormGroup.get('name').value, | ||
276 | + label: this.deviceWizardFormGroup.get('label').value, | ||
277 | + deviceProfileId: profileId, | ||
278 | + additionalInfo: { | ||
279 | + gateway: this.deviceWizardFormGroup.get('gateway').value, | ||
280 | + description: this.deviceWizardFormGroup.get('description').value | ||
281 | + }, | ||
282 | + customerId: null | ||
283 | + }; | ||
284 | + if (this.customerFormGroup.get('customerId').value) { | ||
285 | + device.customerId = { | ||
286 | + entityType: EntityType.CUSTOMER, | ||
287 | + id: this.customerFormGroup.get('customerId').value | ||
288 | + }; | ||
289 | + } | ||
290 | + return this.data.entitiesTableConfig.saveEntity(device); | ||
291 | + } | ||
292 | + | ||
293 | + private saveCredentials(device: BaseData<HasId>): Observable<boolean> { | ||
294 | + if (this.credentialsFormGroup.get('setCredential').value) { | ||
295 | + return this.deviceService.getDeviceCredentials(device.id.id).pipe( | ||
296 | + mergeMap( | ||
297 | + (deviceCredentials) => { | ||
298 | + const deviceCredentialsValue = {...deviceCredentials, ...this.credentialsFormGroup.value.credential}; | ||
299 | + return this.deviceService.saveDeviceCredentials(deviceCredentialsValue); | ||
300 | + } | ||
301 | + ), | ||
302 | + map(() => true)); | ||
303 | + } | ||
304 | + return of(true); | ||
305 | + } | ||
306 | + | ||
307 | + allValid(): boolean { | ||
308 | + if (this.addDeviceWizardStepper.steps.find((item, index) => { | ||
309 | + if (item.stepControl.invalid) { | ||
310 | + item.interacted = true; | ||
311 | + this.addDeviceWizardStepper.selectedIndex = index; | ||
312 | + return true; | ||
313 | + } else { | ||
314 | + return false; | ||
315 | + } | ||
316 | + } )) { | ||
317 | + return false; | ||
318 | + } else { | ||
319 | + return true; | ||
320 | + } | ||
321 | + } | ||
322 | + | ||
323 | + changeStep($event: StepperSelectionEvent): void { | ||
324 | + this.selectedIndex = $event.selectedIndex; | ||
325 | + if (this.selectedIndex === this.maxStepperIndex) { | ||
326 | + this.showNext = false; | ||
327 | + } else { | ||
328 | + this.showNext = true; | ||
329 | + } | ||
330 | + } | ||
331 | +} |
@@ -114,7 +114,8 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon | @@ -114,7 +114,8 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon | ||
114 | disableClose: true, | 114 | disableClose: true, |
115 | panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], | 115 | panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], |
116 | data: { | 116 | data: { |
117 | - deviceProfileName: null | 117 | + deviceProfileName: null, |
118 | + transportType: null | ||
118 | } | 119 | } |
119 | }).afterClosed(); | 120 | }).afterClosed(); |
120 | } | 121 | } |
@@ -30,65 +30,9 @@ | @@ -30,65 +30,9 @@ | ||
30 | <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> | 30 | <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> |
31 | <div mat-dialog-content> | 31 | <div mat-dialog-content> |
32 | <fieldset [disabled]="(isLoading$ | async) || isReadOnly"> | 32 | <fieldset [disabled]="(isLoading$ | async) || isReadOnly"> |
33 | - <mat-form-field class="mat-block"> | ||
34 | - <mat-label translate>device.credentials-type</mat-label> | ||
35 | - <mat-select formControlName="credentialsType" | ||
36 | - (ngModelChange)="credentialsTypeChanged()"> | ||
37 | - <mat-option *ngFor="let credentialsType of credentialsTypes" [value]="credentialsType"> | ||
38 | - {{ credentialTypeNamesMap.get(deviceCredentialsType[credentialsType]) }} | ||
39 | - </mat-option> | ||
40 | - </mat-select> | ||
41 | - </mat-form-field> | ||
42 | - <mat-form-field *ngIf="deviceCredentialsFormGroup.get('credentialsType').value === deviceCredentialsType.ACCESS_TOKEN" | ||
43 | - class="mat-block"> | ||
44 | - <mat-label translate>device.access-token</mat-label> | ||
45 | - <input matInput formControlName="credentialsId" required> | ||
46 | - <mat-error *ngIf="deviceCredentialsFormGroup.get('credentialsId').hasError('required')"> | ||
47 | - {{ 'device.access-token-required' | translate }} | ||
48 | - </mat-error> | ||
49 | - <mat-error *ngIf="deviceCredentialsFormGroup.get('credentialsId').hasError('pattern')"> | ||
50 | - {{ 'device.access-token-invalid' | translate }} | ||
51 | - </mat-error> | ||
52 | - </mat-form-field> | ||
53 | - <mat-form-field *ngIf="deviceCredentialsFormGroup.get('credentialsType').value === deviceCredentialsType.X509_CERTIFICATE" | ||
54 | - class="mat-block"> | ||
55 | - <mat-label translate>device.rsa-key</mat-label> | ||
56 | - <textarea matInput formControlName="credentialsValue" cols="15" rows="5" required></textarea> | ||
57 | - <mat-error *ngIf="deviceCredentialsFormGroup.get('credentialsValue').hasError('required')"> | ||
58 | - {{ 'device.rsa-key-required' | translate }} | ||
59 | - </mat-error> | ||
60 | - </mat-form-field> | ||
61 | - <section *ngIf="deviceCredentialsFormGroup.get('credentialsType').value === deviceCredentialsType.MQTT_BASIC" formGroupName="credentialsBasic"> | ||
62 | - <mat-form-field class="mat-block"> | ||
63 | - <mat-label translate>device.client-id</mat-label> | ||
64 | - <input matInput formControlName="clientId"> | ||
65 | - <mat-error *ngIf="deviceCredentialsFormGroup.get('credentialsBasic.clientId').hasError('pattern')"> | ||
66 | - {{ 'device.client-id-pattern' | translate }} | ||
67 | - </mat-error> | ||
68 | - </mat-form-field> | ||
69 | - <mat-form-field class="mat-block"> | ||
70 | - <mat-label translate>device.user-name</mat-label> | ||
71 | - <input matInput formControlName="userName" [required]="!!deviceCredentialsFormGroup.get('credentialsBasic.password').value"> | ||
72 | - <mat-error *ngIf="deviceCredentialsFormGroup.get('credentialsBasic.userName').hasError('required')"> | ||
73 | - {{ 'device.user-name-required' | translate }} | ||
74 | - </mat-error> | ||
75 | - </mat-form-field> | ||
76 | - <mat-form-field class="mat-block"> | ||
77 | - <mat-label translate>device.password</mat-label> | ||
78 | - <input matInput formControlName="password" | ||
79 | - autocomplete="new-password" | ||
80 | - (ngModelChange)="passwordChanged()" | ||
81 | - [type]="hidePassword ? 'password' : 'text'"> | ||
82 | - <button mat-icon-button matSuffix type="button" | ||
83 | - (click)="hidePassword = !hidePassword" | ||
84 | - [attr.aria-pressed]="hidePassword"> | ||
85 | - <mat-icon>{{hidePassword ? 'visibility_off' : 'visibility'}}</mat-icon> | ||
86 | - </button> | ||
87 | - </mat-form-field> | ||
88 | - <mat-error *ngIf="deviceCredentialsFormGroup.get('credentialsBasic').hasError('atLeastOne')"> | ||
89 | - {{ 'device.client-id-or-user-name-necessary' | translate }} | ||
90 | - </mat-error> | ||
91 | - </section> | 33 | + <tb-device-credentials |
34 | + formControlName="credential"> | ||
35 | + </tb-device-credentials> | ||
92 | </fieldset> | 36 | </fieldset> |
93 | </div> | 37 | </div> |
94 | <div mat-dialog-actions fxLayoutAlign="end center"> | 38 | <div mat-dialog-actions fxLayoutAlign="end center"> |
@@ -19,23 +19,9 @@ import { ErrorStateMatcher } from '@angular/material/core'; | @@ -19,23 +19,9 @@ import { ErrorStateMatcher } from '@angular/material/core'; | ||
19 | import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; | 19 | import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; |
20 | import { Store } from '@ngrx/store'; | 20 | import { Store } from '@ngrx/store'; |
21 | import { AppState } from '@core/core.state'; | 21 | import { AppState } from '@core/core.state'; |
22 | -import { | ||
23 | - FormBuilder, | ||
24 | - FormControl, | ||
25 | - FormGroup, | ||
26 | - FormGroupDirective, | ||
27 | - NgForm, | ||
28 | - ValidationErrors, | ||
29 | - ValidatorFn, | ||
30 | - Validators | ||
31 | -} from '@angular/forms'; | 22 | +import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm } from '@angular/forms'; |
32 | import { DeviceService } from '@core/http/device.service'; | 23 | import { DeviceService } from '@core/http/device.service'; |
33 | -import { | ||
34 | - credentialTypeNames, | ||
35 | - DeviceCredentialMQTTBasic, | ||
36 | - DeviceCredentials, | ||
37 | - DeviceCredentialsType | ||
38 | -} from '@shared/models/device.models'; | 24 | +import { credentialTypeNames, DeviceCredentials, DeviceCredentialsType } from '@shared/models/device.models'; |
39 | import { DialogComponent } from '@shared/components/dialog.component'; | 25 | import { DialogComponent } from '@shared/components/dialog.component'; |
40 | import { Router } from '@angular/router'; | 26 | import { Router } from '@angular/router'; |
41 | 27 | ||
@@ -83,19 +69,10 @@ export class DeviceCredentialsDialogComponent extends | @@ -83,19 +69,10 @@ export class DeviceCredentialsDialogComponent extends | ||
83 | 69 | ||
84 | ngOnInit(): void { | 70 | ngOnInit(): void { |
85 | this.deviceCredentialsFormGroup = this.fb.group({ | 71 | this.deviceCredentialsFormGroup = this.fb.group({ |
86 | - credentialsType: [DeviceCredentialsType.ACCESS_TOKEN], | ||
87 | - credentialsId: [''], | ||
88 | - credentialsValue: [''], | ||
89 | - credentialsBasic: this.fb.group({ | ||
90 | - clientId: ['', [Validators.pattern(/^[A-Za-z0-9]+$/)]], | ||
91 | - userName: [''], | ||
92 | - password: [''] | ||
93 | - }, {validators: this.atLeastOne(Validators.required, ['clientId', 'userName'])}) | 72 | + credential: [null] |
94 | }); | 73 | }); |
95 | if (this.isReadOnly) { | 74 | if (this.isReadOnly) { |
96 | this.deviceCredentialsFormGroup.disable({emitEvent: false}); | 75 | this.deviceCredentialsFormGroup.disable({emitEvent: false}); |
97 | - } else { | ||
98 | - this.registerDisableOnLoadFormControl(this.deviceCredentialsFormGroup.get('credentialsType')); | ||
99 | } | 76 | } |
100 | this.loadDeviceCredentials(); | 77 | this.loadDeviceCredentials(); |
101 | } | 78 | } |
@@ -110,82 +87,20 @@ export class DeviceCredentialsDialogComponent extends | @@ -110,82 +87,20 @@ export class DeviceCredentialsDialogComponent extends | ||
110 | this.deviceService.getDeviceCredentials(this.data.deviceId).subscribe( | 87 | this.deviceService.getDeviceCredentials(this.data.deviceId).subscribe( |
111 | (deviceCredentials) => { | 88 | (deviceCredentials) => { |
112 | this.deviceCredentials = deviceCredentials; | 89 | this.deviceCredentials = deviceCredentials; |
113 | - let credentialsValue = deviceCredentials.credentialsValue; | ||
114 | - let credentialsBasic = {clientId: null, userName: null, password: null}; | ||
115 | - if (deviceCredentials.credentialsType === DeviceCredentialsType.MQTT_BASIC) { | ||
116 | - credentialsValue = null; | ||
117 | - credentialsBasic = JSON.parse(deviceCredentials.credentialsValue) as DeviceCredentialMQTTBasic; | ||
118 | - } | ||
119 | this.deviceCredentialsFormGroup.patchValue({ | 90 | this.deviceCredentialsFormGroup.patchValue({ |
120 | - credentialsType: deviceCredentials.credentialsType, | ||
121 | - credentialsId: deviceCredentials.credentialsId, | ||
122 | - credentialsValue, | ||
123 | - credentialsBasic | ||
124 | - }); | ||
125 | - this.updateValidators(); | 91 | + credential: deviceCredentials |
92 | + }, {emitEvent: false}); | ||
126 | } | 93 | } |
127 | ); | 94 | ); |
128 | } | 95 | } |
129 | 96 | ||
130 | - credentialsTypeChanged(): void { | ||
131 | - this.deviceCredentialsFormGroup.patchValue({ | ||
132 | - credentialsId: null, | ||
133 | - credentialsValue: null, | ||
134 | - credentialsBasic: {clientId: '', userName: '', password: ''} | ||
135 | - }, {emitEvent: true}); | ||
136 | - this.updateValidators(); | ||
137 | - } | ||
138 | - | ||
139 | - updateValidators(): void { | ||
140 | - this.hidePassword = true; | ||
141 | - const crendetialsType = this.deviceCredentialsFormGroup.get('credentialsType').value as DeviceCredentialsType; | ||
142 | - switch (crendetialsType) { | ||
143 | - case DeviceCredentialsType.ACCESS_TOKEN: | ||
144 | - this.deviceCredentialsFormGroup.get('credentialsId').setValidators([Validators.required, Validators.pattern(/^.{1,20}$/)]); | ||
145 | - this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity(); | ||
146 | - this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([]); | ||
147 | - this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity(); | ||
148 | - this.deviceCredentialsFormGroup.get('credentialsBasic').disable(); | ||
149 | - break; | ||
150 | - case DeviceCredentialsType.X509_CERTIFICATE: | ||
151 | - this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([Validators.required]); | ||
152 | - this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity(); | ||
153 | - this.deviceCredentialsFormGroup.get('credentialsId').setValidators([]); | ||
154 | - this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity(); | ||
155 | - this.deviceCredentialsFormGroup.get('credentialsBasic').disable(); | ||
156 | - break; | ||
157 | - case DeviceCredentialsType.MQTT_BASIC: | ||
158 | - this.deviceCredentialsFormGroup.get('credentialsBasic').enable(); | ||
159 | - this.deviceCredentialsFormGroup.get('credentialsBasic').updateValueAndValidity(); | ||
160 | - this.deviceCredentialsFormGroup.get('credentialsId').setValidators([]); | ||
161 | - this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity(); | ||
162 | - this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([]); | ||
163 | - this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity(); | ||
164 | - } | ||
165 | - } | ||
166 | - | ||
167 | - private atLeastOne(validator: ValidatorFn, controls: string[] = null) { | ||
168 | - return (group: FormGroup): ValidationErrors | null => { | ||
169 | - if (!controls) { | ||
170 | - controls = Object.keys(group.controls); | ||
171 | - } | ||
172 | - const hasAtLeastOne = group?.controls && controls.some(k => !validator(group.controls[k])); | ||
173 | - | ||
174 | - return hasAtLeastOne ? null : {atLeastOne: true}; | ||
175 | - }; | ||
176 | - } | ||
177 | - | ||
178 | cancel(): void { | 97 | cancel(): void { |
179 | this.dialogRef.close(null); | 98 | this.dialogRef.close(null); |
180 | } | 99 | } |
181 | 100 | ||
182 | save(): void { | 101 | save(): void { |
183 | this.submitted = true; | 102 | this.submitted = true; |
184 | - const deviceCredentialsValue = this.deviceCredentialsFormGroup.value; | ||
185 | - if (deviceCredentialsValue.credentialsType === DeviceCredentialsType.MQTT_BASIC) { | ||
186 | - deviceCredentialsValue.credentialsValue = JSON.stringify(deviceCredentialsValue.credentialsBasic); | ||
187 | - } | ||
188 | - delete deviceCredentialsValue.credentialsBasic; | 103 | + const deviceCredentialsValue = this.deviceCredentialsFormGroup.value.credential; |
189 | this.deviceCredentials = {...this.deviceCredentials, ...deviceCredentialsValue}; | 104 | this.deviceCredentials = {...this.deviceCredentials, ...deviceCredentialsValue}; |
190 | this.deviceService.saveDeviceCredentials(this.deviceCredentials).subscribe( | 105 | this.deviceService.saveDeviceCredentials(this.deviceCredentials).subscribe( |
191 | (deviceCredentials) => { | 106 | (deviceCredentials) => { |
@@ -193,18 +108,4 @@ export class DeviceCredentialsDialogComponent extends | @@ -193,18 +108,4 @@ export class DeviceCredentialsDialogComponent extends | ||
193 | } | 108 | } |
194 | ); | 109 | ); |
195 | } | 110 | } |
196 | - | ||
197 | - passwordChanged() { | ||
198 | - const value = this.deviceCredentialsFormGroup.get('credentialsBasic.password').value; | ||
199 | - if (value !== '') { | ||
200 | - this.deviceCredentialsFormGroup.get('credentialsBasic.userName').setValidators([Validators.required]); | ||
201 | - if (this.deviceCredentialsFormGroup.get('credentialsBasic.userName').untouched) { | ||
202 | - this.deviceCredentialsFormGroup.get('credentialsBasic.userName').markAsTouched({onlySelf: true}); | ||
203 | - } | ||
204 | - this.deviceCredentialsFormGroup.get('credentialsBasic.userName').updateValueAndValidity(); | ||
205 | - } else { | ||
206 | - this.deviceCredentialsFormGroup.get('credentialsBasic.userName').setValidators([]); | ||
207 | - this.deviceCredentialsFormGroup.get('credentialsBasic.userName').updateValueAndValidity(); | ||
208 | - } | ||
209 | - } | ||
210 | } | 111 | } |
@@ -29,7 +29,7 @@ import { | @@ -29,7 +29,7 @@ import { | ||
29 | import { TranslateService } from '@ngx-translate/core'; | 29 | import { TranslateService } from '@ngx-translate/core'; |
30 | import { DatePipe } from '@angular/common'; | 30 | import { DatePipe } from '@angular/common'; |
31 | import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared/models/entity-type.models'; | 31 | import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared/models/entity-type.models'; |
32 | -import { EntityAction } from '@home/models/entity/entity-component.models'; | 32 | +import { AddEntityDialogData, EntityAction } from '@home/models/entity/entity-component.models'; |
33 | import { Device, DeviceCredentials, DeviceInfo } from '@app/shared/models/device.models'; | 33 | import { Device, DeviceCredentials, DeviceInfo } from '@app/shared/models/device.models'; |
34 | import { DeviceComponent } from '@modules/home/pages/device/device.component'; | 34 | import { DeviceComponent } from '@modules/home/pages/device/device.component'; |
35 | import { forkJoin, Observable, of } from 'rxjs'; | 35 | import { forkJoin, Observable, of } from 'rxjs'; |
@@ -61,6 +61,8 @@ import { | @@ -61,6 +61,8 @@ import { | ||
61 | } from '../../dialogs/add-entities-to-customer-dialog.component'; | 61 | } from '../../dialogs/add-entities-to-customer-dialog.component'; |
62 | import { DeviceTabsComponent } from '@home/pages/device/device-tabs.component'; | 62 | import { DeviceTabsComponent } from '@home/pages/device/device-tabs.component'; |
63 | import { HomeDialogsService } from '@home/dialogs/home-dialogs.service'; | 63 | import { HomeDialogsService } from '@home/dialogs/home-dialogs.service'; |
64 | +import { DeviceWizardDialogComponent } from '@home/components/wizard/device-wizard-dialog.component'; | ||
65 | +import { BaseData, HasId } from '@shared/models/base-data'; | ||
64 | 66 | ||
65 | @Injectable() | 67 | @Injectable() |
66 | export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<DeviceInfo>> { | 68 | export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<DeviceInfo>> { |
@@ -221,7 +223,7 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev | @@ -221,7 +223,7 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev | ||
221 | { | 223 | { |
222 | name: this.translate.instant('device.manage-credentials'), | 224 | name: this.translate.instant('device.manage-credentials'), |
223 | icon: 'security', | 225 | icon: 'security', |
224 | - isEnabled: (entity) => true, | 226 | + isEnabled: () => true, |
225 | onAction: ($event, entity) => this.manageCredentials($event, entity) | 227 | onAction: ($event, entity) => this.manageCredentials($event, entity) |
226 | } | 228 | } |
227 | ); | 229 | ); |
@@ -243,7 +245,7 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev | @@ -243,7 +245,7 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev | ||
243 | { | 245 | { |
244 | name: this.translate.instant('device.manage-credentials'), | 246 | name: this.translate.instant('device.manage-credentials'), |
245 | icon: 'security', | 247 | icon: 'security', |
246 | - isEnabled: (entity) => true, | 248 | + isEnabled: () => true, |
247 | onAction: ($event, entity) => this.manageCredentials($event, entity) | 249 | onAction: ($event, entity) => this.manageCredentials($event, entity) |
248 | } | 250 | } |
249 | ); | 251 | ); |
@@ -253,7 +255,7 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev | @@ -253,7 +255,7 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev | ||
253 | { | 255 | { |
254 | name: this.translate.instant('device.view-credentials'), | 256 | name: this.translate.instant('device.view-credentials'), |
255 | icon: 'security', | 257 | icon: 'security', |
256 | - isEnabled: (entity) => true, | 258 | + isEnabled: () => true, |
257 | onAction: ($event, entity) => this.manageCredentials($event, entity) | 259 | onAction: ($event, entity) => this.manageCredentials($event, entity) |
258 | } | 260 | } |
259 | ); | 261 | ); |
@@ -294,14 +296,14 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev | @@ -294,14 +296,14 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev | ||
294 | name: this.translate.instant('device.add-device-text'), | 296 | name: this.translate.instant('device.add-device-text'), |
295 | icon: 'insert_drive_file', | 297 | icon: 'insert_drive_file', |
296 | isEnabled: () => true, | 298 | isEnabled: () => true, |
297 | - onAction: ($event) => this.config.table.addEntity($event) | 299 | + onAction: ($event) => this.deviceWizard($event) |
298 | }, | 300 | }, |
299 | { | 301 | { |
300 | name: this.translate.instant('device.import'), | 302 | name: this.translate.instant('device.import'), |
301 | icon: 'file_upload', | 303 | icon: 'file_upload', |
302 | isEnabled: () => true, | 304 | isEnabled: () => true, |
303 | onAction: ($event) => this.importDevices($event) | 305 | onAction: ($event) => this.importDevices($event) |
304 | - } | 306 | + }, |
305 | ); | 307 | ); |
306 | } | 308 | } |
307 | if (deviceScope === 'customer') { | 309 | if (deviceScope === 'customer') { |
@@ -326,6 +328,23 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev | @@ -326,6 +328,23 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev | ||
326 | }); | 328 | }); |
327 | } | 329 | } |
328 | 330 | ||
331 | + deviceWizard($event: Event) { | ||
332 | + this.dialog.open<DeviceWizardDialogComponent, AddEntityDialogData<BaseData<HasId>>, | ||
333 | + boolean>(DeviceWizardDialogComponent, { | ||
334 | + disableClose: true, | ||
335 | + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], | ||
336 | + data: { | ||
337 | + entitiesTableConfig: this.config.table.entitiesTableConfig | ||
338 | + } | ||
339 | + }).afterClosed().subscribe( | ||
340 | + (res) => { | ||
341 | + if (res) { | ||
342 | + this.config.table.updateData(); | ||
343 | + } | ||
344 | + } | ||
345 | + ); | ||
346 | + } | ||
347 | + | ||
329 | addDevicesToCustomer($event: Event) { | 348 | addDevicesToCustomer($event: Event) { |
330 | if ($event) { | 349 | if ($event) { |
331 | $event.stopPropagation(); | 350 | $event.stopPropagation(); |
@@ -480,5 +499,4 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev | @@ -480,5 +499,4 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev | ||
480 | } | 499 | } |
481 | return false; | 500 | return false; |
482 | } | 501 | } |
483 | - | ||
484 | } | 502 | } |
@@ -87,6 +87,14 @@ export const deviceProvisionTypeTranslationMap = new Map<DeviceProvisionType, st | @@ -87,6 +87,14 @@ export const deviceProvisionTypeTranslationMap = new Map<DeviceProvisionType, st | ||
87 | ] | 87 | ] |
88 | ) | 88 | ) |
89 | 89 | ||
90 | +export const deviceTransportTypeHintMap = new Map<DeviceTransportType, string>( | ||
91 | + [ | ||
92 | + [DeviceTransportType.DEFAULT, 'device-profile.transport-type-default-hint'], | ||
93 | + [DeviceTransportType.MQTT, 'device-profile.transport-type-mqtt-hint'], | ||
94 | + [DeviceTransportType.LWM2M, 'device-profile.transport-type-lwm2m-hint'] | ||
95 | + ] | ||
96 | +); | ||
97 | + | ||
90 | export const mqttTransportPayloadTypeTranslationMap = new Map<MqttTransportPayloadType, string>( | 98 | export const mqttTransportPayloadTypeTranslationMap = new Map<MqttTransportPayloadType, string>( |
91 | [ | 99 | [ |
92 | [MqttTransportPayloadType.JSON, 'device-profile.mqtt-device-payload-type-json'], | 100 | [MqttTransportPayloadType.JSON, 'device-profile.mqtt-device-payload-type-json'], |
@@ -54,7 +54,8 @@ | @@ -54,7 +54,8 @@ | ||
54 | "share-via": "Share via {{provider}}", | 54 | "share-via": "Share via {{provider}}", |
55 | "continue": "Continue", | 55 | "continue": "Continue", |
56 | "discard-changes": "Discard Changes", | 56 | "discard-changes": "Discard Changes", |
57 | - "download": "Download" | 57 | + "download": "Download", |
58 | + "next-with-label": "Next: {{label}}" | ||
58 | }, | 59 | }, |
59 | "aggregation": { | 60 | "aggregation": { |
60 | "aggregation": "Aggregation", | 61 | "aggregation": "Aggregation", |
@@ -756,7 +757,16 @@ | @@ -756,7 +757,16 @@ | ||
756 | "search": "Search devices", | 757 | "search": "Search devices", |
757 | "selected-devices": "{ count, plural, 1 {1 device} other {# devices} } selected", | 758 | "selected-devices": "{ count, plural, 1 {1 device} other {# devices} } selected", |
758 | "device-configuration": "Device configuration", | 759 | "device-configuration": "Device configuration", |
759 | - "transport-configuration": "Transport configuration" | 760 | + "transport-configuration": "Transport configuration", |
761 | + "wizard": { | ||
762 | + "device-wizard": "Device Wizard", | ||
763 | + "device-details": "Device details", | ||
764 | + "new-device-profile": "Create new device profile", | ||
765 | + "existing-device-profile": "Select existing device profile", | ||
766 | + "specific-configuration": "Specific configuration", | ||
767 | + "customer-to-assign-device": "Customer to assign the device", | ||
768 | + "add-credential": "Add credential" | ||
769 | + } | ||
760 | }, | 770 | }, |
761 | "device-profile": { | 771 | "device-profile": { |
762 | "device-profile": "Device profile", | 772 | "device-profile": "Device profile", |
@@ -774,6 +784,8 @@ | @@ -774,6 +784,8 @@ | ||
774 | "set-default": "Make device profile default", | 784 | "set-default": "Make device profile default", |
775 | "delete": "Delete device profile", | 785 | "delete": "Delete device profile", |
776 | "copyId": "Copy device profile Id", | 786 | "copyId": "Copy device profile Id", |
787 | + "new-device-profile-name": "Device profile name", | ||
788 | + "new-device-profile-name-required": "Device profile name is required.", | ||
777 | "name": "Name", | 789 | "name": "Name", |
778 | "name-required": "Name is required.", | 790 | "name-required": "Name is required.", |
779 | "type": "Profile type", | 791 | "type": "Profile type", |
@@ -782,8 +794,11 @@ | @@ -782,8 +794,11 @@ | ||
782 | "transport-type": "Transport type", | 794 | "transport-type": "Transport type", |
783 | "transport-type-required": "Transport type is required.", | 795 | "transport-type-required": "Transport type is required.", |
784 | "transport-type-default": "Default", | 796 | "transport-type-default": "Default", |
797 | + "transport-type-default-hint": "Default transport type", | ||
785 | "transport-type-mqtt": "MQTT", | 798 | "transport-type-mqtt": "MQTT", |
799 | + "transport-type-mqtt-hint": "MQTT transport type", | ||
786 | "transport-type-lwm2m": "LWM2M", | 800 | "transport-type-lwm2m": "LWM2M", |
801 | + "transport-type-lwm2m-hint": "LWM2M transport type", | ||
787 | "description": "Description", | 802 | "description": "Description", |
788 | "default": "Default", | 803 | "default": "Default", |
789 | "profile-configuration": "Profile configuration", | 804 | "profile-configuration": "Profile configuration", |
@@ -814,7 +829,8 @@ | @@ -814,7 +829,8 @@ | ||
814 | "not-valid-multi-character": "Invalid use of a multi-level wildcard character", | 829 | "not-valid-multi-character": "Invalid use of a multi-level wildcard character", |
815 | "single-level-wildcards-hint": "<code>[+]</code> is suitable for any topic filter level. Ex.: <b>v1/devices/+/telemetry</b> or <b>+/devices/+/attributes</b>.", | 830 | "single-level-wildcards-hint": "<code>[+]</code> is suitable for any topic filter level. Ex.: <b>v1/devices/+/telemetry</b> or <b>+/devices/+/attributes</b>.", |
816 | "multi-level-wildcards-hint": "<code>[#]</code> can replace the topic filter itself and must be the last symbol of the topic. Ex.: <b>#</b> or <b>v1/devices/me/#</b>.", | 831 | "multi-level-wildcards-hint": "<code>[#]</code> can replace the topic filter itself and must be the last symbol of the topic. Ex.: <b>#</b> or <b>v1/devices/me/#</b>.", |
817 | - "alarm-rules": "Alarm rules ({{count}})", | 832 | + "alarm-rules": "Alarm rules", |
833 | + "alarm-rules-with-count": "Alarm rules ({{count}})", | ||
818 | "no-alarm-rules": "No alarm rules configured", | 834 | "no-alarm-rules": "No alarm rules configured", |
819 | "add-alarm-rule": "Add alarm rule", | 835 | "add-alarm-rule": "Add alarm rule", |
820 | "edit-alarm-rule": "Edit alarm rule", | 836 | "edit-alarm-rule": "Edit alarm rule", |