diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/Lwm2mDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/Lwm2mDeviceProfileTransportConfiguration.java index 0c1441f031..08ad7acf5a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/Lwm2mDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/Lwm2mDeviceProfileTransportConfiguration.java @@ -29,6 +29,7 @@ public class Lwm2mDeviceProfileTransportConfiguration implements DeviceProfileTr private static final long serialVersionUID = 6257277825459600068L; private TelemetryMappingConfiguration observeAttr; + private boolean bootstrapServerUpdateEnable; private List bootstrap; private OtherConfiguration clientLwM2mSettings; diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java index fc901abd35..a9bc2401c4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java @@ -709,12 +709,16 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D private void validateLwm2mServersConfigOfBootstrapForClient(List lwM2MBootstrapServersConfigurations) { Set uris = new HashSet<>(); + Set shortServerIds = new HashSet<>(); for (LwM2MBootstrapServerCredential bootstrapServerCredential : lwM2MBootstrapServersConfigurations) { AbstractLwM2MBootstrapServerCredential serverConfig = (AbstractLwM2MBootstrapServerCredential) bootstrapServerCredential; String server = serverConfig.isBootstrapServerIs() ? "Bootstrap Server" : "LwM2M Server" + " shortServerId: " + serverConfig.getShortServerId() + ":"; if (serverConfig.getShortServerId() < 1 || serverConfig.getShortServerId() > 65534) { throw new DeviceCredentialsValidationException(server + " ShortServerId must not be less than 1 and more than 65534!"); } + if (!shortServerIds.add(serverConfig.getShortServerId())){ + throw new DeviceCredentialsValidationException(server + " \"Short server Id\" value = " + serverConfig.getShortServerId() + ". This value must be a unique value for all servers!"); + }; String uri = serverConfig.getHost() + ":" + serverConfig.getPort(); if (!uris.add(uri)){ throw new DeviceCredentialsValidationException(server + " \"Host + port\" value = " + uri + ". This value must be a unique value for all servers!"); diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-bootstrap-config-servers.component.html b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-bootstrap-config-servers.component.html index cd00d1661a..ebd80ef558 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-bootstrap-config-servers.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-bootstrap-config-servers.component.html @@ -32,7 +32,7 @@ diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-bootstrap-config-servers.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-bootstrap-config-servers.component.ts index a4bf5d4958..9230195efe 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-bootstrap-config-servers.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-bootstrap-config-servers.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, forwardRef, Input, OnInit } from '@angular/core'; +import { ChangeDetectorRef, Component, forwardRef, Input, OnInit } from '@angular/core'; import { AbstractControl, ControlValueAccessor, @@ -57,6 +57,15 @@ export class Lwm2mBootstrapConfigServersComponent implements OnInit, ControlValu @Input() disabled: boolean; + public isBootstrapServerUpdateEnableValue: boolean; + @Input() + set isBootstrapServerUpdateEnable(value: boolean) { + this.isBootstrapServerUpdateEnableValue = value; + if (!value) { + this.removeBootstrapServerConfig(); + } + } + private valueChangeSubscription: Subscription = null; private propagateChange = (v: any) => { }; @@ -138,7 +147,7 @@ export class Lwm2mBootstrapConfigServersComponent implements OnInit, ControlValu } addServerConfig(): void { - const addDialogObs = this.isBootstrapAdded() ? of(false) : + const addDialogObs = (this.isBootstrapAdded() || !this.isBootstrapServerUpdateEnableValue) ? of(false) : this.matDialog.open(Lwm2mBootstrapAddConfigServerDialogComponent, { disableClose: true, panelClass: ['tb-dialog', 'tb-fullscreen-dialog'] @@ -178,6 +187,15 @@ export class Lwm2mBootstrapConfigServersComponent implements OnInit, ControlValu return false; } + private removeBootstrapServerConfig(): void { + if (this.bootstrapConfigServersFormGroup) { + const bootstrapServerIndex = this.serverConfigsFromArray().getRawValue().findIndex(server => server.bootstrapServerIs === true); + if (bootstrapServerIndex !== -1) { + this.serverConfigsFromArray().removeAt(bootstrapServerIndex); + } + } + } + private updateModel() { const serverConfigs: Array = this.serverConfigsFromArray().value; this.propagateChange(serverConfigs); diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.html index 20cbd63168..05643ae497 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.html @@ -33,8 +33,12 @@
+ + {{ 'device-profile.lwm2m.include-bootstrap-server' | translate }} +
- +
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts index d16a0eafca..d8fc933fdb 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts @@ -52,6 +52,8 @@ import _ from 'lodash'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { Lwm2mSecurityType } from '@shared/models/lwm2m-security-config.models'; +import { DialogService } from '@core/services/dialog.service'; +import { TranslateService } from '@ngx-translate/core'; @Component({ selector: 'tb-profile-lwm2m-device-transport-configuration', @@ -72,6 +74,7 @@ import { Lwm2mSecurityType } from '@shared/models/lwm2m-security-config.models'; export class Lwm2mDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, Validator, OnDestroy { public disabled = false; + public isBootstrapServerUpdateEnable: boolean; private requiredValue: boolean; private destroy$ = new Subject(); @@ -94,12 +97,15 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro private propagateChange = (v: any) => { } - constructor(private fb: FormBuilder, + constructor(public translate: TranslateService, + private fb: FormBuilder, private cd: ChangeDetectorRef, + private dialogService: DialogService, private deviceProfileService: DeviceProfileService) { this.lwm2mDeviceProfileFormGroup = this.fb.group({ objectIds: [null], observeAttrTelemetry: [null], + bootstrapServerUpdateEnable: [false], bootstrap: [[]], clientLwM2mSettings: this.fb.group({ clientOnlyObserveAfterConnect: [1, []], @@ -114,6 +120,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro compositeOperationsSupport: [false] }) }); + this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.fwUpdateStrategy').valueChanges.pipe( takeUntil(this.destroy$) ).subscribe((fwStrategy) => { @@ -125,6 +132,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro .reset(DEFAULT_FW_UPDATE_RESOURCE, {emitEvent: false}); } }); + this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.swUpdateStrategy').valueChanges.pipe( takeUntil(this.destroy$) ).subscribe((swStrategy) => { @@ -136,6 +144,39 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro .reset(DEFAULT_SW_UPDATE_RESOURCE, {emitEvent: false}); } }); + + this.lwm2mDeviceProfileFormGroup.get('bootstrapServerUpdateEnable').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((value) => { + if (!value) { + const bootstrap = this.lwm2mDeviceProfileFormGroup.get('bootstrap').value; + const bootstrapResultArray = bootstrap.filter(server => server.bootstrapServerIs === true); + if (bootstrapResultArray.length) { + this.dialogService.confirm( + this.translate.instant('device-profile.lwm2m.bootstrap-update-title'), + this.translate.instant('device-profile.lwm2m.bootstrap-update-text'), + this.translate.instant('action.no'), + this.translate.instant('action.yes'), + ).pipe( + takeUntil(this.destroy$) + ).subscribe((result) => { + if (result) { + this.isBootstrapServerUpdateEnable = value; + } else { + this.lwm2mDeviceProfileFormGroup.patchValue({ + bootstrapServerUpdateEnable: true + }, {emitEvent: true}); + } + this.cd.markForCheck(); + }); + } else { + this.isBootstrapServerUpdateEnable = value; + } + } else { + this.isBootstrapServerUpdateEnable = value; + } + }); + this.lwm2mDeviceProfileFormGroup.valueChanges.pipe( takeUntil(this.destroy$) ).subscribe((value) => { @@ -217,6 +258,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro objectIds: value, observeAttrTelemetry: this.getObserveAttrTelemetryObjects(value), bootstrap: this.configurationValue.bootstrap, + bootstrapServerUpdateEnable: this.configurationValue.bootstrapServerUpdateEnable || false, clientLwM2mSettings: { clientOnlyObserveAfterConnect: this.configurationValue.clientLwM2mSettings.clientOnlyObserveAfterConnect, fwUpdateStrategy: this.configurationValue.clientLwM2mSettings.fwUpdateStrategy || 1, @@ -232,6 +274,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro } }, {emitEvent: false}); + this.isBootstrapServerUpdateEnable = this.configurationValue.bootstrapServerUpdateEnable || false; if (!this.disabled) { this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.powerMode').updateValueAndValidity({onlySelf: true}); this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.fwUpdateStrategy').updateValueAndValidity({onlySelf: true}); @@ -261,6 +304,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro } this.configurationValue.bootstrap = config.bootstrap; this.configurationValue.clientLwM2mSettings = config.clientLwM2mSettings; + this.configurationValue.bootstrapServerUpdateEnable = config.bootstrapServerUpdateEnable; this.updateModel(); } diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts index 543c000e12..56ae00c5a9 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts @@ -148,6 +148,7 @@ export interface ServerSecurityConfigInfo extends ServerSecurityConfig { export interface Lwm2mProfileConfigModels { clientLwM2mSettings: ClientLwM2mSettings; observeAttr: ObservableAttributes; + bootstrapServerUpdateEnable: boolean; bootstrap: Array; } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 7bdc11c479..b9ca3a3ad7 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1348,6 +1348,9 @@ "binding-tooltip": "Tis is the list in the\"binding\" resource of the LwM2M server object - /1/x/7.\nIndicates the supported binding modes in the LwM2M Client.\nThis value SHOULD be the same as the value in the “Supported Binding and Modes” resource in the Device Object (/3/0/16).\nWhile multiple transports are supported, only one transport binding can be used during the entire Transport Session.\nAs an example, when UDP and SMS are both supported, the LwM2M Client and the LwM2M Server can choose to communicate either over UDP or SMS during the entire Transport Session.", "bootstrap-server": "Bootstrap Server", "lwm2m-server": "LwM2M Server", + "include-bootstrap-server": "Include Bootstrap Server updates", + "bootstrap-update-title": "You already have configured Bootstrap Server. Are you sure you want to exclude the updates?", + "bootstrap-update-text": "Be careful, after the confirmation the Bootstrap Server configuration data will become unrecoverable.", "server-host": "Host", "server-host-required": "Host is required.", "server-port": "Port",