Browse Source

UI: Improve device profile/data

pull/3477/head
Igor Kulikov 6 years ago
parent
commit
06573211b3
  1. 15
      application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java
  2. 19
      common/data/src/main/java/org/thingsboard/server/common/data/device/data/Lwm2mDeviceTransportConfiguration.java
  3. 19
      common/data/src/main/java/org/thingsboard/server/common/data/device/data/MqttDeviceTransportConfiguration.java
  4. 19
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/Lwm2mDeviceProfileTransportConfiguration.java
  5. 19
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java
  6. 2
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
  7. 17
      dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java
  8. 59
      ui-ngx/src/app/core/http/widget.service.ts
  9. 6
      ui-ngx/src/app/modules/home/components/home-components.module.ts
  10. 1
      ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.html
  11. 21
      ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.ts
  12. 4
      ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.html
  13. 16
      ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.ts
  14. 12
      ui-ngx/src/app/modules/home/components/profile/device/device-profile-transport-configuration.component.html
  15. 24
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m-device-profile-transport-configuration.component.html
  16. 96
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m-device-profile-transport-configuration.component.ts
  17. 24
      ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html
  18. 96
      ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts
  19. 1
      ui-ngx/src/app/modules/home/components/profile/tenant-profile-autocomplete.component.html
  20. 20
      ui-ngx/src/app/modules/home/components/profile/tenant-profile-autocomplete.component.ts
  21. 4
      ui-ngx/src/app/modules/home/pages/device/data/device-data.component.html
  22. 15
      ui-ngx/src/app/modules/home/pages/device/data/device-data.component.ts
  23. 12
      ui-ngx/src/app/modules/home/pages/device/data/device-transport-configuration.component.html
  24. 24
      ui-ngx/src/app/modules/home/pages/device/data/lwm2m-device-transport-configuration.component.html
  25. 96
      ui-ngx/src/app/modules/home/pages/device/data/lwm2m-device-transport-configuration.component.ts
  26. 24
      ui-ngx/src/app/modules/home/pages/device/data/mqtt-device-transport-configuration.component.html
  27. 96
      ui-ngx/src/app/modules/home/pages/device/data/mqtt-device-transport-configuration.component.ts
  28. 4
      ui-ngx/src/app/modules/home/pages/device/device.component.ts
  29. 4
      ui-ngx/src/app/modules/home/pages/device/device.module.ts
  30. 43
      ui-ngx/src/app/shared/models/device.models.ts

15
application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java

@ -237,21 +237,6 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
.andExpect(status().isNotFound());
}
@Test
public void testSaveSameDeviceWithDifferentDeviceProfileId() throws Exception {
Device device = new Device();
device.setName("My device");
device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
DeviceProfile deviceProfile2 = this.createDeviceProfile("Device Profile 2");
DeviceProfile savedDeviceProfile2 = doPost("/api/deviceProfile", deviceProfile2, DeviceProfile.class);
savedDevice.setDeviceProfileId(savedDeviceProfile2.getId());
doPost("/api/device/", savedDevice).andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Changing device profile is prohibited")));
}
@Test
public void testAssignDeviceToCustomerFromDifferentTenant() throws Exception {
loginSysAdmin();

19
common/data/src/main/java/org/thingsboard/server/common/data/device/data/Lwm2mDeviceTransportConfiguration.java

@ -15,13 +15,32 @@
*/
package org.thingsboard.server.common.data.device.data;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import org.thingsboard.server.common.data.DeviceProfileType;
import org.thingsboard.server.common.data.DeviceTransportType;
import java.util.HashMap;
import java.util.Map;
@Data
public class Lwm2mDeviceTransportConfiguration implements DeviceTransportConfiguration {
@JsonIgnore
private Map<String, Object> properties = new HashMap<>();
@JsonAnyGetter
public Map<String, Object> properties() {
return this.properties;
}
@JsonAnySetter
public void put(String name, Object value) {
this.properties.put(name, value);
}
@Override
public DeviceTransportType getType() {
return DeviceTransportType.LWM2M;

19
common/data/src/main/java/org/thingsboard/server/common/data/device/data/MqttDeviceTransportConfiguration.java

@ -15,12 +15,31 @@
*/
package org.thingsboard.server.common.data.device.data;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import org.thingsboard.server.common.data.DeviceTransportType;
import java.util.HashMap;
import java.util.Map;
@Data
public class MqttDeviceTransportConfiguration implements DeviceTransportConfiguration {
@JsonIgnore
private Map<String, Object> properties = new HashMap<>();
@JsonAnyGetter
public Map<String, Object> properties() {
return this.properties;
}
@JsonAnySetter
public void put(String name, Object value) {
this.properties.put(name, value);
}
@Override
public DeviceTransportType getType() {
return DeviceTransportType.MQTT;

19
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/Lwm2mDeviceProfileTransportConfiguration.java

@ -15,13 +15,32 @@
*/
package org.thingsboard.server.common.data.device.profile;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import org.thingsboard.server.common.data.DeviceProfileType;
import org.thingsboard.server.common.data.DeviceTransportType;
import java.util.HashMap;
import java.util.Map;
@Data
public class Lwm2mDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration {
@JsonIgnore
private Map<String, Object> properties = new HashMap<>();
@JsonAnyGetter
public Map<String, Object> properties() {
return this.properties;
}
@JsonAnySetter
public void put(String name, Object value) {
this.properties.put(name, value);
}
@Override
public DeviceTransportType getType() {
return DeviceTransportType.LWM2M;

19
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java

@ -15,12 +15,31 @@
*/
package org.thingsboard.server.common.data.device.profile;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import org.thingsboard.server.common.data.DeviceTransportType;
import java.util.HashMap;
import java.util.Map;
@Data
public class MqttDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration {
@JsonIgnore
private Map<String, Object> properties = new HashMap<>();
@JsonAnyGetter
public Map<String, Object> properties() {
return this.properties;
}
@JsonAnySetter
public void put(String name, Object value) {
this.properties.put(name, value);
}
@Override
public DeviceTransportType getType() {
return DeviceTransportType.MQTT;

2
dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java

@ -436,8 +436,6 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
Device old = deviceDao.findById(device.getTenantId(), device.getId().getId());
if (old == null) {
throw new DataValidationException("Can't update non existing device!");
} else if (!old.getDeviceProfileId().equals(device.getDeviceProfileId())) {
throw new DataValidationException("Changing device profile is prohibited!");
}
}

17
dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java

@ -128,23 +128,6 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest {
}
}
@Test(expected = DataValidationException.class)
public void testSaveSameDeviceWithDifferentDeviceProfileId() {
Device device = new Device();
device.setName("My device");
device.setType("default");
device.setTenantId(tenantId);
device = deviceService.saveDevice(device);
DeviceProfile deviceProfile2 = this.createDeviceProfile(tenantId,"Device Profile 2");
DeviceProfile savedDeviceProfile2 = deviceProfileService.saveDeviceProfile(deviceProfile2);
device.setDeviceProfileId(savedDeviceProfile2.getId());
try {
deviceService.saveDevice(device);
} finally {
deviceService.deleteDevice(tenantId, device.getId());
}
}
@Test(expected = DataValidationException.class)
public void testAssignDeviceToCustomerFromDifferentTenant() {
Device device = new Device();

59
ui-ngx/src/app/core/http/widget.service.ts

@ -43,6 +43,8 @@ export class WidgetService {
private systemWidgetsBundles: Array<WidgetsBundle>;
private tenantWidgetsBundles: Array<WidgetsBundle>;
private loadWidgetsBundleCacheSubject: ReplaySubject<any>;
constructor(
private http: HttpClient,
private utils: UtilsService,
@ -238,34 +240,36 @@ export class WidgetService {
private loadWidgetsBundleCache(config?: RequestConfig): Observable<any> {
if (!this.allWidgetsBundles) {
const loadWidgetsBundleCacheSubject = new ReplaySubject();
this.http.get<Array<WidgetsBundle>>('/api/widgetsBundles',
defaultHttpOptionsFromConfig(config)).subscribe(
(allWidgetsBundles) => {
this.allWidgetsBundles = allWidgetsBundles;
this.systemWidgetsBundles = new Array<WidgetsBundle>();
this.tenantWidgetsBundles = new Array<WidgetsBundle>();
this.allWidgetsBundles = this.allWidgetsBundles.sort((wb1, wb2) => {
let res = wb1.title.localeCompare(wb2.title);
if (res === 0) {
res = wb2.createdTime - wb1.createdTime;
}
return res;
});
this.allWidgetsBundles.forEach((widgetsBundle) => {
if (widgetsBundle.tenantId.id === NULL_UUID) {
this.systemWidgetsBundles.push(widgetsBundle);
} else {
this.tenantWidgetsBundles.push(widgetsBundle);
}
if (!this.loadWidgetsBundleCacheSubject) {
this.loadWidgetsBundleCacheSubject = new ReplaySubject();
this.http.get<Array<WidgetsBundle>>('/api/widgetsBundles',
defaultHttpOptionsFromConfig(config)).subscribe(
(allWidgetsBundles) => {
this.allWidgetsBundles = allWidgetsBundles;
this.systemWidgetsBundles = new Array<WidgetsBundle>();
this.tenantWidgetsBundles = new Array<WidgetsBundle>();
this.allWidgetsBundles = this.allWidgetsBundles.sort((wb1, wb2) => {
let res = wb1.title.localeCompare(wb2.title);
if (res === 0) {
res = wb2.createdTime - wb1.createdTime;
}
return res;
});
this.allWidgetsBundles.forEach((widgetsBundle) => {
if (widgetsBundle.tenantId.id === NULL_UUID) {
this.systemWidgetsBundles.push(widgetsBundle);
} else {
this.tenantWidgetsBundles.push(widgetsBundle);
}
});
this.loadWidgetsBundleCacheSubject.next();
this.loadWidgetsBundleCacheSubject.complete();
},
() => {
this.loadWidgetsBundleCacheSubject.error(null);
});
loadWidgetsBundleCacheSubject.next();
loadWidgetsBundleCacheSubject.complete();
},
() => {
loadWidgetsBundleCacheSubject.error(null);
});
return loadWidgetsBundleCacheSubject.asObservable();
}
return this.loadWidgetsBundleCacheSubject.asObservable();
} else {
return of(null);
}
@ -275,6 +279,7 @@ export class WidgetService {
this.allWidgetsBundles = undefined;
this.systemWidgetsBundles = undefined;
this.tenantWidgetsBundles = undefined;
this.loadWidgetsBundleCacheSubject = undefined;
}
}

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

@ -96,6 +96,8 @@ import { DefaultDeviceProfileTransportConfigurationComponent } from './profile/d
import { DeviceProfileTransportConfigurationComponent } from './profile/device/device-profile-transport-configuration.component';
import { DeviceProfileDialogComponent } from './profile/device-profile-dialog.component';
import { DeviceProfileAutocompleteComponent } from './profile/device-profile-autocomplete.component';
import { MqttDeviceProfileTransportConfigurationComponent } from './profile/device/mqtt-device-profile-transport-configuration.component';
import { Lwm2mDeviceProfileTransportConfigurationComponent } from './profile/device/lwm2m-device-profile-transport-configuration.component';
@NgModule({
declarations:
@ -171,6 +173,8 @@ import { DeviceProfileAutocompleteComponent } from './profile/device-profile-aut
DefaultDeviceProfileConfigurationComponent,
DeviceProfileConfigurationComponent,
DefaultDeviceProfileTransportConfigurationComponent,
MqttDeviceProfileTransportConfigurationComponent,
Lwm2mDeviceProfileTransportConfigurationComponent,
DeviceProfileTransportConfigurationComponent,
DeviceProfileDataComponent,
DeviceProfileComponent,
@ -239,6 +243,8 @@ import { DeviceProfileAutocompleteComponent } from './profile/device-profile-aut
DefaultDeviceProfileConfigurationComponent,
DeviceProfileConfigurationComponent,
DefaultDeviceProfileTransportConfigurationComponent,
MqttDeviceProfileTransportConfigurationComponent,
Lwm2mDeviceProfileTransportConfigurationComponent,
DeviceProfileTransportConfigurationComponent,
DeviceProfileDataComponent,
DeviceProfileComponent,

1
ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.html

@ -19,6 +19,7 @@
<input matInput type="text" placeholder="{{ 'device-profile.device-profile' | translate }}"
#deviceProfileInput
formControlName="deviceProfile"
(focusin)="onFocus()"
[required]="required"
(keydown)="deviceProfileEnter($event)"
(keypress)="deviceProfileEnter($event)"

21
ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.ts

@ -19,7 +19,7 @@ import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from
import { Observable } from 'rxjs';
import { PageLink } from '@shared/models/page/page-link';
import { Direction } from '@shared/models/page/sort-order';
import { map, mergeMap, startWith, tap } from 'rxjs/operators';
import { map, mergeMap, share, startWith, tap } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { AppState } from '@app/core/core.state';
import { TranslateService } from '@ngx-translate/core';
@ -83,6 +83,8 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor,
searchText = '';
private dirty = false;
private propagateChange = (v: any) => { };
constructor(private store: Store<AppState>,
@ -115,9 +117,9 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor,
}
this.updateView(modelValue);
}),
startWith<string | DeviceProfileInfo>(''),
map(value => value ? (typeof value === 'string' ? value : value.name) : ''),
mergeMap(name => this.fetchDeviceProfiles(name) )
mergeMap(name => this.fetchDeviceProfiles(name) ),
share()
);
}
@ -144,14 +146,23 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor,
this.deviceProfileService.getDeviceProfileInfo(value.id).subscribe(
(profile) => {
this.modelValue = new DeviceProfileId(profile.id.id);
this.selectDeviceProfileFormGroup.get('deviceProfile').patchValue(profile, {emitEvent: true});
this.selectDeviceProfileFormGroup.get('deviceProfile').patchValue(profile, {emitEvent: false});
this.deviceProfileChanged.emit(profile);
}
);
} else {
this.modelValue = null;
this.selectDeviceProfileFormGroup.get('deviceProfile').patchValue(null, {emitEvent: true});
this.selectDeviceProfileFormGroup.get('deviceProfile').patchValue(null, {emitEvent: false});
this.selectDefaultDeviceProfileIfNeeded();
}
this.dirty = true;
}
onFocus() {
if (this.dirty) {
this.selectDeviceProfileFormGroup.get('deviceProfile').updateValueAndValidity({onlySelf: true, emitEvent: true});
this.dirty = false;
}
}
updateView(deviceProfile: DeviceProfileInfo | null) {

4
ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.html

@ -17,7 +17,7 @@
-->
<div [formGroup]="deviceProfileDataFormGroup" style="padding-bottom: 16px;">
<mat-accordion multi="true">
<mat-expansion-panel [expanded]="true">
<mat-expansion-panel *ngIf="displayProfileConfiguration" [expanded]="true">
<mat-expansion-panel-header>
<mat-panel-title>
<div translate>device-profile.profile-configuration</div>
@ -28,7 +28,7 @@
required>
</tb-device-profile-configuration>
</mat-expansion-panel>
<mat-expansion-panel [expanded]="true">
<mat-expansion-panel *ngIf="displayTransportConfiguration" [expanded]="true">
<mat-expansion-panel-header>
<mat-panel-title>
<div translate>device-profile.transport-configuration</div>

16
ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.ts

@ -19,7 +19,12 @@ import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Valida
import { Store } from '@ngrx/store';
import { AppState } from '@app/core/core.state';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { DeviceProfileData } from '@shared/models/device.models';
import {
DeviceProfileData,
DeviceProfileType,
deviceProfileTypeConfigurationInfoMap,
DeviceTransportType, deviceTransportTypeConfigurationInfoMap
} from '@shared/models/device.models';
@Component({
selector: 'tb-device-profile-data',
@ -47,6 +52,9 @@ export class DeviceProfileDataComponent implements ControlValueAccessor, OnInit
@Input()
disabled: boolean;
displayProfileConfiguration: boolean;
displayTransportConfiguration: boolean;
private propagateChange = (v: any) => { };
constructor(private store: Store<AppState>,
@ -80,6 +88,12 @@ export class DeviceProfileDataComponent implements ControlValueAccessor, OnInit
}
writeValue(value: DeviceProfileData | null): void {
const deviceProfileType = value?.configuration?.type;
this.displayProfileConfiguration = deviceProfileType &&
deviceProfileTypeConfigurationInfoMap.get(deviceProfileType).hasProfileConfiguration;
const deviceTransportType = value?.transportConfiguration?.type;
this.displayTransportConfiguration = deviceTransportType &&
deviceTransportTypeConfigurationInfoMap.get(deviceTransportType).hasProfileConfiguration;
this.deviceProfileDataFormGroup.patchValue({configuration: value?.configuration}, {emitEvent: false});
this.deviceProfileDataFormGroup.patchValue({transportConfiguration: value?.transportConfiguration}, {emitEvent: false});
}

12
ui-ngx/src/app/modules/home/components/profile/device/device-profile-transport-configuration.component.html

@ -23,5 +23,17 @@
formControlName="configuration">
</tb-default-device-profile-transport-configuration>
</ng-template>
<ng-template [ngSwitchCase]="deviceTransportType.MQTT">
<tb-mqtt-device-profile-transport-configuration
[required]="required"
formControlName="configuration">
</tb-mqtt-device-profile-transport-configuration>
</ng-template>
<ng-template [ngSwitchCase]="deviceTransportType.LWM2M">
<tb-lwm2m-device-profile-transport-configuration
[required]="required"
formControlName="configuration">
</tb-lwm2m-device-profile-transport-configuration>
</ng-template>
</div>
</div>

24
ui-ngx/src/app/modules/home/components/profile/device/lwm2m-device-profile-transport-configuration.component.html

@ -0,0 +1,24 @@
<!--
Copyright © 2016-2020 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<form [formGroup]="lwm2mDeviceProfileTransportConfigurationFormGroup" style="padding-bottom: 16px;">
<tb-json-object-edit
[required]="required"
label="{{ 'device-profile.transport-type-lwm2m' | translate }}"
formControlName="configuration">
</tb-json-object-edit>
</form>

96
ui-ngx/src/app/modules/home/components/profile/device/lwm2m-device-profile-transport-configuration.component.ts

@ -0,0 +1,96 @@
///
/// Copyright © 2016-2020 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, forwardRef, Input, OnInit } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@app/core/core.state';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import {
DeviceProfileTransportConfiguration,
DeviceTransportType, Lwm2mDeviceProfileTransportConfiguration
} from '@shared/models/device.models';
@Component({
selector: 'tb-lwm2m-device-profile-transport-configuration',
templateUrl: './lwm2m-device-profile-transport-configuration.component.html',
styleUrls: [],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => Lwm2mDeviceProfileTransportConfigurationComponent),
multi: true
}]
})
export class Lwm2mDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, OnInit {
lwm2mDeviceProfileTransportConfigurationFormGroup: FormGroup;
private requiredValue: boolean;
get required(): boolean {
return this.requiredValue;
}
@Input()
set required(value: boolean) {
this.requiredValue = coerceBooleanProperty(value);
}
@Input()
disabled: boolean;
private propagateChange = (v: any) => { };
constructor(private store: Store<AppState>,
private fb: FormBuilder) {
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
ngOnInit() {
this.lwm2mDeviceProfileTransportConfigurationFormGroup = this.fb.group({
configuration: [null, Validators.required]
});
this.lwm2mDeviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => {
this.updateModel();
});
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (this.disabled) {
this.lwm2mDeviceProfileTransportConfigurationFormGroup.disable({emitEvent: false});
} else {
this.lwm2mDeviceProfileTransportConfigurationFormGroup.enable({emitEvent: false});
}
}
writeValue(value: Lwm2mDeviceProfileTransportConfiguration | null): void {
this.lwm2mDeviceProfileTransportConfigurationFormGroup.patchValue({configuration: value}, {emitEvent: false});
}
private updateModel() {
let configuration: DeviceProfileTransportConfiguration = null;
if (this.lwm2mDeviceProfileTransportConfigurationFormGroup.valid) {
configuration = this.lwm2mDeviceProfileTransportConfigurationFormGroup.getRawValue().configuration;
configuration.type = DeviceTransportType.LWM2M;
}
this.propagateChange(configuration);
}
}

24
ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html

@ -0,0 +1,24 @@
<!--
Copyright © 2016-2020 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<form [formGroup]="mqttDeviceProfileTransportConfigurationFormGroup" style="padding-bottom: 16px;">
<tb-json-object-edit
[required]="required"
label="{{ 'device-profile.transport-type-mqtt' | translate }}"
formControlName="configuration">
</tb-json-object-edit>
</form>

96
ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts

@ -0,0 +1,96 @@
///
/// Copyright © 2016-2020 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, forwardRef, Input, OnInit } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@app/core/core.state';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import {
DeviceProfileTransportConfiguration,
DeviceTransportType, MqttDeviceProfileTransportConfiguration
} from '@shared/models/device.models';
@Component({
selector: 'tb-mqtt-device-profile-transport-configuration',
templateUrl: './mqtt-device-profile-transport-configuration.component.html',
styleUrls: [],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MqttDeviceProfileTransportConfigurationComponent),
multi: true
}]
})
export class MqttDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, OnInit {
mqttDeviceProfileTransportConfigurationFormGroup: FormGroup;
private requiredValue: boolean;
get required(): boolean {
return this.requiredValue;
}
@Input()
set required(value: boolean) {
this.requiredValue = coerceBooleanProperty(value);
}
@Input()
disabled: boolean;
private propagateChange = (v: any) => { };
constructor(private store: Store<AppState>,
private fb: FormBuilder) {
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
ngOnInit() {
this.mqttDeviceProfileTransportConfigurationFormGroup = this.fb.group({
configuration: [null, Validators.required]
});
this.mqttDeviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => {
this.updateModel();
});
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (this.disabled) {
this.mqttDeviceProfileTransportConfigurationFormGroup.disable({emitEvent: false});
} else {
this.mqttDeviceProfileTransportConfigurationFormGroup.enable({emitEvent: false});
}
}
writeValue(value: MqttDeviceProfileTransportConfiguration | null): void {
this.mqttDeviceProfileTransportConfigurationFormGroup.patchValue({configuration: value}, {emitEvent: false});
}
private updateModel() {
let configuration: DeviceProfileTransportConfiguration = null;
if (this.mqttDeviceProfileTransportConfigurationFormGroup.valid) {
configuration = this.mqttDeviceProfileTransportConfigurationFormGroup.getRawValue().configuration;
configuration.type = DeviceTransportType.MQTT;
}
this.propagateChange(configuration);
}
}

1
ui-ngx/src/app/modules/home/components/profile/tenant-profile-autocomplete.component.html

@ -19,6 +19,7 @@
<input matInput type="text" placeholder="{{ 'tenant-profile.tenant-profile' | translate }}"
#tenantProfileInput
formControlName="tenantProfile"
(focusin)="onFocus()"
[required]="required"
(keydown)="tenantProfileEnter($event)"
(keypress)="tenantProfileEnter($event)"

20
ui-ngx/src/app/modules/home/components/profile/tenant-profile-autocomplete.component.ts

@ -19,7 +19,7 @@ import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from
import { Observable } from 'rxjs';
import { PageLink } from '@shared/models/page/page-link';
import { Direction } from '@shared/models/page/sort-order';
import { map, mergeMap, startWith, tap } from 'rxjs/operators';
import { map, mergeMap, share, startWith, tap } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { AppState } from '@app/core/core.state';
import { TranslateService } from '@ngx-translate/core';
@ -74,6 +74,8 @@ export class TenantProfileAutocompleteComponent implements ControlValueAccessor,
searchText = '';
private dirty = false;
private propagateChange = (v: any) => { };
constructor(private store: Store<AppState>,
@ -106,9 +108,9 @@ export class TenantProfileAutocompleteComponent implements ControlValueAccessor,
}
this.updateView(modelValue);
}),
startWith<string | EntityInfoData>(''),
map(value => value ? (typeof value === 'string' ? value : value.name) : ''),
mergeMap(name => this.fetchTenantProfiles(name) )
mergeMap(name => this.fetchTenantProfiles(name) ),
share()
);
}
@ -136,14 +138,22 @@ export class TenantProfileAutocompleteComponent implements ControlValueAccessor,
this.tenantProfileService.getTenantProfileInfo(value.id).subscribe(
(profile) => {
this.modelValue = new TenantProfileId(profile.id.id);
this.selectTenantProfileFormGroup.get('tenantProfile').patchValue(profile, {emitEvent: true});
this.selectTenantProfileFormGroup.get('tenantProfile').patchValue(profile, {emitEvent: false});
}
);
} else {
this.modelValue = null;
this.selectTenantProfileFormGroup.get('tenantProfile').patchValue(null, {emitEvent: true});
this.selectTenantProfileFormGroup.get('tenantProfile').patchValue(null, {emitEvent: false});
this.selectDefaultTenantProfileIfNeeded();
}
this.dirty = true;
}
onFocus() {
if (this.dirty) {
this.selectTenantProfileFormGroup.get('tenantProfile').updateValueAndValidity({onlySelf: true, emitEvent: true});
this.dirty = false;
}
}
updateView(value: TenantProfileId | null) {

4
ui-ngx/src/app/modules/home/pages/device/data/device-data.component.html

@ -17,7 +17,7 @@
-->
<div [formGroup]="deviceDataFormGroup" style="padding-bottom: 16px;">
<mat-accordion multi="true">
<mat-expansion-panel [expanded]="true">
<mat-expansion-panel *ngIf="displayDeviceConfiguration" [expanded]="true">
<mat-expansion-panel-header>
<mat-panel-title>
<div translate>device.device-configuration</div>
@ -28,7 +28,7 @@
required>
</tb-device-configuration>
</mat-expansion-panel>
<mat-expansion-panel [expanded]="true">
<mat-expansion-panel *ngIf="displayTransportConfiguration" [expanded]="true">
<mat-expansion-panel-header>
<mat-panel-title>
<div translate>device.transport-configuration</div>

15
ui-ngx/src/app/modules/home/pages/device/data/device-data.component.ts

@ -19,7 +19,11 @@ import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Valida
import { Store } from '@ngrx/store';
import { AppState } from '@app/core/core.state';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { DeviceData } from '@shared/models/device.models';
import {
DeviceData,
deviceProfileTypeConfigurationInfoMap,
deviceTransportTypeConfigurationInfoMap
} from '@shared/models/device.models';
@Component({
selector: 'tb-device-data',
@ -47,6 +51,9 @@ export class DeviceDataComponent implements ControlValueAccessor, OnInit {
@Input()
disabled: boolean;
displayDeviceConfiguration: boolean;
displayTransportConfiguration: boolean;
private propagateChange = (v: any) => { };
constructor(private store: Store<AppState>,
@ -80,6 +87,12 @@ export class DeviceDataComponent implements ControlValueAccessor, OnInit {
}
writeValue(value: DeviceData | null): void {
const deviceProfileType = value?.configuration?.type;
this.displayDeviceConfiguration = deviceProfileType &&
deviceProfileTypeConfigurationInfoMap.get(deviceProfileType).hasDeviceConfiguration;
const deviceTransportType = value?.transportConfiguration?.type;
this.displayTransportConfiguration = deviceTransportType &&
deviceTransportTypeConfigurationInfoMap.get(deviceTransportType).hasDeviceConfiguration;
this.deviceDataFormGroup.patchValue({configuration: value?.configuration}, {emitEvent: false});
this.deviceDataFormGroup.patchValue({transportConfiguration: value?.transportConfiguration}, {emitEvent: false});
}

12
ui-ngx/src/app/modules/home/pages/device/data/device-transport-configuration.component.html

@ -23,5 +23,17 @@
formControlName="configuration">
</tb-default-device-transport-configuration>
</ng-template>
<ng-template [ngSwitchCase]="deviceTransportType.MQTT">
<tb-mqtt-device-transport-configuration
[required]="required"
formControlName="configuration">
</tb-mqtt-device-transport-configuration>
</ng-template>
<ng-template [ngSwitchCase]="deviceTransportType.LWM2M">
<tb-lwm2m-device-transport-configuration
[required]="required"
formControlName="configuration">
</tb-lwm2m-device-transport-configuration>
</ng-template>
</div>
</div>

24
ui-ngx/src/app/modules/home/pages/device/data/lwm2m-device-transport-configuration.component.html

@ -0,0 +1,24 @@
<!--
Copyright © 2016-2020 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<form [formGroup]="lwm2mDeviceTransportConfigurationFormGroup" style="padding-bottom: 16px;">
<tb-json-object-edit
[required]="required"
label="{{ 'device-profile.transport-type-lwm2m' | translate }}"
formControlName="configuration">
</tb-json-object-edit>
</form>

96
ui-ngx/src/app/modules/home/pages/device/data/lwm2m-device-transport-configuration.component.ts

@ -0,0 +1,96 @@
///
/// Copyright © 2016-2020 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, forwardRef, Input, OnInit } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@app/core/core.state';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import {
DeviceTransportConfiguration,
DeviceTransportType, Lwm2mDeviceTransportConfiguration
} from '@shared/models/device.models';
@Component({
selector: 'tb-lwm2m-device-transport-configuration',
templateUrl: './lwm2m-device-transport-configuration.component.html',
styleUrls: [],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => Lwm2mDeviceTransportConfigurationComponent),
multi: true
}]
})
export class Lwm2mDeviceTransportConfigurationComponent implements ControlValueAccessor, OnInit {
lwm2mDeviceTransportConfigurationFormGroup: FormGroup;
private requiredValue: boolean;
get required(): boolean {
return this.requiredValue;
}
@Input()
set required(value: boolean) {
this.requiredValue = coerceBooleanProperty(value);
}
@Input()
disabled: boolean;
private propagateChange = (v: any) => { };
constructor(private store: Store<AppState>,
private fb: FormBuilder) {
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
ngOnInit() {
this.lwm2mDeviceTransportConfigurationFormGroup = this.fb.group({
configuration: [null, Validators.required]
});
this.lwm2mDeviceTransportConfigurationFormGroup.valueChanges.subscribe(() => {
this.updateModel();
});
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (this.disabled) {
this.lwm2mDeviceTransportConfigurationFormGroup.disable({emitEvent: false});
} else {
this.lwm2mDeviceTransportConfigurationFormGroup.enable({emitEvent: false});
}
}
writeValue(value: Lwm2mDeviceTransportConfiguration | null): void {
this.lwm2mDeviceTransportConfigurationFormGroup.patchValue({configuration: value}, {emitEvent: false});
}
private updateModel() {
let configuration: DeviceTransportConfiguration = null;
if (this.lwm2mDeviceTransportConfigurationFormGroup.valid) {
configuration = this.lwm2mDeviceTransportConfigurationFormGroup.getRawValue().configuration;
configuration.type = DeviceTransportType.LWM2M;
}
this.propagateChange(configuration);
}
}

24
ui-ngx/src/app/modules/home/pages/device/data/mqtt-device-transport-configuration.component.html

@ -0,0 +1,24 @@
<!--
Copyright © 2016-2020 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<form [formGroup]="mqttDeviceTransportConfigurationFormGroup" style="padding-bottom: 16px;">
<tb-json-object-edit
[required]="required"
label="{{ 'device-profile.transport-type-mqtt' | translate }}"
formControlName="configuration">
</tb-json-object-edit>
</form>

96
ui-ngx/src/app/modules/home/pages/device/data/mqtt-device-transport-configuration.component.ts

@ -0,0 +1,96 @@
///
/// Copyright © 2016-2020 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, forwardRef, Input, OnInit } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@app/core/core.state';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import {
DeviceTransportConfiguration,
DeviceTransportType, MqttDeviceTransportConfiguration
} from '@shared/models/device.models';
@Component({
selector: 'tb-mqtt-device-transport-configuration',
templateUrl: './mqtt-device-transport-configuration.component.html',
styleUrls: [],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MqttDeviceTransportConfigurationComponent),
multi: true
}]
})
export class MqttDeviceTransportConfigurationComponent implements ControlValueAccessor, OnInit {
mqttDeviceTransportConfigurationFormGroup: FormGroup;
private requiredValue: boolean;
get required(): boolean {
return this.requiredValue;
}
@Input()
set required(value: boolean) {
this.requiredValue = coerceBooleanProperty(value);
}
@Input()
disabled: boolean;
private propagateChange = (v: any) => { };
constructor(private store: Store<AppState>,
private fb: FormBuilder) {
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
ngOnInit() {
this.mqttDeviceTransportConfigurationFormGroup = this.fb.group({
configuration: [null, Validators.required]
});
this.mqttDeviceTransportConfigurationFormGroup.valueChanges.subscribe(() => {
this.updateModel();
});
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (this.disabled) {
this.mqttDeviceTransportConfigurationFormGroup.disable({emitEvent: false});
} else {
this.mqttDeviceTransportConfigurationFormGroup.enable({emitEvent: false});
}
}
writeValue(value: MqttDeviceTransportConfiguration | null): void {
this.mqttDeviceTransportConfigurationFormGroup.patchValue({configuration: value}, {emitEvent: false});
}
private updateModel() {
let configuration: DeviceTransportConfiguration = null;
if (this.mqttDeviceTransportConfigurationFormGroup.valid) {
configuration = this.mqttDeviceTransportConfigurationFormGroup.getRawValue().configuration;
configuration.type = DeviceTransportType.MQTT;
}
this.propagateChange(configuration);
}
}

4
ui-ngx/src/app/modules/home/pages/device/device.component.ts

@ -141,7 +141,7 @@ export class DeviceComponent extends EntityComponent<DeviceInfo> {
}
onDeviceProfileChanged(deviceProfile: DeviceProfileInfo) {
if (deviceProfile) {
if (deviceProfile && this.isEdit) {
const deviceProfileType: DeviceProfileType = deviceProfile.type;
const deviceTransportType: DeviceTransportType = deviceProfile.transportType;
let deviceData: DeviceData = this.entityForm.getRawValue().deviceData;
@ -151,6 +151,7 @@ export class DeviceComponent extends EntityComponent<DeviceInfo> {
transportConfiguration: createDeviceTransportConfiguration(deviceTransportType)
};
this.entityForm.patchValue({deviceData});
this.entityForm.markAsDirty();
} else {
let changed = false;
if (deviceData.configuration.type !== deviceProfileType) {
@ -163,6 +164,7 @@ export class DeviceComponent extends EntityComponent<DeviceInfo> {
}
if (changed) {
this.entityForm.patchValue({deviceData});
this.entityForm.markAsDirty();
}
}
}

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

@ -29,12 +29,16 @@ import { DeviceConfigurationComponent } from './data/device-configuration.compon
import { DeviceDataComponent } from './data/device-data.component';
import { DefaultDeviceTransportConfigurationComponent } from './data/default-device-transport-configuration.component';
import { DeviceTransportConfigurationComponent } from './data/device-transport-configuration.component';
import { MqttDeviceTransportConfigurationComponent } from './data/mqtt-device-transport-configuration.component';
import { Lwm2mDeviceTransportConfigurationComponent } from './data/lwm2m-device-transport-configuration.component';
@NgModule({
declarations: [
DefaultDeviceConfigurationComponent,
DeviceConfigurationComponent,
DefaultDeviceTransportConfigurationComponent,
MqttDeviceTransportConfigurationComponent,
Lwm2mDeviceTransportConfigurationComponent,
DeviceTransportConfigurationComponent,
DeviceDataComponent,
DeviceComponent,

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

@ -34,12 +34,29 @@ export enum DeviceTransportType {
LWM2M = 'LWM2M'
}
export interface DeviceConfigurationFormInfo {
hasProfileConfiguration: boolean;
hasDeviceConfiguration: boolean;
}
export const deviceProfileTypeTranslationMap = new Map<DeviceProfileType, string>(
[
[DeviceProfileType.DEFAULT, 'device-profile.type-default']
]
);
export const deviceProfileTypeConfigurationInfoMap = new Map<DeviceProfileType, DeviceConfigurationFormInfo>(
[
[
DeviceProfileType.DEFAULT,
{
hasProfileConfiguration: false,
hasDeviceConfiguration: false,
}
]
]
);
export const deviceTransportTypeTranslationMap = new Map<DeviceTransportType, string>(
[
[DeviceTransportType.DEFAULT, 'device-profile.transport-type-default'],
@ -48,6 +65,32 @@ export const deviceTransportTypeTranslationMap = new Map<DeviceTransportType, st
]
);
export const deviceTransportTypeConfigurationInfoMap = new Map<DeviceTransportType, DeviceConfigurationFormInfo>(
[
[
DeviceTransportType.DEFAULT,
{
hasProfileConfiguration: false,
hasDeviceConfiguration: false,
}
],
[
DeviceTransportType.MQTT,
{
hasProfileConfiguration: true,
hasDeviceConfiguration: true,
}
],
[
DeviceTransportType.LWM2M,
{
hasProfileConfiguration: true,
hasDeviceConfiguration: true,
}
]
]
);
export interface DefaultDeviceProfileConfiguration {
[key: string]: any;
}

Loading…
Cancel
Save