diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index fa2cc58ace..739f984a97 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -300,7 +300,6 @@ public class DeviceController extends BaseController { try { Device device = checkDeviceId(deviceCredentials.getDeviceId(), Operation.WRITE_CREDENTIALS); DeviceCredentials result = checkNotNull(deviceCredentialsService.updateDeviceCredentials(getCurrentUser().getTenantId(), deviceCredentials)); - //log.info("0 LwM2M CredentialsUpdate start) tbClusterService.pushMsgToCore(new DeviceCredentialsUpdateNotificationMsg(getCurrentUser().getTenantId(), deviceCredentials.getDeviceId(), result), null); sendEntityNotificationMsg(getTenantId(), device.getId(), EdgeEventActionType.CREDENTIALS_UPDATED); diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceLwm2mController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceLwm2mController.java index c83ef5a470..5f2e946f10 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceLwm2mController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceLwm2mController.java @@ -15,17 +15,32 @@ */ package org.thingsboard.server.controller; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; +import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.lwm2m.LwM2mObject; import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.security.permission.Resource; import java.util.List; +import java.util.Map; @Slf4j @RestController @@ -33,13 +48,16 @@ import java.util.List; @RequestMapping("/api") public class DeviceLwm2mController extends BaseController { - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") - @RequestMapping(value = "/lwm2m/deviceProfile/{objectIds}", method = RequestMethod.GET) + @RequestMapping(value = "/lwm2m/deviceProfile", params = {"sortOrder", "sortProperty"}, method = RequestMethod.GET) @ResponseBody - public List getLwm2mListObjects(@PathVariable("objectIds") int[] objectIds) throws ThingsboardException { + public List getLwm2mListObjects(@RequestParam String sortOrder, + @RequestParam String sortProperty, + @RequestParam(required = false) int[] objectIds, + @RequestParam(required = false) String searchText) + throws ThingsboardException { try { - return lwM2MModelsRepository.getLwm2mObjects(objectIds, null); + return lwM2MModelsRepository.getLwm2mObjects(objectIds, searchText, sortProperty, sortOrder); } catch (Exception e) { throw handleException(e); } @@ -50,11 +68,11 @@ public class DeviceLwm2mController extends BaseController { @ResponseBody public PageData getLwm2mListObjects(@RequestParam int pageSize, @RequestParam int page, - @RequestParam(required = false) String textSearch, + @RequestParam(required = false) String searchText, @RequestParam(required = false) String sortProperty, @RequestParam(required = false) String sortOrder) throws ThingsboardException { try { - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + PageLink pageLink = createPageLink(pageSize, page, searchText, sortProperty, sortOrder); return checkNotNull(lwM2MModelsRepository.findDeviceLwm2mObjects(getTenantId(), pageLink)); } catch (Exception e) { throw handleException(e); @@ -72,4 +90,40 @@ public class DeviceLwm2mController extends BaseController { throw handleException(e); } } + + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/lwm2m/device-credentials", method = RequestMethod.POST) + @ResponseBody + public Device saveDeviceWithCredentials(@RequestBody (required=false) Map, Object> deviceWithDeviceCredentials) throws ThingsboardException { + ObjectMapper mapper = new ObjectMapper(); + Device device = checkNotNull(mapper.convertValue(deviceWithDeviceCredentials.get(Device.class), Device.class)); + DeviceCredentials credentials = checkNotNull(mapper.convertValue( deviceWithDeviceCredentials.get(DeviceCredentials.class), DeviceCredentials.class)); + try { + device.setTenantId(getCurrentUser().getTenantId()); + checkEntity(device.getId(), device, Resource.DEVICE); + Device savedDevice = deviceService.saveDeviceWithCredentials(device, credentials); + checkNotNull(savedDevice); + + tbClusterService.onDeviceChange(savedDevice, null); + tbClusterService.pushMsgToCore(new DeviceNameOrTypeUpdateMsg(savedDevice.getTenantId(), + savedDevice.getId(), savedDevice.getName(), savedDevice.getType()), null); + tbClusterService.onEntityStateChange(savedDevice.getTenantId(), savedDevice.getId(), + device.getId() == null ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); + + logEntityAction(savedDevice.getId(), savedDevice, + savedDevice.getCustomerId(), + device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null); + + if (device.getId() == null) { + deviceStateService.onDeviceAdded(savedDevice); + } else { + deviceStateService.onDeviceUpdated(savedDevice); + } + return savedDevice; + } catch (Exception e) { + logEntityAction(emptyId(EntityType.DEVICE), device, + null, device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e); + throw handleException(e); + } + } } diff --git a/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MModelsRepository.java b/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MModelsRepository.java index 5952943d77..81dd7d7e66 100644 --- a/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MModelsRepository.java +++ b/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MModelsRepository.java @@ -23,8 +23,11 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.data.domain.PageImpl; import org.springframework.stereotype.Service; -import org.thingsboard.server.common.data.lwm2m.*; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.lwm2m.LwM2mInstance; +import org.thingsboard.server.common.data.lwm2m.LwM2mObject; +import org.thingsboard.server.common.data.lwm2m.LwM2mResource; +import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.transport.lwm2m.LwM2MTransportConfigBootstrap; @@ -33,17 +36,24 @@ import org.thingsboard.server.dao.service.Validator; import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; import java.math.BigInteger; -import java.security.*; +import java.security.AlgorithmParameters; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.KeyStoreException; +import java.security.PublicKey; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.security.spec.ECGenParameterSpec; import java.security.spec.ECParameterSpec; -import java.security.spec.ECPublicKeySpec; import java.security.spec.ECPoint; +import java.security.spec.ECPublicKeySpec; import java.security.spec.KeySpec; -import java.util.List; import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Predicate; +import java.util.stream.Collector; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -70,22 +80,32 @@ public class LwM2MModelsRepository { * Filter by Predicate (uses objectIds, if objectIds is null then it uses textSearch, * if textSearch is null then it uses AllList from List) */ - public List getLwm2mObjects(int[] objectIds, String textSearch) { - return getLwm2mObjects((objectIds != null && objectIds.length > 0) ? - (ObjectModel element) -> IntStream.of(objectIds).anyMatch(x -> x == element.id) : - (textSearch != null && !textSearch.isEmpty()) ? (ObjectModel element) -> element.name.contains(textSearch) : null); + public List getLwm2mObjects(int[] objectIds, String textSearch, String sortProperty, String sortOrder) { + if (objectIds == null && textSearch != null && !textSearch.isEmpty()) { + objectIds = getObjectIdFromTextSearch(textSearch); + } + int[] finalObjectIds = objectIds; + return getLwm2mObjects((objectIds != null && objectIds.length > 0 && textSearch != null && !textSearch.isEmpty()) ? + (ObjectModel element) -> IntStream.of(finalObjectIds).anyMatch(x -> x == element.id) || element.name.toLowerCase().contains(textSearch.toLowerCase()) : + (objectIds != null && objectIds.length > 0) ? + (ObjectModel element) -> IntStream.of(finalObjectIds).anyMatch(x -> x == element.id) : + (textSearch != null && !textSearch.isEmpty()) ? + (ObjectModel element) -> element.name.contains(textSearch) : + null, + sortProperty, sortOrder); } /** * @param predicate * @return list of LwM2mObject */ - private List getLwm2mObjects(Predicate predicate) { + private List getLwm2mObjects(Predicate predicate, String sortProperty, String sortOrder) { List lwM2mObjects = new ArrayList<>(); List listObjects = (predicate == null) ? this.contextServer.getModelsValue() : contextServer.getModelsValue().stream() .filter(predicate) .collect(Collectors.toList()); + listObjects.forEach(obj -> { LwM2mObject lwM2mObject = new LwM2mObject(); lwM2mObject.setId(obj.id); @@ -105,6 +125,29 @@ public class LwM2MModelsRepository { lwM2mObject.setInstances(new LwM2mInstance[]{instance}); lwM2mObjects.add(lwM2mObject); }); + return lwM2mObjects.size() > 1 ? this.sortList (lwM2mObjects, sortProperty, sortOrder) : lwM2mObjects; + } + + private List sortList (List lwM2mObjects, String sortProperty, String sortOrder) { + switch (sortProperty) { + case "name": + switch (sortOrder) { + case "ASC": + lwM2mObjects.sort((o1, o2) -> o1.getName().compareTo(o2.getName())); + break; + case "DESC": + lwM2mObjects.stream().sorted(Comparator.comparing(LwM2mObject::getName).reversed()); + break; + } + case "id": + switch (sortOrder) { + case "ASC": + lwM2mObjects.sort((o1, o2) -> Long.compare(o1.getId(), o2.getId())); + break; + case "DESC": + lwM2mObjects.sort((o1, o2) -> Long.compare(o2.getId(), o1.getId())); + } + } return lwM2mObjects; } @@ -126,13 +169,32 @@ public class LwM2MModelsRepository { * PageNumber = 1, PageSize = List.size() */ public PageData findLwm2mListObjects(PageLink pageLink) { - PageImpl page = new PageImpl(getLwm2mObjects(null, pageLink.getTextSearch())); + PageImpl page = new PageImpl(getLwm2mObjects(getObjectIdFromTextSearch(pageLink.getTextSearch()), + pageLink.getTextSearch(), + pageLink.getSortOrder().getProperty(), + pageLink.getSortOrder().getDirection().name())); PageData pageData = new PageData(page.getContent(), page.getTotalPages(), page.getTotalElements(), page.hasNext()); return pageData; } /** - * + * Filter for id Object + * @param textSearch - + * @return - return Object id only first chartAt in textSearch + */ + private int[] getObjectIdFromTextSearch(String textSearch) { + String filtered = null; + if (textSearch !=null && !textSearch.isEmpty()) { + AtomicInteger a = new AtomicInteger(); + filtered = textSearch.chars () + .mapToObj(chr -> (char) chr) + .filter(i -> Character.isDigit(i) && textSearch.charAt(a.getAndIncrement()) == i) + .collect(Collector.of(StringBuilder::new, StringBuilder::append, StringBuilder::append, StringBuilder::toString)); + } + return (filtered != null && !filtered.isEmpty()) ? new int[]{Integer.parseInt(filtered)} : new int[0]; + } + + /** * @param securityMode * @param bootstrapServerIs * @return ServerSecurityConfig more value is default: Important - port, host, publicKey @@ -143,60 +205,60 @@ public class LwM2MModelsRepository { } /** - * * @param bootstrapServerIs * @param mode * @return ServerSecurityConfig more value is default: Important - port, host, publicKey */ private ServerSecurityConfig getBootstrapServer(boolean bootstrapServerIs, LwM2MSecurityMode mode) { ServerSecurityConfig bsServ = new ServerSecurityConfig(); + bsServ.setBootstrapServerIs(bootstrapServerIs); if (bootstrapServerIs) { + bsServ.setServerId(contextBootStrap.getBootstrapServerId()); switch (mode) { case NO_SEC: bsServ.setHost(contextBootStrap.getBootstrapHost()); - bsServ.setPort(contextBootStrap.getBootstrapPort()); + bsServ.setPort(contextBootStrap.getBootstrapPortNoSecPsk()); bsServ.setServerPublicKey(""); break; case PSK: bsServ.setHost(contextBootStrap.getBootstrapSecureHost()); - bsServ.setPort(contextBootStrap.getBootstrapSecurePort()); + bsServ.setPort(contextBootStrap.getBootstrapSecurePortPsk()); bsServ.setServerPublicKey(""); break; case RPK: bsServ.setHost(contextBootStrap.getBootstrapSecureHost()); - bsServ.setPort(contextBootStrap.getBootstrapSecurePort()); + bsServ.setPort(contextBootStrap.getBootstrapSecurePortRpk()); bsServ.setServerPublicKey(getRPKPublicKey(this.contextBootStrap.getBootstrapPublicX(), this.contextBootStrap.getBootstrapPublicY())); break; case X509: bsServ.setHost(contextBootStrap.getBootstrapSecureHost()); - bsServ.setPort(contextBootStrap.getBootstrapSecurePortCert()); + bsServ.setPort(contextBootStrap.getBootstrapSecurePortX509()); bsServ.setServerPublicKey(getServerPublicKeyX509(contextBootStrap.getBootstrapAlias())); break; default: break; } } else { - bsServ.setBootstrapServerIs(bootstrapServerIs); - bsServ.setServerId(123); + bsServ.setServerId(contextServer.getServerId()); switch (mode) { case NO_SEC: bsServ.setHost(contextServer.getServerHost()); - bsServ.setPort(contextServer.getServerPort()); + bsServ.setPort(contextServer.getServerPortNoSecPsk()); bsServ.setServerPublicKey(""); break; case PSK: bsServ.setHost(contextServer.getServerSecureHost()); - bsServ.setPort(contextServer.getServerSecurePort()); + bsServ.setPort(contextServer.getServerPortPsk()); bsServ.setServerPublicKey(""); break; case RPK: bsServ.setHost(contextServer.getServerSecureHost()); - bsServ.setPort(contextServer.getServerSecurePort()); + bsServ.setPort(contextServer.getServerPortRpk()); bsServ.setServerPublicKey(getRPKPublicKey(this.contextServer.getServerPublicX(), this.contextServer.getServerPublicY())); break; case X509: bsServ.setHost(contextServer.getServerSecureHost()); - bsServ.setPort(contextServer.getServerSecurePortCert()); + bsServ.setPort(contextServer.getServerPortX509()); bsServ.setServerPublicKey(getServerPublicKeyX509(contextServer.getServerAlias())); break; default: @@ -207,25 +269,23 @@ public class LwM2MModelsRepository { } /** - * * @param alias * @return PublicKey format HexString or null */ - private String getServerPublicKeyX509 (String alias) { + private String getServerPublicKeyX509(String alias) { try { - X509Certificate serverCertificate = (X509Certificate) contextServer.getKeyStoreValue().getCertificate(alias); - return Hex.encodeHexString(serverCertificate.getEncoded()); + X509Certificate serverCertificate = (X509Certificate) contextServer.getKeyStoreValue().getCertificate(alias); + return Hex.encodeHexString(serverCertificate.getEncoded()); } catch (CertificateEncodingException | KeyStoreException e) { e.printStackTrace(); } - return null; + return null; } /** - * * @param publicServerX * @param publicServerY - * @return PublicKey format HexString or null + * @return PublicKey format HexString or null */ private String getRPKPublicKey(String publicServerX, String publicServerY) { try { @@ -241,9 +301,9 @@ public class LwM2MModelsRepository { KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)), parameterSpec); /** Get keys */ - PublicKey publicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec); - if (publicKey != null && publicKey.getEncoded().length > 0 ) { - return Hex.encodeHexString(publicKey.getEncoded()); + PublicKey publicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec); + if (publicKey != null && publicKey.getEncoded().length > 0) { + return Hex.encodeHexString(publicKey.getEncoded()); } } } catch (GeneralSecurityException | IllegalArgumentException e) { diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index 32cf287720..a56e98fe5f 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -49,34 +49,31 @@ import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceProvisionService; import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.device.provision.ProvisionFailedException; import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.device.provision.ProvisionResponse; import org.thingsboard.server.dao.relation.RelationService; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.gen.transport.TransportProtos.DeviceCredentialsProto; import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; -import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; -import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsgOrBuilder; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionResponseStatus; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.dao.device.provision.ProvisionFailedException; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; -import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.state.DeviceStateService; @@ -326,6 +323,7 @@ public class DefaultTransportApiService implements TransportApiService { break; case MQTT_BASIC: case X509_CERTIFICATE: + case LWM2M_CREDENTIALS: provisionResponse.setCredentialsValue(deviceCredentials.getCredentialsValue()); break; } diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 210465f528..982ed87e94 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -584,7 +584,13 @@ transport: timeout: "${LWM2M_TIMEOUT:120000}" # model_path_file: "${LWM2M_MODEL_PATH_FILE:./common/transport/lwm2m/src/main/resources/models/}" model_path_file: "${LWM2M_MODEL_PATH_FILE:}" - support_deprecated_ciphers_enable: "${LWM2M_SUPPORT_DEPRECATED_CIPHERS_ENABLED:true}" + recommended_ciphers: "${LWM2M_RECOMMENDED_CIPHERS:false}" + recommended_supported_groups: "${LWM2M_RECOMMENDED_SUPPORTED_GROUPS:false}" + request_pool_size: "${LWM2M_REQUEST_POOL_SIZE:100}" + request_error_pool_size: "${LWM2M_REQUEST_ERROR_POOL_SIZE:10}" + registered_pool_size: "${LWM2M_REGISTERED_POOL_SIZE:10}" + update_registered_pool_size: "${LWM2M_UPDATE_REGISTERED_POOL_SIZE:10}" + un_registered_pool_size: "${LWM2M_UN_REGISTERED_POOL_SIZE:10}" secure: # Only Certificate_x509: # To get helps about files format and how to generate it, see: https://github.com/eclipse/leshan/wiki/Credential-files-format @@ -597,24 +603,19 @@ transport: root_alias: "${LWM2M_SERVER_ROOT_CA:rootca}" enable_gen_psk_rpk: "${ENABLE_GEN_PSK_RPK:true}" server: + id: "${LWM2M_SERVER_ID:123}" bind_address: "${LWM2M_BIND_ADDRESS:0.0.0.0}" - bind_port: "${LWM2M_BIND_PORT:5685}" - bind_port_cert: "${LWM2M_BIND_PORT_CERT:5687}" + bind_port_no_sec_psk: "${LWM2M_BIND_PORT_NO_SEC_PSK:5685}" + bind_port_no_sec_rpk: "${LWM2M_BIND_PORT_NO_SEC_RPK:5687}" + bind_port_no_sec_x509: "${LWM2M_BIND_PORT_NO_SEC_X509:5689}" secure: - start_all: "${START_SERVER_ALL:true}" - #leshan.core (V1_1) - #DTLS security modes: - #0: Pre-Shared Key mode - #1: Raw Public Key mode - #2: Certificate mode X509 - #3: NoSec mode * - #OMA-TS-LightweightM2M_Core-V1_1_1-20190617-A (add) - #4: Certificate mode X509 with EST - # If only startAll == false - dtls_mode: "${LWM2M_SECURITY_MODE:1}" bind_address: "${LWM2M_BIND_ADDRESS:0.0.0.0}" - bind_port: "${LWM2M_BIND_PORT_SEC:5686}" - bind_port_cert: "${LWM2M_BIND_PORT_SEC_CERT:5688}" + start_psk: "${START_SERVER_PSK:true}" + start_rpk: "${START_SERVER_RPK:true}" + start_x509: "${START_SERVER_X509:true}" + bind_port_psk: "${LWM2M_BIND_PORT_SEC_PSK:5686}" + bind_port_rpk: "${LWM2M_BIND_PORT_SEC_RPK:5688}" + bind_port_x509: "${LWM2M_BIND_PORT_SEC_X509:5690}" # Only RPK: Public & Private Key # create_rpk: "${CREATE_RPK:}" public_x: "${LWM2M_SERVER_PUBLIC_X:405354ea8893471d9296afbc8b020a5c6201b0bb25812a53b849d4480fa5f069}" @@ -624,23 +625,26 @@ transport: alias: "${LWM2M_KEYSTORE_ALIAS_SERVER:server}" bootstrap: enable: "${BOOTSTRAP:true}" + id: "${LWM2M_SERVER_ID:111}" bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}" - bind_port: "${LWM2M_BIND_PORT_BS:5689}" - bind_port_cert: "${LWM2M_BIND_PORT_SER_BS:5691}" + bind_port_no_sec_psk: "${LWM2M_BIND_PORT_NO_SEC_BS:5691}" + bind_port_no_sec_rpk: "${LWM2M_BIND_PORT_NO_SEC_BS:5693}" + bind_port_no_sec_x509: "${LWM2M_BIND_PORT_NO_SEC_BS:5695}" secure: - start_all: "${START_BOOTSTRAP_ALL:true}" - # If only startAll == false - dtls_mode: "${LWM2M_SECURITY_MODE_BS:1}" bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}" - bind_port: "${LWM2M_BIND_PORT_SEC_BS:5690}" - bind_port_cert: "${LWM2M_BIND_PORT_SEC_CERT_BS:5692}" + start_psk: "${START_SERVER_PSK_BS:true}" + start_rpk: "${START_SERVER_RPK_BS:true}" + start_x509: "${START_SERVER_X509_BS:true}" + bind_port_psk: "${LWM2M_BIND_PORT_SEC_PSK_BS:5692}" + bind_port_rpk: "${LWM2M_BIND_PORT_SER_RPK_BS:5694}" + bind_port_x509: "${LWM2M_BIND_PORT_SEC_X509_BS:5696}" # Only RPK: Public & Private Key public_x: "${LWM2M_SERVER_PUBLIC_X_BS:993ef2b698c6a9c0c1d8be78b13a9383c0854c7c7c7a504d289b403794648183}" public_y: "${LWM2M_SERVER_PUBLIC_Y_BS:267412d5fc4e5ceb2257cb7fd7f76ebdac2fa9aa100afb162e990074cc0bfaa2}" private_s: "${LWM2M_SERVER_PRIVATE_S_BS:9dbdbb073fc63570693a9aaf1013414e261c571f27e27fc6a8c1c2ad9347875a}" # Only Certificate_x509: alias: "${LWM2M_KEYSTORE_ALIAS_BOOTSTRAP:bootstrap}" - # Redis + # Redis redis_url: "${LWM2M_REDIS_URL:''}" # Edges parameters diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java index d2f6454a53..06a2fe4e7d 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java @@ -23,12 +23,12 @@ import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.device.DeviceSearchQuery; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.common.data.page.TimePageLink; +import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.dao.device.provision.ProvisionRequest; import java.util.List; @@ -47,6 +47,8 @@ public interface DeviceService { Device saveDeviceWithAccessToken(Device device, String accessToken); + Device saveDeviceWithCredentials(Device device, DeviceCredentials deviceCredentials); + Device assignDeviceToCustomer(TenantId tenantId, DeviceId deviceId, CustomerId customerId); Device unassignDeviceFromCustomer(TenantId tenantId, DeviceId deviceId); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Device.java b/common/data/src/main/java/org/thingsboard/server/common/data/Device.java index aef34d6306..ad93983cac 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/Device.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/Device.java @@ -20,7 +20,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.device.data.DeviceData; -import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; @@ -64,6 +63,17 @@ public class Device extends SearchTextBasedWithAdditionalInfo implemen this.setDeviceData(device.getDeviceData()); } + public Device updateDevice(Device device) { + this.tenantId = device.getTenantId(); + this.customerId = device.getCustomerId(); + this.name = device.getName(); + this.type = device.getType(); + this.label = device.getLabel(); + this.deviceProfileId = device.getDeviceProfileId(); + this.setDeviceData(device.getDeviceData()); + return this; + } + public TenantId getTenantId() { return tenantId; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentialsType.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentialsType.java index 82a4b2d179..9df283f425 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentialsType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentialsType.java @@ -21,6 +21,4 @@ public enum DeviceCredentialsType { X509_CERTIFICATE, MQTT_BASIC, LWM2M_CREDENTIALS - - } diff --git a/common/queue/src/main/proto/queue.proto b/common/queue/src/main/proto/queue.proto index b895bffa5f..b9fd492ccf 100644 --- a/common/queue/src/main/proto/queue.proto +++ b/common/queue/src/main/proto/queue.proto @@ -77,6 +77,7 @@ enum CredentialsType { ACCESS_TOKEN = 0; X509_CERTIFICATE = 1; MQTT_BASIC = 2; + LWM2M_CREDENTIALS = 3; } message KeyValueProto { diff --git a/common/transport/lwm2m/pom.xml b/common/transport/lwm2m/pom.xml index 59dd2f6b08..00d559c9d1 100644 --- a/common/transport/lwm2m/pom.xml +++ b/common/transport/lwm2m/pom.xml @@ -82,15 +82,10 @@ ch.qos.logback logback-classic - org.eclipse.leshan leshan-server-cf - - - - org.eclipse.leshan leshan-client-cf @@ -100,12 +95,6 @@ org.eclipse.leshan leshan-server-redis - - - - - - org.springframework.boot spring-boot-starter-test @@ -124,14 +113,20 @@ org.eclipse.californium californium-core - ${californium.version} - test-jar + test-jar test + + org.eclipse.californium + californium-core + + + + + org.eclipse.californium element-connector - ${californium.version} test-jar test diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java index e2bf4e296b..ed7d1d9da1 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java @@ -18,6 +18,7 @@ package org.thingsboard.server.transport.lwm2m.bootstrap; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.scandium.config.DtlsConnectorConfig; import org.eclipse.leshan.core.model.StaticModel; +import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.server.bootstrap.BootstrapSessionManager; import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServer; import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServerBuilder; @@ -28,18 +29,42 @@ import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapSecurityStore; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MInMemoryBootstrapConfigStore; -import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MSetSecurityStoreBootstrap; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2mDefaultBootstrapSessionManager; import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; import org.thingsboard.server.transport.lwm2m.server.LwM2MTransportContextServer; -import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.X509; + +import java.math.BigInteger; +import java.security.AlgorithmParameters; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.security.interfaces.ECPublicKey; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPrivateKeySpec; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.KeySpec; +import java.util.Arrays; + +import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA256; +import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.NO_SEC; +import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.PSK; import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.RPK; +import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.X509; import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.getCoapConfig; @Slf4j @Component -@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true'&& '${transport.lwm2m.bootstrap.enable:false}'=='true') || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true'&& '${transport.lwm2m.bootstrap.enable}'=='true')") +@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true'&& '${transport.lwm2m.bootstrap.enable:false}'=='true') || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled:false}'=='true'&& '${transport.lwm2m.bootstrap.enable:false}'=='true')") public class LwM2MTransportBootstrapServerConfiguration { + private PublicKey publicKey; + private PrivateKey privateKey; @Autowired private LwM2MTransportContextBootstrap contextBs; @@ -53,27 +78,35 @@ public class LwM2MTransportBootstrapServerConfiguration { @Autowired private LwM2MInMemoryBootstrapConfigStore lwM2MInMemoryBootstrapConfigStore; - @Primary - @Bean(name = "leshanBootstrapCert") - public LeshanBootstrapServer getLeshanBootstrapServerCert() { - log.info("Prepare and start BootstrapServerCert... PostConstruct"); - return getLeshanBootstrapServer(this.contextBs.getCtxBootStrap().getBootstrapPortCert(), this.contextBs.getCtxBootStrap().getBootstrapSecurePortCert(), X509); + @Bean(name = "leshanBootstrapX509") + @ConditionalOnExpression("('${transport.lwm2m.bootstrap.secure.start_x509:false}'=='true')") + public LeshanBootstrapServer getLeshanBootstrapServerX509() { + log.info("Prepare and start BootstrapServerX509... PostConstruct"); + return getLeshanBootstrapServer(this.contextBs.getCtxBootStrap().getBootstrapPortNoSecX509(), this.contextBs.getCtxBootStrap().getBootstrapSecurePortX509(), X509); + } + + @Bean(name = "leshanBootstrapPsk") + @ConditionalOnExpression("('${transport.lwm2m.bootstrap.secure.start_psk:false}'=='true')") + public LeshanBootstrapServer getLeshanBootstrapServerPsk() { + log.info("Prepare and start BootstrapServerRsk... PostConstruct"); + return getLeshanBootstrapServer(this.contextBs.getCtxBootStrap().getBootstrapPortNoSecPsk(), this.contextBs.getCtxBootStrap().getBootstrapSecurePortPsk(), PSK); } - @Bean(name = "leshanBootstrapRPK") - public LeshanBootstrapServer getLeshanBootstrapServerRPK() { - log.info("Prepare and start BootstrapServerRPK... PostConstruct"); - return getLeshanBootstrapServer(this.contextBs.getCtxBootStrap().getBootstrapPort(), this.contextBs.getCtxBootStrap().getBootstrapSecurePort(), RPK); + @Bean(name = "leshanBootstrapRpk") + @ConditionalOnExpression("('${transport.lwm2m.bootstrap.secure.start_rpk:false}'=='true')") + public LeshanBootstrapServer getLeshanBootstrapServerRpk() { + log.info("Prepare and start BootstrapServerRpk... PostConstruct"); + return getLeshanBootstrapServer(this.contextBs.getCtxBootStrap().getBootstrapPortNoSecRpk(), this.contextBs.getCtxBootStrap().getBootstrapSecurePortRpk(), RPK); } - public LeshanBootstrapServer getLeshanBootstrapServer(Integer bootstrapPort, Integer bootstrapSecurePort, LwM2MSecurityMode dtlsMode) { + public LeshanBootstrapServer getLeshanBootstrapServer(Integer bootstrapPortNoSec, Integer bootstrapSecurePort, LwM2MSecurityMode dtlsMode) { LeshanBootstrapServerBuilder builder = new LeshanBootstrapServerBuilder(); - builder.setLocalAddress(this.contextBs.getCtxBootStrap().getBootstrapHost(), bootstrapPort); + builder.setLocalAddress(this.contextBs.getCtxBootStrap().getBootstrapHost(), bootstrapPortNoSec); builder.setLocalSecureAddress(this.contextBs.getCtxBootStrap().getBootstrapSecureHost(), bootstrapSecurePort); /** Create CoAP Config */ - builder.setCoapConfig(getCoapConfig ()); + builder.setCoapConfig(getCoapConfig (bootstrapPortNoSec, bootstrapSecurePort)); /** ConfigStore */ builder.setConfigStore(lwM2MInMemoryBootstrapConfigStore); @@ -84,13 +117,22 @@ public class LwM2MTransportBootstrapServerConfiguration { /** Define model provider (Create Models )*/ builder.setModel(new StaticModel(contextS.getCtxServer().getModelsValue())); + /** Create credentials */ + LwM2MSetSecurityStoreBootstrap(builder, dtlsMode); + /** Create and Set DTLS Config */ DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(); - dtlsConfig.setRecommendedCipherSuitesOnly(contextS.getCtxServer().isSupportDeprecatedCiphersEnable()); + if (dtlsMode==PSK) { + dtlsConfig.setRecommendedCipherSuitesOnly(this.contextS.getCtxServer().isRecommendedCiphers()); + dtlsConfig.setRecommendedSupportedGroupsOnly(this.contextS.getCtxServer().isRecommendedSupportedGroups()); + dtlsConfig.setSupportedCipherSuites(TLS_PSK_WITH_AES_128_CBC_SHA256); + } + else { + dtlsConfig.setRecommendedCipherSuitesOnly(this.contextS.getCtxServer().isRecommendedCiphers()); +// dtlsConfig.setRecommendedSupportedGroupsOnly(false); + } builder.setDtlsConfig(dtlsConfig); - /** Create credentials */ - new LwM2MSetSecurityStoreBootstrap(builder, contextBs, contextS, dtlsMode); BootstrapSessionManager sessionManager = new LwM2mDefaultBootstrapSessionManager(lwM2MBootstrapSecurityStore); builder.setSessionManager(sessionManager); @@ -98,4 +140,151 @@ public class LwM2MTransportBootstrapServerConfiguration { /** Create BootstrapServer */ return builder.build(); } + + public void LwM2MSetSecurityStoreBootstrap(LeshanBootstrapServerBuilder builder, LwM2MSecurityMode dtlsMode) { + + /** Set securityStore with new registrationStore */ + + switch (dtlsMode) { + /** Use No_Sec only */ + case NO_SEC: + setServerWithX509Cert(builder, NO_SEC.code); + break; + /** Use PSK/RPK */ + case PSK: + break; + case RPK: + setRPK(builder); + break; + case X509: + setServerWithX509Cert(builder, X509.code); + break; + /** Use X509_EST only */ + case X509_EST: + // TODO support sentinel pool and make pool configurable + break; + /** Use ather X509, PSK, No_Sec ?? */ + default: + break; + } + } + + private void setRPK(LeshanBootstrapServerBuilder builder) { + try { + /** Get Elliptic Curve Parameter spec for secp256r1 */ + AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC"); + algoParameters.init(new ECGenParameterSpec("secp256r1")); + ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class); + if (this.contextBs.getCtxBootStrap().getBootstrapPublicX() != null && !this.contextBs.getCtxBootStrap().getBootstrapPublicX().isEmpty() && this.contextBs.getCtxBootStrap().getBootstrapPublicY() != null && !this.contextBs.getCtxBootStrap().getBootstrapPublicY().isEmpty()) { + /** Get point values */ + byte[] publicX = Hex.decodeHex(this.contextBs.getCtxBootStrap().getBootstrapPublicX().toCharArray()); + byte[] publicY = Hex.decodeHex(this.contextBs.getCtxBootStrap().getBootstrapPublicY().toCharArray()); + /** Create key specs */ + KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)), + parameterSpec); + /** Get keys */ + this.publicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec); + } + if (this.contextBs.getCtxBootStrap().getBootstrapPrivateS() != null && !this.contextBs.getCtxBootStrap().getBootstrapPrivateS().isEmpty()) { + /** Get point values */ + byte[] privateS = Hex.decodeHex(this.contextBs.getCtxBootStrap().getBootstrapPrivateS().toCharArray()); + /** Create key specs */ + KeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(privateS), parameterSpec); + /** Get keys */ + this.privateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec); + } + if (this.publicKey != null && this.publicKey.getEncoded().length > 0 && + this.privateKey != null && this.privateKey.getEncoded().length > 0) { + builder.setPublicKey(this.publicKey); + builder.setPrivateKey(this.privateKey); + this.contextBs.getCtxBootStrap().setBootstrapPublicKey(this.publicKey); + getParamsRPK(); + } + } catch (GeneralSecurityException | IllegalArgumentException e) { + log.error("[{}] Failed generate Server PSK/RPK", e.getMessage()); + throw new RuntimeException(e); + } + } + + private void setServerWithX509Cert(LeshanBootstrapServerBuilder builder, int securityModeCode) { + try { + if (this.contextS.getCtxServer().getKeyStoreValue() != null) { + KeyStore keyStoreServer = this.contextS.getCtxServer().getKeyStoreValue(); + setBuilderX509(builder); + X509Certificate rootCAX509Cert = (X509Certificate) keyStoreServer.getCertificate(this.contextS.getCtxServer().getRootAlias()); + if (rootCAX509Cert != null && securityModeCode == X509.code) { + X509Certificate[] trustedCertificates = new X509Certificate[1]; + trustedCertificates[0] = rootCAX509Cert; + builder.setTrustedCertificates(trustedCertificates); + } else { + /** by default trust all */ + builder.setTrustedCertificates(new X509Certificate[0]); + } + } + else { + /** by default trust all */ + builder.setTrustedCertificates(new X509Certificate[0]); + log.error("Unable to load X509 files for BootStrapServer"); + } + } catch (KeyStoreException ex) { + log.error("[{}] Unable to load X509 files server", ex.getMessage()); + } + + } + + private void setBuilderX509(LeshanBootstrapServerBuilder builder) { + /** + * For deb => KeyStorePathFile == yml or commandline: KEY_STORE_PATH_FILE + * For idea => KeyStorePathResource == common/transport/lwm2m/src/main/resources/credentials: in LwM2MTransportContextServer: credentials/serverKeyStore.jks + */ + try { + X509Certificate serverCertificate = (X509Certificate) this.contextS.getCtxServer().getKeyStoreValue().getCertificate(this.contextBs.getCtxBootStrap().getBootstrapAlias()); + this.privateKey = (PrivateKey) this.contextS.getCtxServer().getKeyStoreValue().getKey(this.contextBs.getCtxBootStrap().getBootstrapAlias(), this.contextS.getCtxServer().getKeyStorePasswordServer() == null ? null : this.contextS.getCtxServer().getKeyStorePasswordServer().toCharArray()); + if (this.privateKey != null && this.privateKey.getEncoded().length > 0) { + builder.setPrivateKey(this.privateKey); + } + if (serverCertificate != null) { + builder.setCertificateChain(new X509Certificate[]{serverCertificate}); + this.infoParamsX509(serverCertificate); + } + } catch (Exception ex) { + log.error("[{}] Unable to load KeyStore files server", ex.getMessage()); + } + } + + private void getParamsRPK() { + if (this.publicKey instanceof ECPublicKey) { + /** Get x coordinate */ + byte[] x = ((ECPublicKey) this.publicKey).getW().getAffineX().toByteArray(); + if (x[0] == 0) + x = Arrays.copyOfRange(x, 1, x.length); + + /** Get Y coordinate */ + byte[] y = ((ECPublicKey) this.publicKey).getW().getAffineY().toByteArray(); + if (y[0] == 0) + y = Arrays.copyOfRange(y, 1, y.length); + + /** Get Curves params */ + String params = ((ECPublicKey) this.publicKey).getParams().toString(); + log.info( + " \nBootstrap uses RPK : \n Elliptic Curve parameters : [{}] \n Public x coord : [{}] \n Public y coord : [{}] \n Public Key (Hex): [{}] \n Private Key (Hex): [{}]", + params, Hex.encodeHexString(x), Hex.encodeHexString(y), + Hex.encodeHexString(this.publicKey.getEncoded()), + Hex.encodeHexString(this.privateKey.getEncoded())); + } else { + throw new IllegalStateException("Unsupported Public Key Format (only ECPublicKey supported)."); + } + } + + private void infoParamsX509(X509Certificate certificate) { + try { + log.info("BootStrap uses X509 : \n X509 Certificate (Hex): [{}] \n Private Key (Hex): [{}]", + Hex.encodeHexString(certificate.getEncoded()), + Hex.encodeHexString(this.privateKey.getEncoded())); + } catch (CertificateEncodingException e) { + log.error("", e); + } + } + + } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerInitializer.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerInitializer.java index 07d79d3955..6ca751b138 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerInitializer.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerInitializer.java @@ -14,57 +14,56 @@ * limitations under the License. */ package org.thingsboard.server.transport.lwm2m.bootstrap; + import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; -import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @Slf4j @Service -@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled}'=='true'&& '${transport.lwm2m.bootstrap.enable}'=='true') || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true'&& '${transport.lwm2m.bootstrap.enable}'=='true')") +@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true'&& '${transport.lwm2m.bootstrap.enable:false}'=='true') || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled:false}'=='true'&& '${transport.lwm2m.bootstrap.enable:false}'=='true')") public class LwM2MTransportBootstrapServerInitializer { - @Autowired - @Qualifier("leshanBootstrapCert") + @Autowired(required = false) + @Qualifier("leshanBootstrapX509") private LeshanBootstrapServer lhBServerCert; - @Autowired - @Qualifier("leshanBootstrapRPK") - private LeshanBootstrapServer lhBServerRPK; + @Autowired(required = false) + @Qualifier("leshanBootstrapPsk") + private LeshanBootstrapServer lhBServerPsk; + + @Autowired(required = false) + @Qualifier("leshanBootstrapRpk") + private LeshanBootstrapServer lhBServerRpk; @Autowired private LwM2MTransportContextBootstrap contextBS; @PostConstruct public void init() { - if (this.contextBS.getCtxBootStrap().isBootstrapStartAll()) { - this.lhBServerCert.start(); - this.lhBServerRPK.start(); + if (this.contextBS.getCtxBootStrap().getBootstrapStartPsk()) { + this.lhBServerPsk.start(); + } + if (this.contextBS.getCtxBootStrap().getBootstrapStartRpk()) { + this.lhBServerRpk.start(); } - else { - if (this.contextBS.getCtxBootStrap().getBootStrapDtlsMode() == LwM2MSecurityMode.X509.code) { - this.lhBServerCert.start(); - } - else { - this.lhBServerRPK.start(); - } + if (this.contextBS.getCtxBootStrap().getBootstrapStartX509()) { + this.lhBServerCert.start(); } } @PreDestroy public void shutdown() throws InterruptedException { log.info("Stopping LwM2M transport Bootstrap Server!"); - try { - lhBServerCert.destroy(); - lhBServerRPK.destroy(); - } finally { - } + lhBServerPsk.destroy(); + lhBServerRpk.destroy(); + lhBServerCert.destroy(); log.info("LwM2M transport Bootstrap Server stopped!"); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java index 630cdfecde..e68cddc3be 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java @@ -29,28 +29,41 @@ import org.eclipse.leshan.server.security.BootstrapSecurityStore; import org.eclipse.leshan.server.security.SecurityInfo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.stereotype.Component; -import org.thingsboard.server.transport.lwm2m.secure.LwM2MGetSecurityInfo; +import org.springframework.stereotype.Service; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; +import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator; import org.thingsboard.server.transport.lwm2m.secure.ReadResultSecurityStore; +import org.thingsboard.server.transport.lwm2m.server.LwM2MSessionMsgListener; +import org.thingsboard.server.transport.lwm2m.server.LwM2MTransportContextServer; +import org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler; import org.thingsboard.server.transport.lwm2m.utils.TypeServer; import java.io.IOException; import java.security.GeneralSecurityException; import java.util.Arrays; import java.util.List; +import java.util.UUID; -import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.*; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.BOOTSTRAP_SERVER; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.LOG_LW2M_ERROR; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.LOG_LW2M_INFO; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.LWM2M_SERVER; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.SERVERS; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.getBootstrapParametersFromThingsboard; @Slf4j -@Component("LwM2MBootstrapSecurityStore") +@Service("LwM2MBootstrapSecurityStore") @ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' && '${transport.lwm2m.bootstrap.enable:false}'=='true') || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true' && '${transport.lwm2m.bootstrap.enable}'=='true')") public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { private final EditableBootstrapConfigStore bootstrapConfigStore; @Autowired - LwM2MGetSecurityInfo lwM2MGetSecurityInfo; + LwM2mCredentialsSecurityInfoValidator lwM2MCredentialsSecurityInfoValidator; + + @Autowired + public LwM2MTransportContextServer context; public LwM2MBootstrapSecurityStore(EditableBootstrapConfigStore bootstrapConfigStore) { this.bootstrapConfigStore = bootstrapConfigStore; @@ -59,8 +72,8 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { @Override public List getAllByEndpoint(String endPoint) { String endPointKey = endPoint; - ReadResultSecurityStore store = lwM2MGetSecurityInfo.getSecurityInfo(endPointKey, TypeServer.BOOTSTRAP); - if (store.getBootstrapJsonCredential() != null) { + ReadResultSecurityStore store = lwM2MCredentialsSecurityInfoValidator.createAndValidateCredentialsSecurityInfo(endPointKey, TypeServer.BOOTSTRAP); + if (store.getBootstrapJsonCredential() != null && store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) { /** add value to store from BootstrapJson */ this.setBootstrapConfigScurityInfo(store); BootstrapConfig bsConfigNew = store.getBootstrapConfig(); @@ -73,7 +86,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { } bootstrapConfigStore.add(endPoint, bsConfigNew); } catch (InvalidConfigurationException e) { - e.printStackTrace(); + log.error("", e); } return store.getSecurityInfo() == null ? null : Arrays.asList(store.getSecurityInfo()); } @@ -83,17 +96,16 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { @Override public SecurityInfo getByIdentity(String identity) { - ReadResultSecurityStore store = lwM2MGetSecurityInfo.getSecurityInfo(identity, TypeServer.BOOTSTRAP); - /** add value to store from BootstrapJson */ - this.setBootstrapConfigScurityInfo(store); - - if (store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) { + ReadResultSecurityStore store = lwM2MCredentialsSecurityInfoValidator.createAndValidateCredentialsSecurityInfo(identity, TypeServer.BOOTSTRAP); + if (store.getBootstrapJsonCredential() != null && store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) { + /** add value to store from BootstrapJson */ + this.setBootstrapConfigScurityInfo(store); BootstrapConfig bsConfig = store.getBootstrapConfig(); if (bsConfig.security != null) { try { bootstrapConfigStore.add(store.getEndPoint(), bsConfig); } catch (InvalidConfigurationException e) { - e.printStackTrace(); + log.error("", e); } return store.getSecurityInfo(); } @@ -141,28 +153,36 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { private LwM2MBootstrapConfig getParametersBootstrap(ReadResultSecurityStore store) { try { JsonObject bootstrapJsonCredential = store.getBootstrapJsonCredential(); - ObjectMapper mapper = new ObjectMapper(); - LwM2MBootstrapConfig lwM2MBootstrapConfig = mapper.readValue(bootstrapJsonCredential.toString(), LwM2MBootstrapConfig.class); - JsonObject bootstrapObject = getBootstrapParametersFromThingsboard(store.getDeviceProfile()); - lwM2MBootstrapConfig.servers = mapper.readValue(bootstrapObject.get(SERVERS).toString(), LwM2MBootstrapServers.class); - LwM2MServerBootstrap profileServerBootstrap = mapper.readValue(bootstrapObject.get(BOOTSTRAP_SERVER).toString(), LwM2MServerBootstrap.class); - LwM2MServerBootstrap profileLwm2mServer = mapper.readValue(bootstrapObject.get(LWM2M_SERVER).toString(), LwM2MServerBootstrap.class); - if (getValidatedSecurityMode(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap, lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer)) { - lwM2MBootstrapConfig.bootstrapServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap); - lwM2MBootstrapConfig.lwm2mServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer); - return lwM2MBootstrapConfig; - } - else { - log.error(" [{}] Different values SecurityMode between of client and profile.", store.getEndPoint()); - log.error(LOG_LW2M_ERROR + " getParametersBootstrap: [{}] Different values SecurityMode between of client and profile.", store.getEndPoint()); - String logMsg = String.format(LOG_LW2M_ERROR + " getParametersBootstrap: %s Different values SecurityMode between of client and profile.", store.getEndPoint()); -// sentLogsToThingsboard(logMsg, store.getEndPoint()); - return null; + if (bootstrapJsonCredential != null) { + ObjectMapper mapper = new ObjectMapper(); + LwM2MBootstrapConfig lwM2MBootstrapConfig = mapper.readValue(bootstrapJsonCredential.toString(), LwM2MBootstrapConfig.class); + JsonObject bootstrapObject = getBootstrapParametersFromThingsboard(store.getDeviceProfile()); + lwM2MBootstrapConfig.servers = mapper.readValue(bootstrapObject.get(SERVERS).toString(), LwM2MBootstrapServers.class); + LwM2MServerBootstrap profileServerBootstrap = mapper.readValue(bootstrapObject.get(BOOTSTRAP_SERVER).toString(), LwM2MServerBootstrap.class); + LwM2MServerBootstrap profileLwm2mServer = mapper.readValue(bootstrapObject.get(LWM2M_SERVER).toString(), LwM2MServerBootstrap.class); + UUID sessionUUiD = UUID.randomUUID(); + TransportProtos.SessionInfoProto sessionInfo = context.getValidateSessionInfo(store.getMsg(), sessionUUiD.getMostSignificantBits(), sessionUUiD.getLeastSignificantBits()); + context.getTransportService().registerAsyncSession(sessionInfo, new LwM2MSessionMsgListener(null, sessionInfo)); + if (this.getValidatedSecurityMode(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap, lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer)) { + lwM2MBootstrapConfig.bootstrapServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap); + lwM2MBootstrapConfig.lwm2mServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer); + String logMsg = String.format(LOG_LW2M_INFO + ": getParametersBootstrap: %s Access connect client with bootstrap server.", store.getEndPoint()); + context.sentParametersOnThingsboard(context.getTelemetryMsgObject(logMsg), LwM2MTransportHandler.DEVICE_TELEMETRY_TOPIC, sessionInfo); + return lwM2MBootstrapConfig; + } else { + log.error(" [{}] Different values SecurityMode between of client and profile.", store.getEndPoint()); + log.error(LOG_LW2M_ERROR + " getParametersBootstrap: [{}] Different values SecurityMode between of client and profile.", store.getEndPoint()); + String logMsg = String.format(LOG_LW2M_ERROR + ": getParametersBootstrap: %s Different values SecurityMode between of client and profile.", store.getEndPoint()); + context.sentParametersOnThingsboard(context.getTelemetryMsgObject(logMsg), LwM2MTransportHandler.DEVICE_TELEMETRY_TOPIC, sessionInfo); + return null; + } } } catch (JsonProcessingException e) { log.error("Unable to decode Json or Certificate for [{}] [{}]", store.getEndPoint(), e.getMessage()); return null; } + log.error("Unable to decode Json or Certificate for [{}]", store.getEndPoint()); + return null; } /** @@ -175,7 +195,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { * @return false if not sync between SecurityMode of Bootstrap credential and profile */ private boolean getValidatedSecurityMode(LwM2MServerBootstrap bootstrapFromCredential, LwM2MServerBootstrap profileServerBootstrap, LwM2MServerBootstrap lwm2mFromCredential, LwM2MServerBootstrap profileLwm2mServer) { - return (bootstrapFromCredential.getSecurityMode().equals(profileServerBootstrap.getSecurityMode()) && + return (bootstrapFromCredential.getSecurityMode().equals(profileServerBootstrap.getSecurityMode()) && lwm2mFromCredential.getSecurityMode().equals(profileLwm2mServer.getSecurityMode())); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MSetSecurityStoreBootstrap.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MSetSecurityStoreBootstrap.java deleted file mode 100644 index 65364076ef..0000000000 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MSetSecurityStoreBootstrap.java +++ /dev/null @@ -1,205 +0,0 @@ -/** - * Copyright © 2016-2021 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.transport.lwm2m.bootstrap.secure; - -import lombok.Data; -import lombok.extern.slf4j.Slf4j; -import org.eclipse.leshan.core.util.Hex; -import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServerBuilder; -import org.eclipse.leshan.server.security.EditableSecurityStore; -import org.thingsboard.server.transport.lwm2m.bootstrap.LwM2MTransportContextBootstrap; -import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; -import org.thingsboard.server.transport.lwm2m.server.LwM2MTransportContextServer; - -import java.math.BigInteger; -import java.security.AlgorithmParameters; -import java.security.KeyStore; -import java.security.PublicKey; -import java.security.PrivateKey; -import java.security.KeyFactory; -import java.security.GeneralSecurityException; -import java.security.KeyStoreException; -import java.security.cert.X509Certificate; -import java.security.interfaces.ECPublicKey; -import java.security.spec.ECGenParameterSpec; -import java.security.spec.ECParameterSpec; -import java.security.spec.ECPublicKeySpec; -import java.security.spec.ECPoint; -import java.security.spec.KeySpec; -import java.security.spec.ECPrivateKeySpec; -import java.util.Arrays; - -import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.NO_SEC; -import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.X509; - -@Slf4j -@Data -public class LwM2MSetSecurityStoreBootstrap { - - private KeyStore keyStore; - private PublicKey publicKey; - private PrivateKey privateKey; - private LwM2MTransportContextBootstrap contextBs; - private LwM2MTransportContextServer contextS; - private LeshanBootstrapServerBuilder builder; - EditableSecurityStore securityStore; - - public LwM2MSetSecurityStoreBootstrap(LeshanBootstrapServerBuilder builder, LwM2MTransportContextBootstrap contextBs, LwM2MTransportContextServer contextS, LwM2MSecurityMode dtlsMode) { - this.builder = builder; - this.contextBs = contextBs; - this.contextS = contextS; - /** Set securityStore with new registrationStore */ - - switch (dtlsMode) { - /** Use No_Sec only */ - case NO_SEC: - setServerWithX509Cert(NO_SEC.code); - break; - /** Use PSK/RPK */ - case PSK: - case RPK: - setRPK(); - break; - case X509: - setServerWithX509Cert(X509.code); - break; - /** Use X509_EST only */ - case X509_EST: - // TODO support sentinel pool and make pool configurable - break; - /** Use ather X509, PSK, No_Sec ?? */ - default: - break; - } - } - - private void setRPK() { - try { - /** Get Elliptic Curve Parameter spec for secp256r1 */ - AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC"); - algoParameters.init(new ECGenParameterSpec("secp256r1")); - ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class); - if (this.contextBs.getCtxBootStrap().getBootstrapPublicX() != null && !this.contextBs.getCtxBootStrap().getBootstrapPublicX().isEmpty() && this.contextBs.getCtxBootStrap().getBootstrapPublicY() != null && !this.contextBs.getCtxBootStrap().getBootstrapPublicY().isEmpty()) { - /** Get point values */ - byte[] publicX = Hex.decodeHex(this.contextBs.getCtxBootStrap().getBootstrapPublicX().toCharArray()); - byte[] publicY = Hex.decodeHex(this.contextBs.getCtxBootStrap().getBootstrapPublicY().toCharArray()); - /** Create key specs */ - KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)), - parameterSpec); - /** Get keys */ - this.publicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec); - } - if (this.contextBs.getCtxBootStrap().getBootstrapPrivateS() != null && !this.contextBs.getCtxBootStrap().getBootstrapPrivateS().isEmpty()) { - /** Get point values */ - byte[] privateS = Hex.decodeHex(this.contextBs.getCtxBootStrap().getBootstrapPrivateS().toCharArray()); - /** Create key specs */ - KeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(privateS), parameterSpec); - /** Get keys */ - this.privateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec); - } - if (this.publicKey != null && this.publicKey.getEncoded().length > 0 && - this.privateKey != null && this.privateKey.getEncoded().length > 0) { - this.builder.setPublicKey(this.publicKey); - this.builder.setPrivateKey(this.privateKey); - this.contextBs.getCtxBootStrap().setBootstrapPublicKey(this.publicKey); - getParamsRPK(); - } - } catch (GeneralSecurityException | IllegalArgumentException e) { - log.error("[{}] Failed generate Server PSK/RPK", e.getMessage()); - throw new RuntimeException(e); - } - } - - private void setServerWithX509Cert(int securityModeCode) { - try { - if (this.contextS.getCtxServer().getKeyStoreValue() != null) { - KeyStore keyStoreServer = this.contextS.getCtxServer().getKeyStoreValue(); - setBuilderX509(); - X509Certificate rootCAX509Cert = (X509Certificate) keyStoreServer.getCertificate(this.contextS.getCtxServer().getRootAlias()); - if (rootCAX509Cert != null && securityModeCode == X509.code) { - X509Certificate[] trustedCertificates = new X509Certificate[1]; - trustedCertificates[0] = rootCAX509Cert; - this.builder.setTrustedCertificates(trustedCertificates); - } else { - /** by default trust all */ - this.builder.setTrustedCertificates(new X509Certificate[0]); - } - } - else { - /** by default trust all */ - this.builder.setTrustedCertificates(new X509Certificate[0]); - log.error("Unable to load X509 files for BootStrapServer"); - } - } catch (KeyStoreException ex) { - log.error("[{}] Unable to load X509 files server", ex.getMessage()); - } - - } - - private void setBuilderX509() { - /** - * For deb => KeyStorePathFile == yml or commandline: KEY_STORE_PATH_FILE - * For idea => KeyStorePathResource == common/transport/lwm2m/src/main/resources/credentials: in LwM2MTransportContextServer: credentials/serverKeyStore.jks - */ - try { - X509Certificate serverCertificate = (X509Certificate) this.contextS.getCtxServer().getKeyStoreValue().getCertificate(this.contextBs.getCtxBootStrap().getBootstrapAlias()); - this.privateKey = (PrivateKey) this.contextS.getCtxServer().getKeyStoreValue().getKey(this.contextBs.getCtxBootStrap().getBootstrapAlias(), this.contextS.getCtxServer().getKeyStorePasswordServer() == null ? null : this.contextS.getCtxServer().getKeyStorePasswordServer().toCharArray()); - if (this.privateKey != null && this.privateKey.getEncoded().length > 0) { - this.builder.setPrivateKey(this.privateKey); - } - if (serverCertificate != null) { - this.builder.setCertificateChain(new X509Certificate[]{serverCertificate}); - this.contextBs.getCtxBootStrap().setBootstrapCertificate(serverCertificate); - } - } catch (Exception ex) { - log.error("[{}] Unable to load KeyStore files server", ex.getMessage()); - } - } - - private void getParamsRPK() { - if (this.publicKey instanceof ECPublicKey) { - /** Get x coordinate */ - byte[] x = ((ECPublicKey) this.publicKey).getW().getAffineX().toByteArray(); - if (x[0] == 0) - x = Arrays.copyOfRange(x, 1, x.length); - - /** Get Y coordinate */ - byte[] y = ((ECPublicKey) this.publicKey).getW().getAffineY().toByteArray(); - if (y[0] == 0) - y = Arrays.copyOfRange(y, 1, y.length); - - /** Get Curves params */ - String params = ((ECPublicKey) this.publicKey).getParams().toString(); - log.info( - " \nBootstrap uses RPK : \n Elliptic Curve parameters : [{}] \n Public x coord : [{}] \n Public y coord : [{}] \n Public Key (Hex): [{}] \n Private Key (Hex): [{}]", - params, Hex.encodeHexString(x), Hex.encodeHexString(y), - Hex.encodeHexString(this.publicKey.getEncoded()), - Hex.encodeHexString(this.privateKey.getEncoded())); - } else { - throw new IllegalStateException("Unsupported Public Key Format (only ECPublicKey supported)."); - } - } - -// private void getParamsX509() { -// try { -// log.info("BootStrap uses X509 : \n X509 Certificate (Hex): [{}] \n Private Key (Hex): [{}]", -// Hex.encodeHexString(this.certificate.getEncoded()), -// Hex.encodeHexString(this.privateKey.getEncoded())); -// } catch (CertificateEncodingException e) { -// e.printStackTrace(); -// } -// } -} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java index 3344180baa..3ba6daee7a 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java @@ -49,7 +49,6 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession this.securityChecker = securityChecker; } - @Override public BootstrapSession begin(String endpoint, Identity clientIdentity) { boolean authorized; if (bsSecurityStore != null) { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LWM2MGenerationPSkRPkECC.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LWM2MGenerationPSkRPkECC.java index 552593f110..f4abbd2fda 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LWM2MGenerationPSkRPkECC.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LWM2MGenerationPSkRPkECC.java @@ -73,15 +73,15 @@ public class LWM2MGenerationPSkRPkECC { try { kpg = KeyPairGenerator.getInstance(algorithm, provider); } catch (NoSuchAlgorithmException e) { - e.printStackTrace(); + log.error("", e); } catch (NoSuchProviderException e) { - e.printStackTrace(); + log.error("", e); } ECGenParameterSpec ecsp = new ECGenParameterSpec(nameParameterSpec); try { kpg.initialize(ecsp); } catch (InvalidAlgorithmParameterException e) { - e.printStackTrace(); + log.error("", e); } KeyPair kp = kpg.genKeyPair(); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2MGetSecurityInfo.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java similarity index 77% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2MGetSecurityInfo.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java index 0603c84282..64739e85f5 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2MGetSecurityInfo.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java @@ -39,12 +39,15 @@ import java.util.Optional; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.*; +import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.NO_SEC; +import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.PSK; +import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.RPK; +import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.X509; @Slf4j @Component("LwM2MGetSecurityInfo") @ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' ) || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')") -public class LwM2MGetSecurityInfo { +public class LwM2mCredentialsSecurityInfoValidator { @Autowired public LwM2MTransportContextServer contextS; @@ -53,7 +56,13 @@ public class LwM2MGetSecurityInfo { public LwM2MTransportContextBootstrap contextBS; - public ReadResultSecurityStore getSecurityInfo(String endPoint, TypeServer keyValue) { + /** + * Request to thingsboard Response from thingsboard ValidateDeviceLwM2MCredentials + * @param endPoint - + * @param keyValue - + * @return ValidateDeviceCredentialsResponseMsg and SecurityInfo + */ + public ReadResultSecurityStore createAndValidateCredentialsSecurityInfo(String endPoint, TypeServer keyValue) { CountDownLatch latch = new CountDownLatch(1); final ReadResultSecurityStore[] resultSecurityStore = new ReadResultSecurityStore[1]; contextS.getTransportService().process(ValidateDeviceLwM2MCredentialsRequestMsg.newBuilder().setCredentialsId(endPoint).build(), @@ -61,7 +70,7 @@ public class LwM2MGetSecurityInfo { @Override public void onSuccess(ValidateDeviceCredentialsResponseMsg msg) { String credentialsBody = msg.getCredentialsBody(); - resultSecurityStore[0] = putSecurityInfo(endPoint, msg.getDeviceInfo().getDeviceName(), credentialsBody, keyValue); + resultSecurityStore[0] = createSecurityInfo(endPoint, credentialsBody, keyValue); resultSecurityStore[0].setMsg(msg); Optional deviceProfileOpt = LwM2MTransportHandler.decode(msg.getProfileBody().toByteArray()); deviceProfileOpt.ifPresent(profile -> resultSecurityStore[0].setDeviceProfile(profile)); @@ -70,20 +79,27 @@ public class LwM2MGetSecurityInfo { @Override public void onError(Throwable e) { - log.trace("[{}] Failed to process credentials PSK: {}", endPoint, e); - resultSecurityStore[0] = putSecurityInfo(endPoint, null, null, null); + log.trace("[{}] [{}] Failed to process credentials ", endPoint, e); + resultSecurityStore[0] = createSecurityInfo(endPoint, null, null); latch.countDown(); } }); try { latch.await(contextS.getCtxServer().getTimeout(), TimeUnit.MILLISECONDS); } catch (InterruptedException e) { - e.printStackTrace(); + log.error("Failed to await credentials!", e); } return resultSecurityStore[0]; } - private ReadResultSecurityStore putSecurityInfo(String endPoint, String deviceName, String jsonStr, TypeServer keyValue) { + /** + * Create new SecurityInfo + * @param endPoint - + * @param jsonStr - + * @param keyValue - + * @return SecurityInfo + */ + private ReadResultSecurityStore createSecurityInfo(String endPoint, String jsonStr, TypeServer keyValue) { ReadResultSecurityStore result = new ReadResultSecurityStore(); JsonObject objectMsg = LwM2MTransportHandler.validateJson(jsonStr); if (objectMsg != null && !objectMsg.isJsonNull()) { @@ -99,20 +115,21 @@ public class LwM2MGetSecurityInfo { if (keyValue.equals(TypeServer.BOOTSTRAP)) { result.setBootstrapJsonCredential(object); result.setEndPoint(endPoint); + result.setSecurityMode(LwM2MSecurityMode.fromSecurityMode(object.get("bootstrapServer").getAsJsonObject().get("securityMode").getAsString().toLowerCase()).code); } else { LwM2MSecurityMode lwM2MSecurityMode = LwM2MSecurityMode.fromSecurityMode(object.get("securityConfigClientMode").getAsString().toLowerCase()); switch (lwM2MSecurityMode) { case NO_SEC: - getClientSecurityInfoNoSec(result); + createClientSecurityInfoNoSec(result); break; case PSK: - getClientSecurityInfoPSK(result, endPoint, object); + createClientSecurityInfoPSK(result, endPoint, object); break; case RPK: - getClientSecurityInfoRPK(result, endPoint, object); + createClientSecurityInfoRPK(result, endPoint, object); break; case X509: - getClientSecurityInfoX509(result, endPoint); + createClientSecurityInfoX509(result, endPoint); break; default: break; @@ -123,12 +140,12 @@ public class LwM2MGetSecurityInfo { return result; } - private void getClientSecurityInfoNoSec(ReadResultSecurityStore result) { + private void createClientSecurityInfoNoSec(ReadResultSecurityStore result) { result.setSecurityInfo(null); result.setSecurityMode(NO_SEC.code); } - private void getClientSecurityInfoPSK(ReadResultSecurityStore result, String endPoint, JsonObject object) { + private void createClientSecurityInfoPSK(ReadResultSecurityStore result, String endPoint, JsonObject object) { /** PSK Deserialization */ String identity = (object.has("identity") && object.get("identity").isJsonPrimitive()) ? object.get("identity").getAsString() : null; if (identity != null && !identity.isEmpty()) { @@ -148,7 +165,7 @@ public class LwM2MGetSecurityInfo { } } - private void getClientSecurityInfoRPK(ReadResultSecurityStore result, String endpoint, JsonObject object) { + private void createClientSecurityInfoRPK(ReadResultSecurityStore result, String endpoint, JsonObject object) { try { if (object.has("key") && object.get("key").isJsonPrimitive()) { byte[] rpkkey = Hex.decodeHex(object.get("key").getAsString().toLowerCase().toCharArray()); @@ -163,7 +180,7 @@ public class LwM2MGetSecurityInfo { } } - private void getClientSecurityInfoX509(ReadResultSecurityStore result, String endpoint) { + private void createClientSecurityInfoX509(ReadResultSecurityStore result, String endpoint) { result.setSecurityInfo(SecurityInfo.newX509CertInfo(endpoint)); result.setSecurityMode(X509.code); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MSessionMsgListener.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MSessionMsgListener.java index fe3f4d69e7..21f1c5e2cc 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MSessionMsgListener.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MSessionMsgListener.java @@ -33,17 +33,17 @@ import java.util.Optional; @Slf4j public class LwM2MSessionMsgListener implements GenericFutureListener>, SessionMsgListener { - private LwM2MTransportService service; + private LwM2MTransportServiceImpl service; private TransportProtos.SessionInfoProto sessionInfo; - LwM2MSessionMsgListener(LwM2MTransportService service, TransportProtos.SessionInfoProto sessionInfo) { + public LwM2MSessionMsgListener(LwM2MTransportServiceImpl service, TransportProtos.SessionInfoProto sessionInfo) { this.service = service; this.sessionInfo = sessionInfo; } @Override public void onGetAttributesResponse(GetAttributeResponseMsg getAttributesResponse) { - log.info("[{}] attributesResponse", getAttributesResponse); + this.service.onGetAttributesResponse(getAttributesResponse, this.sessionInfo); } @Override diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportContextServer.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportContextServer.java index d7f37e9719..fa171a9399 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportContextServer.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportContextServer.java @@ -30,15 +30,26 @@ package org.thingsboard.server.transport.lwm2m.server; * limitations under the License. */ +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; import org.thingsboard.server.common.transport.TransportContext; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.common.transport.TransportServiceCallback; +import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.common.transport.lwm2m.LwM2MTransportConfigServer; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.transport.lwm2m.server.adaptors.LwM2MJsonAdaptor; +import org.thingsboard.server.transport.lwm2m.server.secure.LwM2mInMemorySecurityStore; import javax.annotation.PostConstruct; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.LOG_LW2M_TELEMETRY; + @Slf4j @Component @ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')") @@ -47,14 +58,87 @@ public class LwM2MTransportContextServer extends TransportContext { private LwM2MTransportConfigServer ctxServer; @Autowired - LwM2MTransportConfigServer lwM2MTransportConfigServer; + protected LwM2MTransportConfigServer lwM2MTransportConfigServer; + + @Autowired + private TransportService transportService; + + @Getter + @Autowired + private LwM2MJsonAdaptor adaptor; + + @Autowired + LwM2mInMemorySecurityStore lwM2mInMemorySecurityStore; @PostConstruct public void init() { this.ctxServer = lwM2MTransportConfigServer; } - public LwM2MTransportConfigServer getCtxServer () { + public LwM2MTransportConfigServer getCtxServer() { return this.ctxServer; } + + /** + * Sent to Thingsboard Attribute || Telemetry + * + * @param msg - JsonObject: [{name: value}] + * @return - dummy + */ + private TransportServiceCallback getPubAckCallbackSentAttrTelemetry(final T msg) { + return new TransportServiceCallback() { + @Override + public void onSuccess(Void dummy) { + log.trace("Success to publish msg: {}, dummy: {}", msg, dummy); + } + + @Override + public void onError(Throwable e) { + log.trace("[{}] Failed to publish msg: {}", msg, e); + } + }; + } + + public void sentParametersOnThingsboard(JsonElement msg, String topicName, TransportProtos.SessionInfoProto sessionInfo) { + try { + if (topicName.equals(LwM2MTransportHandler.DEVICE_ATTRIBUTES_TOPIC)) { + TransportProtos.PostAttributeMsg postAttributeMsg = adaptor.convertToPostAttributes(msg); + TransportServiceCallback call = this.getPubAckCallbackSentAttrTelemetry(postAttributeMsg); + transportService.process(sessionInfo, postAttributeMsg, this.getPubAckCallbackSentAttrTelemetry(call)); + } else if (topicName.equals(LwM2MTransportHandler.DEVICE_TELEMETRY_TOPIC)) { + TransportProtos.PostTelemetryMsg postTelemetryMsg = adaptor.convertToPostTelemetry(msg); + TransportServiceCallback call = this.getPubAckCallbackSentAttrTelemetry(postTelemetryMsg); + transportService.process(sessionInfo, postTelemetryMsg, this.getPubAckCallbackSentAttrTelemetry(call)); + } + } catch (AdaptorException e) { + log.error("[{}] Failed to process publish msg [{}]", topicName, e); + log.info("[{}] Closing current session due to invalid publish", topicName); + } + } + + public JsonObject getTelemetryMsgObject(String logMsg) { + JsonObject telemetries = new JsonObject(); + telemetries.addProperty(LOG_LW2M_TELEMETRY, logMsg); + return telemetries; + } + + /** + * @return - sessionInfo after access connect client + */ + public TransportProtos.SessionInfoProto getValidateSessionInfo(TransportProtos.ValidateDeviceCredentialsResponseMsg msg, long mostSignificantBits, long leastSignificantBits) { + return TransportProtos.SessionInfoProto.newBuilder() + .setNodeId(this.getNodeId()) + .setSessionIdMSB(mostSignificantBits) + .setSessionIdLSB(leastSignificantBits) + .setDeviceIdMSB(msg.getDeviceInfo().getDeviceIdMSB()) + .setDeviceIdLSB(msg.getDeviceInfo().getDeviceIdLSB()) + .setTenantIdMSB(msg.getDeviceInfo().getTenantIdMSB()) + .setTenantIdLSB(msg.getDeviceInfo().getTenantIdLSB()) + .setDeviceName(msg.getDeviceInfo().getDeviceName()) + .setDeviceType(msg.getDeviceInfo().getDeviceType()) + .setDeviceProfileIdLSB(msg.getDeviceInfo().getDeviceProfileIdLSB()) + .setDeviceProfileIdMSB(msg.getDeviceInfo().getDeviceProfileIdMSB()) + .build(); + } + } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportHandler.java index 4335f9fec0..249ee78344 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportHandler.java @@ -16,7 +16,6 @@ package org.thingsboard.server.transport.lwm2m.server; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.gson.Gson; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.JsonSyntaxException; @@ -24,57 +23,60 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.eclipse.californium.core.network.config.NetworkConfig; import org.eclipse.leshan.core.model.ResourceModel; +import org.eclipse.leshan.core.node.LwM2mMultipleResource; import org.eclipse.leshan.core.node.LwM2mNode; import org.eclipse.leshan.core.node.LwM2mObject; import org.eclipse.leshan.core.node.LwM2mObjectInstance; +import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mSingleResource; -import org.eclipse.leshan.core.node.LwM2mMultipleResource; +import org.eclipse.leshan.core.node.codec.CodecException; import org.eclipse.leshan.core.util.Hex; -import org.eclipse.leshan.server.californium.LeshanServer; import org.eclipse.leshan.server.californium.LeshanServerBuilder; import org.nustaq.serialization.FSTConfiguration; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.transport.lwm2m.server.client.AttrTelemetryObserveValue; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClient; -import javax.annotation.PostConstruct; import java.io.File; import java.io.IOException; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Optional; -import java.util.Map; import java.util.Arrays; -import java.util.List; -import java.util.ArrayList; +import java.util.Date; import java.util.LinkedList; -import java.util.Collections; +import java.util.Optional; @Slf4j -@Component("LwM2MTransportHandler") -@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' )|| ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')") -public class LwM2MTransportHandler{ +//@Component("LwM2MTransportHandler") +//@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' )|| ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')") +public class LwM2MTransportHandler { // We choose a default timeout a bit higher to the MAX_TRANSMIT_WAIT(62-93s) which is the time from starting to // send a Confirmable message to the time when an acknowledgement is no longer expected. - public static final long DEFAULT_TIMEOUT = 2 * 60 * 1000l; // 2min in ms - public static final String OBSERVE_ATTRIBUTE_TELEMETRY = "observeAttr"; - public static final String KEYNAME = "keyName"; + + public static final String BASE_DEVICE_API_TOPIC = "v1/devices/me"; public static final String ATTRIBUTE = "attribute"; public static final String TELEMETRY = "telemetry"; + private static final String REQUEST = "/request"; + private static final String RESPONSE = "/response"; + private static final String ATTRIBUTES = "/" + ATTRIBUTE; + public static final String TELEMETRIES = "/" + TELEMETRY; + public static final String ATTRIBUTES_RESPONSE = ATTRIBUTES + RESPONSE; + public static final String ATTRIBUTES_REQUEST = ATTRIBUTES + REQUEST; + public static final String DEVICE_ATTRIBUTES_RESPONSE = ATTRIBUTES_RESPONSE + "/"; + public static final String DEVICE_ATTRIBUTES_REQUEST = ATTRIBUTES_REQUEST + "/"; + public static final String DEVICE_ATTRIBUTES_TOPIC = BASE_DEVICE_API_TOPIC + ATTRIBUTES; + public static final String DEVICE_TELEMETRY_TOPIC = BASE_DEVICE_API_TOPIC + TELEMETRIES; + + public static final long DEFAULT_TIMEOUT = 2 * 60 * 1000L; // 2min in ms + public static final String OBSERVE_ATTRIBUTE_TELEMETRY = "observeAttr"; + public static final String KEYNAME = "keyName"; public static final String OBSERVE = "observe"; public static final String BOOTSTRAP = "bootstrap"; public static final String SERVERS = "servers"; public static final String LWM2M_SERVER = "lwm2mServer"; public static final String BOOTSTRAP_SERVER = "bootstrapServer"; - public static final String BASE_DEVICE_API_TOPIC = "v1/devices/me"; - public static final String DEVICE_ATTRIBUTES_TOPIC = BASE_DEVICE_API_TOPIC + "/attributes"; - public static final String DEVICE_TELEMETRY_TOPIC = BASE_DEVICE_API_TOPIC + "/telemetry"; + public static final String LOG_LW2M_TELEMETRY = "logLwm2m"; public static final String LOG_LW2M_INFO = "info"; public static final String LOG_LW2M_ERROR = "error"; @@ -91,44 +93,55 @@ public class LwM2MTransportHandler{ /** * Replaces the Object Instance or the Resource(s) with the new value provided in the “Write” operation. (see * section 5.3.3 of the LW M2M spec). + * if all resources are to be replaced */ public static final String POST_TYPE_OPER_WRITE_REPLACE = "replace"; /** * Adds or updates Resources provided in the new value and leaves other existing Resources unchanged. (see section * 5.3.3 of the LW M2M spec). + * if this is a partial update request */ public static final String PUT_TYPE_OPER_WRITE_UPDATE = "update"; public static final String PUT_TYPE_OPER_WRITE_ATTRIBUTES = "wright-attributes"; public static final String EVENT_AWAKE = "AWAKE"; + public static final String SERVICE_CHANNEL = "SERVICE"; + public static final String RESPONSE_CHANNEL = "RESP"; - private static Gson gson = null; +// @Autowired +// @Qualifier("LeshanServerCert") +// private LeshanServer lhServerCert; +// +// @Autowired +// @Qualifier("LeshanServerNoSecPskRpk") +// private LeshanServer lhServerNoSecPskRpk; - @Autowired - @Qualifier("LeshanServerCert") - private LeshanServer lhServerCert; +// @Autowired +// @Qualifier("ServerListenerCert") +// private LwM2mServerListener serverListenerCert; +// +// @Autowired +// @Qualifier("ServerListenerNoSecPskRpk") +// private LwM2mServerListener serverListenerNoSecPskRpk; - @Autowired - @Qualifier("leshanServerNoSecPskRpk") - private LeshanServer lhServerNoSecPskRpk; - @Autowired - private LwM2MTransportService service; +// @PostConstruct +// public void init() { +// try { +// serverListenerCert.init(lhServerCert); +// this.lhServerCert.getRegistrationService().addListener(serverListenerCert.registrationListener); +// this.lhServerCert.getPresenceService().addListener(serverListenerCert.presenceListener); +// this.lhServerCert.getObservationService().addListener(serverListenerCert.observationListener); +// serverListenerNoSecPskRpk.init(lhServerNoSecPskRpk); +// this.lhServerNoSecPskRpk.getRegistrationService().addListener(serverListenerNoSecPskRpk.registrationListener); +// this.lhServerNoSecPskRpk.getPresenceService().addListener(serverListenerNoSecPskRpk.presenceListener); +// this.lhServerNoSecPskRpk.getObservationService().addListener(serverListenerNoSecPskRpk.observationListener); +// } catch (Exception e) { +// log.error("init [{}]", e.toString()); +// } +// } - - @PostConstruct - public void init() { - LwM2mServerListener lwM2mServerListener = new LwM2mServerListener(lhServerCert, service); - this.lhServerCert.getRegistrationService().addListener(lwM2mServerListener.registrationListener); - this.lhServerCert.getPresenceService().addListener(lwM2mServerListener.presenceListener); - this.lhServerCert.getObservationService().addListener(lwM2mServerListener.observationListener); - lwM2mServerListener = new LwM2mServerListener(lhServerNoSecPskRpk, service); - this.lhServerNoSecPskRpk.getRegistrationService().addListener(lwM2mServerListener.registrationListener); - this.lhServerNoSecPskRpk.getPresenceService().addListener(lwM2mServerListener.presenceListener); - this.lhServerNoSecPskRpk.getObservationService().addListener(lwM2mServerListener.observationListener); - } - - public static NetworkConfig getCoapConfig() { + public static NetworkConfig getCoapConfig(Integer serverPortNoSec, Integer serverSecurePort) { NetworkConfig coapConfig; File configFile = new File(NetworkConfig.DEFAULT_FILE_NAME); if (configFile.isFile()) { @@ -138,28 +151,26 @@ public class LwM2MTransportHandler{ coapConfig = LeshanServerBuilder.createDefaultNetworkConfig(); coapConfig.store(configFile); } + coapConfig.setString("COAP_PORT", Integer.toString(serverPortNoSec)); + coapConfig.setString("COAP_SECURE_PORT", Integer.toString(serverSecurePort)); return coapConfig; } - public static String getValueTypeToString (Object value, ResourceModel.Type type) { + public static boolean equalsResourceValue(Object valueOld, Object valueNew, ResourceModel.Type type, LwM2mPath resourcePath) throws CodecException { switch (type) { - case STRING: // String - case OBJLNK: // ObjectLink - return value.toString(); - case INTEGER: // Long - return Long.toString((long) value); - case BOOLEAN: // Boolean - return Boolean.toString((Boolean) value); - case FLOAT: // Double - return Double.toString((Float)value); - case TIME: // Date - String DATE_FORMAT = "MMM d, yyyy HH:mm a"; - DateFormat formatter = new SimpleDateFormat(DATE_FORMAT); - return formatter.format(new Date((Long) Integer.toUnsignedLong(Integer.valueOf((Integer) value)))); - case OPAQUE: // byte[] value, base64 - return Hex.encodeHexString((byte[])value); + case BOOLEAN: + case INTEGER: + case FLOAT: + return String.valueOf(valueOld).equals(String.valueOf(valueNew)); + case TIME: + return ((Date) valueOld).getTime() == ((Date) valueNew).getTime(); + case STRING: + case OBJLNK: + return valueOld.equals(valueNew); + case OPAQUE: + return Hex.decodeHex(((String) valueOld).toCharArray()).equals(Hex.decodeHex(((String) valueNew).toCharArray())); default: - return null; + throw new CodecException("Invalid value type for resource %s, type %s", resourcePath, type); } } @@ -182,26 +193,25 @@ public class LwM2MTransportHandler{ attrTelemetryObserveValue.setPostAttributeProfile(profilesConfigData.get(ATTRIBUTE).getAsJsonArray()); attrTelemetryObserveValue.setPostTelemetryProfile(profilesConfigData.get(TELEMETRY).getAsJsonArray()); attrTelemetryObserveValue.setPostObserveProfile(profilesConfigData.get(OBSERVE).getAsJsonArray()); - return attrTelemetryObserveValue; + return attrTelemetryObserveValue; } /** - * @return deviceProfileBody with Observe&Attribute&Telemetry From Thingsboard * Example: with pathResource (use only pathResource) * property: "observeAttr" * {"keyName": { - * "/3/0/1": "modelNumber", - * "/3/0/0": "manufacturer", - * "/3/0/2": "serialNumber" - * }, + * "/3/0/1": "modelNumber", + * "/3/0/0": "manufacturer", + * "/3/0/2": "serialNumber" + * }, * "attribute":["/2/0/1","/3/0/9"], * "telemetry":["/1/0/1","/2/0/1","/6/0/1"], * "observe":["/2/0","/2/0/0","/4/0/2"]} */ public static JsonObject getObserveAttrTelemetryFromThingsboard(DeviceProfile deviceProfile) { if (deviceProfile != null && ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties().size() > 0) { - Lwm2mDeviceProfileTransportConfiguration lwm2mDeviceProfileTransportConfiguration = (Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); +// Lwm2mDeviceProfileTransportConfiguration lwm2mDeviceProfileTransportConfiguration = (Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); Object observeAttr = ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties(); try { ObjectMapper mapper = new ObjectMapper(); @@ -209,7 +219,7 @@ public class LwM2MTransportHandler{ JsonObject objectMsg = (observeAttrStr != null) ? validateJson(observeAttrStr) : null; return (getValidateCredentialsBodyFromThingsboard(objectMsg)) ? objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject() : null; } catch (IOException e) { - e.printStackTrace(); + log.error("", e); } } return null; @@ -217,7 +227,6 @@ public class LwM2MTransportHandler{ public static JsonObject getBootstrapParametersFromThingsboard(DeviceProfile deviceProfile) { if (deviceProfile != null && ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties().size() > 0) { - Lwm2mDeviceProfileTransportConfiguration lwm2mDeviceProfileTransportConfiguration = (Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); Object bootstrap = ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties(); try { ObjectMapper mapper = new ObjectMapper(); @@ -225,7 +234,7 @@ public class LwM2MTransportHandler{ JsonObject objectMsg = (bootstrapStr != null) ? validateJson(bootstrapStr) : null; return (getValidateBootstrapProfileFromThingsboard(objectMsg)) ? objectMsg.get(BOOTSTRAP).getAsJsonObject() : null; } catch (IOException e) { - e.printStackTrace(); + log.error("", e); } } return null; @@ -276,7 +285,7 @@ public class LwM2MTransportHandler{ jsonValidFlesh = jsonValidFlesh.replaceAll("\n", ""); jsonValidFlesh = jsonValidFlesh.replaceAll("\t", ""); jsonValidFlesh = jsonValidFlesh.replaceAll(" ", ""); - String jsonValid = (jsonValidFlesh.substring(0, 1).equals("\"") && jsonValidFlesh.substring(jsonValidFlesh.length() - 1).equals("\"")) ? jsonValidFlesh.substring(1, jsonValidFlesh.length() - 1) : jsonValidFlesh; + String jsonValid = (jsonValidFlesh.charAt(0) == '"' && jsonValidFlesh.charAt(jsonValidFlesh.length() - 1) == '"') ? jsonValidFlesh.substring(1, jsonValidFlesh.length() - 1) : jsonValidFlesh; try { object = new JsonParser().parse(jsonValid).getAsJsonObject(); } catch (JsonSyntaxException e) { @@ -288,7 +297,7 @@ public class LwM2MTransportHandler{ public static Optional decode(byte[] byteArray) { try { - FSTConfiguration config = FSTConfiguration.createDefaultConfiguration();; + FSTConfiguration config = FSTConfiguration.createDefaultConfiguration(); T msg = (T) config.asObject(byteArray); return Optional.ofNullable(msg); } catch (IllegalArgumentException e) { @@ -297,37 +306,29 @@ public class LwM2MTransportHandler{ } } - /** - * Equals to Map for values - * @param map1 - * @param map2 - * @param - * @return - */ - public static > boolean mapsEquals(Map map1, Map map2) { - List values1 = new ArrayList(map1.values()); - List values2 = new ArrayList(map2.values()); - Collections.sort(values1); - Collections.sort(values2); - return values1.equals(values2); - } - - public static String convertCamelCase (String str) { - str = str.toLowerCase().replace("/[^a-z ]+/g", " "); - str = str.replace("/^(.)|\\s(.)/g", "$1"); - str = str.replace("/[^a-zA-Z]+/g", ""); - return str; - } - - public static String splitCamelCaseString(String s){ - LinkedList linkedListOut = new LinkedList(); + public static String splitCamelCaseString(String s) { + LinkedList linkedListOut = new LinkedList<>(); LinkedList linkedList = new LinkedList((Arrays.asList(s.split(" ")))); - linkedList.stream().forEach(str-> { + linkedList.forEach(str -> { String strOut = str.replaceAll("\\W", "").replaceAll("_", "").toUpperCase(); - if (strOut.length()>1) linkedListOut.add(strOut.substring(0, 1) + strOut.substring(1).toLowerCase()); + if (strOut.length() > 1) linkedListOut.add(strOut.charAt(0) + strOut.substring(1).toLowerCase()); else linkedListOut.add(strOut); }); linkedListOut.set(0, (linkedListOut.get(0).substring(0, 1).toLowerCase() + linkedListOut.get(0).substring(1))); return StringUtils.join(linkedListOut, ""); } + + public static TransportServiceCallback getAckCallback(LwM2MClient lwM2MClient, int requestId, String typeTopic) { + return new TransportServiceCallback() { + @Override + public void onSuccess(Void dummy) { + log.trace("[{}] [{}] - requestId [{}] - EndPoint , Access AckCallback", typeTopic, requestId, lwM2MClient.getEndPoint()); + } + + @Override + public void onError(Throwable e) { + log.trace("[{}] Failed to publish msg", e.toString()); + } + }; + } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportRequest.java index 0df0a44504..1c4590ace8 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportRequest.java @@ -20,26 +20,28 @@ import org.eclipse.californium.core.coap.Response; import org.eclipse.leshan.core.attributes.Attribute; import org.eclipse.leshan.core.attributes.AttributeSet; import org.eclipse.leshan.core.model.ResourceModel; +import org.eclipse.leshan.core.node.LwM2mNode; +import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mSingleResource; import org.eclipse.leshan.core.node.ObjectLink; import org.eclipse.leshan.core.observation.Observation; +import org.eclipse.leshan.core.request.CancelObservationRequest; import org.eclipse.leshan.core.request.ContentFormat; -import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.request.DiscoverRequest; import org.eclipse.leshan.core.request.DownlinkRequest; +import org.eclipse.leshan.core.request.ExecuteRequest; import org.eclipse.leshan.core.request.ObserveRequest; -import org.eclipse.leshan.core.request.CancelObservationRequest; import org.eclipse.leshan.core.request.ReadRequest; -import org.eclipse.leshan.core.request.ExecuteRequest; import org.eclipse.leshan.core.request.WriteAttributesRequest; -import org.eclipse.leshan.core.response.ResponseCallback; -import org.eclipse.leshan.core.response.LwM2mResponse; -import org.eclipse.leshan.core.response.ObserveResponse; -import org.eclipse.leshan.core.response.ReadResponse; +import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.response.CancelObservationResponse; import org.eclipse.leshan.core.response.DeleteResponse; -import org.eclipse.leshan.core.response.ExecuteResponse; import org.eclipse.leshan.core.response.DiscoverResponse; +import org.eclipse.leshan.core.response.ExecuteResponse; +import org.eclipse.leshan.core.response.LwM2mResponse; +import org.eclipse.leshan.core.response.ObserveResponse; +import org.eclipse.leshan.core.response.ReadResponse; +import org.eclipse.leshan.core.response.ResponseCallback; import org.eclipse.leshan.core.response.WriteAttributesResponse; import org.eclipse.leshan.core.response.WriteResponse; import org.eclipse.leshan.core.util.Hex; @@ -50,6 +52,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClient; +import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import javax.annotation.PostConstruct; import java.util.ArrayList; @@ -65,32 +68,36 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandle import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.GET_TYPE_OPER_DISCOVER; import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.GET_TYPE_OPER_OBSERVE; import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.GET_TYPE_OPER_READ; -import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.PUT_TYPE_OPER_WRITE_ATTRIBUTES; -import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.PUT_TYPE_OPER_WRITE_UPDATE; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.LOG_LW2M_ERROR; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.LOG_LW2M_INFO; import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.POST_TYPE_OPER_EXECUTE; import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.POST_TYPE_OPER_OBSERVE_CANCEL; import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.POST_TYPE_OPER_WRITE_REPLACE; -import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.LOG_LW2M_ERROR; -import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.LOG_LW2M_INFO; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.PUT_TYPE_OPER_WRITE_ATTRIBUTES; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.PUT_TYPE_OPER_WRITE_UPDATE; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.RESPONSE_CHANNEL; @Slf4j @Service("LwM2MTransportRequest") @ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' ) || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')") public class LwM2MTransportRequest { - private final ExecutorService executorService; - private static final String RESPONSE_CHANNEL = "THINGSBOARD_RESP"; + private ExecutorService executorResponse; + private ExecutorService executorResponseError; + private LwM2mValueConverterImpl converter; @Autowired - LwM2MTransportService service; - - public LwM2MTransportRequest() { - executorService = Executors.newCachedThreadPool( - new NamedThreadFactory(String.format("LwM2M %s channel response", RESPONSE_CHANNEL))); - } + LwM2MTransportServiceImpl service; + @Autowired + public LwM2MTransportContextServer context; @PostConstruct public void init() { + this.converter = LwM2mValueConverterImpl.getInstance(); + executorResponse = Executors.newFixedThreadPool(this.context.getCtxServer().getRequestPoolSize(), + new NamedThreadFactory(String.format("LwM2M %s channel response", RESPONSE_CHANNEL))); + executorResponseError = Executors.newFixedThreadPool(this.context.getCtxServer().getRequestErrorPoolSize(), + new NamedThreadFactory(String.format("LwM2M %s channel response Error", RESPONSE_CHANNEL))); } public Collection doGetRegistrations(LeshanServer lwServer) { @@ -113,16 +120,13 @@ public class LwM2MTransportRequest { * @param lwM2MClient * @param observation */ - public void sendAllRequest(LeshanServer lwServer, Registration registration, String target, String typeOper, - String contentFormatParam, LwM2MClient lwM2MClient, Observation observation, Object params, long timeoutInMs) { - ResultIds resultIds = new ResultIds(target); + public void sendAllRequest(LeshanServer lwServer, Registration registration, String target, String typeOper, String contentFormatParam, + LwM2MClient lwM2MClient, Observation observation, Object params, long timeoutInMs, boolean isDelayedUpdate) { + LwM2mPath resultIds = new LwM2mPath(target); if (registration != null && resultIds.getObjectId() >= 0) { DownlinkRequest request = null; ContentFormat contentFormat = contentFormatParam != null ? ContentFormat.fromName(contentFormatParam.toUpperCase()) : null; - ResourceModel resource = (resultIds.resourceId >= 0) ? (lwM2MClient != null) ? - lwM2MClient.getModelObjects().get(resultIds.getObjectId()).getObjectModel().resources.get(resultIds.resourceId) : null : null; - ResourceModel.Type resType = (resource == null) ? null : resource.type; - boolean resMultiple = (resource == null) ? false : resource.multiple; + ResourceModel resource = service.context.getCtxServer().getResourceModel(registration, resultIds); timeoutInMs = timeoutInMs > 0 ? timeoutInMs : DEFAULT_TIMEOUT; switch (typeOper) { case GET_TYPE_OPER_READ: @@ -132,10 +136,10 @@ public class LwM2MTransportRequest { request = new DiscoverRequest(target); break; case GET_TYPE_OPER_OBSERVE: - if (resultIds.getResourceId() >= 0) { - request = new ObserveRequest(resultIds.getObjectId(), resultIds.getInstanceId(), resultIds.getResourceId()); - } else if (resultIds.getInstanceId() >= 0) { - request = new ObserveRequest(resultIds.getObjectId(), resultIds.getInstanceId()); + if (resultIds.isResource()) { + request = new ObserveRequest(resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId()); + } else if (resultIds.isObjectInstance()) { + request = new ObserveRequest(resultIds.getObjectId(), resultIds.getObjectInstanceId()); } else if (resultIds.getObjectId() >= 0) { request = new ObserveRequest(resultIds.getObjectId()); } @@ -144,27 +148,30 @@ public class LwM2MTransportRequest { request = new CancelObservationRequest(observation); break; case POST_TYPE_OPER_EXECUTE: - if (params != null && !resMultiple) { - request = new ExecuteRequest(target, LwM2MTransportHandler.getValueTypeToString(params, resType)); + if (params != null && resource != null && !resource.multiple) { + request = new ExecuteRequest(target, (String) this.converter.convertValue(params, resource.type, ResourceModel.Type.STRING, resultIds)); } else { request = new ExecuteRequest(target); } break; case POST_TYPE_OPER_WRITE_REPLACE: // Request to write a String Single-Instance Resource using the TLV content format. - if (contentFormat.equals(ContentFormat.TLV) && !resMultiple) { - request = this.getWriteRequestSingleResource(null, resultIds.getObjectId(), resultIds.getInstanceId(), resultIds.getResourceId(), params, resType); - } - // Mode.REPLACE && Request to write a String Single-Instance Resource using the given content format (TEXT, TLV, JSON) - else if (!contentFormat.equals(ContentFormat.TLV) && !resMultiple) { - request = this.getWriteRequestSingleResource(contentFormat, resultIds.getObjectId(), resultIds.getInstanceId(), resultIds.getResourceId(), params, resType); + if (resource != null) { + if (contentFormat.equals(ContentFormat.TLV) && !resource.multiple) { + request = this.getWriteRequestSingleResource(null, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId(), params, resource.type, registration); + } + // Mode.REPLACE && Request to write a String Single-Instance Resource using the given content format (TEXT, TLV, JSON) + else if (!contentFormat.equals(ContentFormat.TLV) && !resource.multiple) { + request = this.getWriteRequestSingleResource(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId(), params, resource.type, registration); + } } break; case PUT_TYPE_OPER_WRITE_UPDATE: if (resultIds.getResourceId() >= 0) { ResourceModel resourceModel = lwServer.getModelProvider().getObjectModel(registration).getObjectModel(resultIds.getObjectId()).resources.get(resultIds.getResourceId()); ResourceModel.Type typeRes = resourceModel.type; -// request = getWriteRequestResource(resultIds.getObjectId(), resultIds.getInstanceId(), resultIds.getResourceId(), params, typeRes); + LwM2mNode node = LwM2mSingleResource.newStringResource(resultIds.getResourceId(), (String) this.converter.convertValue(params, resource.type, ResourceModel.Type.STRING, resultIds)); + request = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, target, node); } break; case PUT_TYPE_OPER_WRITE_ATTRIBUTES: @@ -202,55 +209,76 @@ public class LwM2MTransportRequest { Attribute pmin = new Attribute(MINIMUM_PERIOD, Integer.toUnsignedLong(Integer.valueOf("1"))); Attribute[] attrs = {pmin}; AttributeSet attrSet = new AttributeSet(attrs); - if (resultIds.getResourceId() >= 0) { - request = new WriteAttributesRequest(resultIds.getObjectId(), resultIds.getInstanceId(), resultIds.getResourceId(), attrSet); - } else if (resultIds.getInstanceId() >= 0) { - request = new WriteAttributesRequest(resultIds.getObjectId(), resultIds.getInstanceId(), attrSet); + if (resultIds.isResource()) { + request = new WriteAttributesRequest(resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId(), attrSet); + } else if (resultIds.isObjectInstance()) { + request = new WriteAttributesRequest(resultIds.getObjectId(), resultIds.getObjectInstanceId(), attrSet); } else if (resultIds.getObjectId() >= 0) { request = new WriteAttributesRequest(resultIds.getObjectId(), attrSet); } break; default: } - if (request != null) sendRequest(lwServer, registration, request, lwM2MClient, timeoutInMs); + + if (request != null) { + this.sendRequest(lwServer, registration, request, lwM2MClient, timeoutInMs, isDelayedUpdate); + } else if (isDelayedUpdate) { + String msg = String.format(LOG_LW2M_ERROR + ": sendRequest: Resource path - %s msg No SendRequest to Client", target); + service.sentLogsToThingsboard(msg, registration); + log.error("[{}] - [{}] No SendRequest", target); + this.handleResponseError(registration, target, lwM2MClient, isDelayedUpdate); + + } } } /** * - * @param lwServer - * @param registration - * @param request - * @param lwM2MClient - * @param timeoutInMs + * @param lwServer - + * @param registration - + * @param request - + * @param lwM2MClient - + * @param timeoutInMs - */ - private void sendRequest(LeshanServer lwServer, Registration registration, DownlinkRequest request, LwM2MClient lwM2MClient, long timeoutInMs) { + + private void sendRequest(LeshanServer lwServer, Registration registration, DownlinkRequest request, LwM2MClient lwM2MClient, long timeoutInMs, boolean isDelayedUpdate) { lwServer.send(registration, request, timeoutInMs, (ResponseCallback) response -> { - if (isSuccess(((Response)response.getCoapResponse()).getCode())) { - this.handleResponse(registration, request.getPath().toString(), response, request, lwM2MClient); + if (isSuccess(((Response) response.getCoapResponse()).getCode())) { + this.handleResponse(registration, request.getPath().toString(), response, request, lwM2MClient, isDelayedUpdate); if (request instanceof WriteRequest && ((WriteRequest) request).isReplaceRequest()) { - String msg = String.format(LOG_LW2M_INFO + " sendRequest Replace: CoapCde - %s Lwm2m code - %d name - %s Resource path - %s value - %s SendRequest to Client", - ((Response)response.getCoapResponse()).getCode(), response.getCode().getCode(), response.getCode().getName(), request.getPath().toString(), - ((LwM2mSingleResource)((WriteRequest) request).getNode()).getValue().toString()); - service.sentLogsToThingsboard(msg, registration.getId()); - log.info("[{}] - [{}] [{}] [{}] Update SendRequest", ((Response)response.getCoapResponse()).getCode(), response.getCode(), request.getPath().toString(), ((LwM2mSingleResource)((WriteRequest) request).getNode()).getValue()); + String delayedUpdateStr = ""; + if (isDelayedUpdate) { + delayedUpdateStr = " (delayedUpdate) "; + } + String msg = String.format(LOG_LW2M_INFO + ": sendRequest Replace%s: CoapCde - %s Lwm2m code - %d name - %s Resource path - %s value - %s SendRequest to Client", + delayedUpdateStr, ((Response) response.getCoapResponse()).getCode(), response.getCode().getCode(), response.getCode().getName(), request.getPath().toString(), + ((LwM2mSingleResource) ((WriteRequest) request).getNode()).getValue().toString()); + service.sentLogsToThingsboard(msg, registration); + log.info("[{}] - [{}] [{}] [{}] Update SendRequest[{}]", ((Response) response.getCoapResponse()).getCode(), response.getCode(), request.getPath().toString(), + ((LwM2mSingleResource) ((WriteRequest) request).getNode()).getValue(), delayedUpdateStr); + } + } else { + String msg = String.format(LOG_LW2M_ERROR + ": sendRequest: CoapCode - %s Lwm2m code - %d name - %s Resource path - %s SendRequest to Client", + ((Response) response.getCoapResponse()).getCode(), response.getCode().getCode(), response.getCode().getName(), request.getPath().toString()); + service.sentLogsToThingsboard(msg, registration); + log.error("[{}] - [{}] [{}] error SendRequest", ((Response) response.getCoapResponse()).getCode(), response.getCode(), request.getPath().toString()); + if (request instanceof WriteRequest && ((WriteRequest) request).isReplaceRequest() && isDelayedUpdate) { + this.handleResponseError(registration, request.getPath().toString(), lwM2MClient, isDelayedUpdate); } } - else { - String msg = String.format(LOG_LW2M_ERROR + " sendRequest: CoapCde - %s Lwm2m code - %d name - %s Resource path - %s SendRequest to Client", - ((Response)response.getCoapResponse()).getCode(), response.getCode().getCode(), response.getCode().getName(), request.getPath().toString()); - service.sentLogsToThingsboard(msg, registration.getId()); - log.error("[{}] - [{}] [{}] error SendRequest", ((Response)response.getCoapResponse()).getCode(), response.getCode(), request.getPath().toString()); - } + }, e -> { - String msg = String.format(LOG_LW2M_ERROR + " sendRequest: Resource path - %s msg error - %s SendRequest to Client", + String msg = String.format(LOG_LW2M_ERROR + ": sendRequest: Resource path - %s msg error - %s SendRequest to Client", request.getPath().toString(), e.toString()); - service.sentLogsToThingsboard(msg, registration.getId()); - log.error("[{}] - [{}] error SendRequest", request.getPath().toString(), e.toString()); + service.sentLogsToThingsboard(msg, registration); + log.error("[{}] - [{}] error SendRequest", request.getPath().toString(), e.toString()); + if (request instanceof WriteRequest && ((WriteRequest) request).isReplaceRequest() && isDelayedUpdate) { + this.handleResponseError(registration, request.getPath().toString(), lwM2MClient, isDelayedUpdate); + } }); } - private WriteRequest getWriteRequestSingleResource(ContentFormat contentFormat, Integer objectId, Integer instanceId, Integer resourceId, Object value, ResourceModel.Type type) { + private WriteRequest getWriteRequestSingleResource(ContentFormat contentFormat, Integer objectId, Integer instanceId, Integer resourceId, Object value, ResourceModel.Type type, Registration registration) { try { switch (type) { case STRING: // String @@ -264,29 +292,38 @@ public class LwM2MTransportRequest { case FLOAT: // Double return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, Double.valueOf(value.toString())) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, Double.valueOf(value.toString())); case TIME: // Date - return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, new Date((Long) Integer.toUnsignedLong(Integer.valueOf(value.toString())))) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, new Date((Long) Integer.toUnsignedLong(Integer.valueOf(value.toString())))); + return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, new Date(Long.decode(value.toString()))) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, new Date((Long) Integer.toUnsignedLong(Integer.valueOf(value.toString())))); case OPAQUE: // byte[] value, base64 return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, Hex.decodeHex(value.toString().toCharArray())) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, Hex.decodeHex(value.toString().toCharArray())); default: } return null; } catch (NumberFormatException e) { - String patn = "/" + objectId + "/" + instanceId + "/" + resourceId; - log.error("Path: [{}] type: [{}] value: [{}] errorMsg: [{}]]", patn, type, value, e.toString()); - return null; + String patn = "/" + objectId + "/" + instanceId + "/" + resourceId; + String msg = String.format(LOG_LW2M_ERROR + ": NumberFormatException: Resource path - %s type - %s value - %s msg error - %s SendRequest to Client", + patn, type, value, e.toString()); + service.sentLogsToThingsboard(msg, registration); + log.error("Path: [{}] type: [{}] value: [{}] errorMsg: [{}]]", patn, type, value, e.toString()); + return null; } } - private void handleResponse(Registration registration, final String path, LwM2mResponse response, DownlinkRequest request, LwM2MClient lwM2MClient) { - executorService.submit(new Runnable() { - @Override - public void run() { + private void handleResponse(Registration registration, final String path, LwM2mResponse response, DownlinkRequest request, LwM2MClient lwM2MClient, boolean isDelayedUpdate) { + executorResponse.submit(() -> { + try { + sendResponse(registration, path, response, request, lwM2MClient, isDelayedUpdate); + } catch (Exception e) { + log.error("[{}] endpoint [{}] path [{}] Exception Unable to after send response.", registration.getEndpoint(), path, e); + } + }); + } - try { - sendResponse(registration, path, response, request, lwM2MClient); - } catch (RuntimeException t) { - log.error("[{}] endpoint [{}] path [{}] error Unable to after send response.", registration.getEndpoint(), path, t.toString()); - } + private void handleResponseError(Registration registration, final String path, LwM2MClient lwM2MClient, boolean isDelayedUpdate) { + executorResponseError.submit(() -> { + try { + if (isDelayedUpdate) lwM2MClient.onSuccessOrErrorDelayedRequests(path); + } catch (RuntimeException t) { + log.error("[{}] endpoint [{}] path [{}] RuntimeException Unable to after send response.", registration.getEndpoint(), path, t); } }); } @@ -298,7 +335,7 @@ public class LwM2MTransportRequest { * @param response - * @param lwM2MClient - */ - private void sendResponse(Registration registration, String path, LwM2mResponse response, DownlinkRequest request, LwM2MClient lwM2MClient) { + private void sendResponse(Registration registration, String path, LwM2mResponse response, DownlinkRequest request, LwM2MClient lwM2MClient, boolean isDelayedUpdate) { if (response instanceof ObserveResponse) { service.onObservationResponse(registration, path, (ReadResponse) response); } else if (response instanceof CancelObservationResponse) { @@ -329,7 +366,7 @@ public class LwM2MTransportRequest { log.info("[{}] Path [{}] WriteAttributesResponse 8_Send", path, response); } else if (response instanceof WriteResponse) { log.info("[{}] Path [{}] WriteAttributesResponse 9_Send", path, response); - service.onAttributeUpdateOk(registration, path, (WriteRequest) request); + service.onAttributeUpdateOk(registration, path, (WriteRequest) request, isDelayedUpdate); } } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServerConfiguration.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServerConfiguration.java index b9f61c3bfe..388138d8c3 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServerConfiguration.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServerConfiguration.java @@ -20,10 +20,16 @@ import org.eclipse.californium.scandium.config.DtlsConnectorConfig; import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder; import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder; import org.eclipse.leshan.core.node.codec.LwM2mNodeDecoder; +import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.server.californium.LeshanServer; import org.eclipse.leshan.server.californium.LeshanServerBuilder; import org.eclipse.leshan.server.model.LwM2mModelProvider; import org.eclipse.leshan.server.model.VersionedModelProvider; +import org.eclipse.leshan.server.redis.RedisRegistrationStore; +import org.eclipse.leshan.server.redis.RedisSecurityStore; +import org.eclipse.leshan.server.security.DefaultAuthorizer; +import org.eclipse.leshan.server.security.EditableSecurityStore; +import org.eclipse.leshan.server.security.SecurityChecker; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.context.annotation.Bean; @@ -31,11 +37,36 @@ import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; -import org.thingsboard.server.transport.lwm2m.server.secure.LwM2MSetSecurityStoreServer; import org.thingsboard.server.transport.lwm2m.server.secure.LwM2mInMemorySecurityStore; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; -import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.X509; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.util.Pool; + +import java.math.BigInteger; +import java.net.URI; +import java.net.URISyntaxException; +import java.security.AlgorithmParameters; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.KeyStoreException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.security.interfaces.ECPublicKey; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPrivateKeySpec; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.KeySpec; +import java.util.Arrays; + +import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA256; +import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.PSK; import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.RPK; +import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.X509; import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.getCoapConfig; @@ -45,6 +76,8 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandle @Configuration("LwM2MTransportServerConfiguration") @ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' ) || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')") public class LwM2MTransportServerConfiguration { + private PublicKey publicKey; + private PrivateKey privateKey; @Autowired private LwM2MTransportContextServer context; @@ -53,50 +86,257 @@ public class LwM2MTransportServerConfiguration { private LwM2mInMemorySecurityStore lwM2mInMemorySecurityStore; @Primary - @Bean(name = "LeshanServerCert") - public LeshanServer getLeshanServerCert() { - log.info("Starting LwM2M transport ServerCert... PostConstruct"); - return getLeshanServer(this.context.getCtxServer().getServerPortCert(), this.context.getCtxServer().getServerSecurePortCert(), X509); + @Bean(name = "leshanServerPsk") + @ConditionalOnExpression("('${transport.lwm2m.server.secure.start_psk:false}'=='true')") + public LeshanServer getLeshanServerPsk() { + log.info("Starting LwM2M transport ServerPsk... PostConstruct"); + return getLeshanServer(this.context.getCtxServer().getServerPortNoSecPsk(), this.context.getCtxServer().getServerPortPsk(), PSK); } - @Bean(name = "leshanServerNoSecPskRpk") - public LeshanServer getLeshanServerNoSecPskRpk() { - log.info("Starting LwM2M transport ServerNoSecPskRpk... PostConstruct"); - return getLeshanServer(this.context.getCtxServer().getServerPort(), this.context.getCtxServer().getServerSecurePort(), RPK); + @Bean(name = "leshanServerRpk") + @ConditionalOnExpression("('${transport.lwm2m.server.secure.start_rpk:false}'=='true')") + public LeshanServer getLeshanServerRpk() { + log.info("Starting LwM2M transport ServerRpk... PostConstruct"); + return getLeshanServer(this.context.getCtxServer().getServerPortNoSecRpk(), this.context.getCtxServer().getServerPortRpk(), RPK); } - private LeshanServer getLeshanServer(Integer serverPort, Integer serverSecurePort, LwM2MSecurityMode dtlsMode) { + @Bean(name = "leshanServerX509") + @ConditionalOnExpression("('${transport.lwm2m.server.secure.start_x509:false}'=='true')") + public LeshanServer getLeshanServerX509() { + log.info("Starting LwM2M transport ServerX509... PostConstruct"); + return getLeshanServer(this.context.getCtxServer().getServerPortNoSecX509(), this.context.getCtxServer().getServerPortX509(), X509); + } + private LeshanServer getLeshanServer(Integer serverPortNoSec, Integer serverSecurePort, LwM2MSecurityMode dtlsMode) { LeshanServerBuilder builder = new LeshanServerBuilder(); - builder.setLocalAddress(this.context.getCtxServer().getServerHost(), serverPort); + builder.setLocalAddress(this.context.getCtxServer().getServerHost(), serverPortNoSec); builder.setLocalSecureAddress(this.context.getCtxServer().getServerSecureHost(), serverSecurePort); builder.setEncoder(new DefaultLwM2mNodeEncoder()); LwM2mNodeDecoder decoder = new DefaultLwM2mNodeDecoder(); builder.setDecoder(decoder); - builder.setEncoder(new DefaultLwM2mNodeEncoder(new LwM2mValueConverterImpl())); + builder.setEncoder(new DefaultLwM2mNodeEncoder(LwM2mValueConverterImpl.getInstance())); /** Create CoAP Config */ - builder.setCoapConfig(getCoapConfig()); + builder.setCoapConfig(getCoapConfig(serverPortNoSec, serverSecurePort)); /** Define model provider (Create Models )*/ LwM2mModelProvider modelProvider = new VersionedModelProvider(this.context.getCtxServer().getModelsValue()); builder.setObjectModelProvider(modelProvider); + /** Create DTLS security mode + * There can be only one DTLS security mode + */ + this.LwM2MSetSecurityStoreServer(builder, dtlsMode); + /** Create DTLS Config */ DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(); - dtlsConfig.setRecommendedCipherSuitesOnly(this.context.getCtxServer().isSupportDeprecatedCiphersEnable()); + if (dtlsMode==PSK) { + dtlsConfig.setRecommendedCipherSuitesOnly(this.context.getCtxServer().isRecommendedCiphers()); + dtlsConfig.setRecommendedSupportedGroupsOnly(this.context.getCtxServer().isRecommendedSupportedGroups()); + dtlsConfig.setSupportedCipherSuites(TLS_PSK_WITH_AES_128_CBC_SHA256); + } + else { + dtlsConfig.setRecommendedSupportedGroupsOnly(!this.context.getCtxServer().isRecommendedSupportedGroups()); + dtlsConfig.setRecommendedCipherSuitesOnly(!this.context.getCtxServer().isRecommendedCiphers()); + } /** Set DTLS Config */ builder.setDtlsConfig(dtlsConfig); /** Use a magic converter to support bad type send by the UI. */ - builder.setEncoder(new DefaultLwM2mNodeEncoder(new LwM2mValueConverterImpl())); + builder.setEncoder(new DefaultLwM2mNodeEncoder(LwM2mValueConverterImpl.getInstance())); - /** Create DTLS security mode - * There can be only one DTLS security mode - */ - new LwM2MSetSecurityStoreServer(builder, context, lwM2mInMemorySecurityStore, dtlsMode); /** Create LWM2M server */ return builder.build(); } + + private void LwM2MSetSecurityStoreServer(LeshanServerBuilder builder, LwM2MSecurityMode dtlsMode) { + /** Set securityStore with new registrationStore */ + EditableSecurityStore securityStore = lwM2mInMemorySecurityStore; + switch (dtlsMode) { + /** Use PSK only */ + case PSK: + generatePSK_RPK(); + infoParamsPSK(); + break; + /** Use RPK only */ + case RPK: + generatePSK_RPK(); + if (this.publicKey != null && this.publicKey.getEncoded().length > 0 && + this.privateKey != null && this.privateKey.getEncoded().length > 0) { + builder.setPublicKey(this.publicKey); + builder.setPrivateKey(this.privateKey); + infoParamsRPK(); + } + break; + /** Use x509 only */ + case X509: + setServerWithX509Cert(builder); + break; + /** No security */ + case NO_SEC: + builder.setTrustedCertificates(new X509Certificate[0]); + break; + /** Use x509 with EST */ + case X509_EST: + // TODO support sentinel pool and make pool configurable + break; + case REDIS: + /** + * Set securityStore with new registrationStore (if use redis store) + * Connect to redis + */ + Pool jedis = null; + try { + jedis = new JedisPool(new URI(this.context.getCtxServer().getRedisUrl())); + securityStore = new RedisSecurityStore(jedis); + builder.setRegistrationStore(new RedisRegistrationStore(jedis)); + } catch (URISyntaxException e) { + log.error("", e); + } + break; + default: + } + + /** Set securityStore with registrationStore (if x509)*/ + if (dtlsMode == X509) { + builder.setAuthorizer(new DefaultAuthorizer(securityStore, new SecurityChecker() { + @Override + protected boolean matchX509Identity(String endpoint, String receivedX509CommonName, + String expectedX509CommonName) { + return endpoint.startsWith(expectedX509CommonName); + } + })); + } + + /** Set securityStore with new registrationStore */ + builder.setSecurityStore(securityStore); + } + + private void generatePSK_RPK() { + try { + /** Get Elliptic Curve Parameter spec for secp256r1 */ + AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC"); + algoParameters.init(new ECGenParameterSpec("secp256r1")); + ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class); + if (this.context.getCtxServer().getServerPublicX() != null && !this.context.getCtxServer().getServerPublicX().isEmpty() && this.context.getCtxServer().getServerPublicY() != null && !this.context.getCtxServer().getServerPublicY().isEmpty()) { + /** Get point values */ + byte[] publicX = Hex.decodeHex(this.context.getCtxServer().getServerPublicX().toCharArray()); + byte[] publicY = Hex.decodeHex(this.context.getCtxServer().getServerPublicY().toCharArray()); + /** Create key specs */ + KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)), + parameterSpec); + /** Get keys */ + this.publicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec); + } + if (this.context.getCtxServer().getServerPrivateS() != null && !this.context.getCtxServer().getServerPrivateS().isEmpty()) { + /** Get point values */ + byte[] privateS = Hex.decodeHex(this.context.getCtxServer().getServerPrivateS().toCharArray()); + /** Create key specs */ + KeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(privateS), parameterSpec); + /** Get keys */ + this.privateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec); + } + } catch (GeneralSecurityException | IllegalArgumentException e) { + log.error("[{}] Failed generate Server PSK/RPK", e.getMessage()); + throw new RuntimeException(e); + } + } + + private void infoParamsPSK() { + log.info("\nServer uses PSK -> private key : \n security key : [{}] \n serverSecureURI : [{}]", + Hex.encodeHexString(this.privateKey.getEncoded()), + this.context.getCtxServer().getServerSecureHost() + ":" + Integer.toString(this.context.getCtxServer().getServerPortPsk())); + } + + private void infoParamsRPK() { + if (this.publicKey instanceof ECPublicKey) { + /** Get x coordinate */ + byte[] x = ((ECPublicKey) this.publicKey).getW().getAffineX().toByteArray(); + if (x[0] == 0) + x = Arrays.copyOfRange(x, 1, x.length); + + /** Get Y coordinate */ + byte[] y = ((ECPublicKey) this.publicKey).getW().getAffineY().toByteArray(); + if (y[0] == 0) + y = Arrays.copyOfRange(y, 1, y.length); + + /** Get Curves params */ + String params = ((ECPublicKey) this.publicKey).getParams().toString(); + log.info( + " \nServer uses RPK : \n Elliptic Curve parameters : [{}] \n Public x coord : [{}] \n Public y coord : [{}] \n Public Key (Hex): [{}] \n Private Key (Hex): [{}]", + params, Hex.encodeHexString(x), Hex.encodeHexString(y), + Hex.encodeHexString(this.publicKey.getEncoded()), + Hex.encodeHexString(this.privateKey.getEncoded())); + } else { + throw new IllegalStateException("Unsupported Public Key Format (only ECPublicKey supported)."); + } + } + + + private void setServerWithX509Cert(LeshanServerBuilder builder) { + try { + if (this.context.getCtxServer().getKeyStoreValue() != null) { + setBuilderX509(builder); + X509Certificate rootCAX509Cert = (X509Certificate) this.context.getCtxServer().getKeyStoreValue().getCertificate(this.context.getCtxServer().getRootAlias()); + if (rootCAX509Cert != null) { + X509Certificate[] trustedCertificates = new X509Certificate[1]; + trustedCertificates[0] = rootCAX509Cert; + builder.setTrustedCertificates(trustedCertificates); + } else { + /** by default trust all */ + builder.setTrustedCertificates(new X509Certificate[0]); + } + } else { + /** by default trust all */ + builder.setTrustedCertificates(new X509Certificate[0]); + log.error("Unable to load X509 files for LWM2MServer"); + } + } catch (KeyStoreException ex) { + log.error("[{}] Unable to load X509 files server", ex.getMessage()); + } + } + + private void setBuilderX509(LeshanServerBuilder builder) { + /** + * For deb => KeyStorePathFile == yml or commandline: KEY_STORE_PATH_FILE + * For idea => KeyStorePathResource == common/transport/lwm2m/src/main/resources/credentials: in LwM2MTransportContextServer: credentials/serverKeyStore.jks + */ + try { + X509Certificate serverCertificate = (X509Certificate) this.context.getCtxServer().getKeyStoreValue().getCertificate(this.context.getCtxServer().getServerAlias()); + PrivateKey privateKey = (PrivateKey) this.context.getCtxServer().getKeyStoreValue().getKey(this.context.getCtxServer().getServerAlias(), this.context.getCtxServer().getKeyStorePasswordServer() == null ? null : this.context.getCtxServer().getKeyStorePasswordServer().toCharArray()); + builder.setPrivateKey(privateKey); + builder.setCertificateChain(new X509Certificate[]{serverCertificate}); + this.infoParamsX509(serverCertificate, privateKey); + } catch (Exception ex) { + log.error("[{}] Unable to load KeyStore files server", ex.getMessage()); + } +// /** +// * For deb => KeyStorePathFile == yml or commandline: KEY_STORE_PATH_FILE +// * For idea => KeyStorePathResource == common/transport/lwm2m/src/main/resources/credentials: in LwM2MTransportContextServer: credentials/serverKeyStore.jks +// */ +// try { +// X509Certificate serverCertificate = (X509Certificate) this.context.getCtxServer().getKeyStoreValue().getCertificate(this.context.getCtxServer().getServerPrivateS()); +// this.privateKey = (PrivateKey) this.context.getCtxServer().getKeyStoreValue().getKey(this.context.getCtxServer().getServerAlias(), this.context.getCtxServer().getKeyStorePasswordServer() == null ? null : this.context.getCtxServer().getKeyStorePasswordServer().toCharArray()); +// if (this.privateKey != null && this.privateKey.getEncoded().length > 0) { +// builder.setPrivateKey(this.privateKey); +// } +// if (serverCertificate != null) { +// builder.setCertificateChain(new X509Certificate[]{serverCertificate}); +// this.infoParamsX509(serverCertificate); +// } +// } catch (Exception ex) { +// log.error("[{}] Unable to load KeyStore files server", ex.getMessage()); +// } + } + + private void infoParamsX509(X509Certificate certificate, PrivateKey privateKey) { + try { + log.info("Server uses X509 : \n X509 Certificate (Hex): [{}] \n Private Key (Hex): [{}]", + Hex.encodeHexString(certificate.getEncoded()), + Hex.encodeHexString(privateKey.getEncoded())); + } catch (CertificateEncodingException e) { + log.error("", e); + } + } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServerInitializer.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServerInitializer.java index e6ada21360..90215326b4 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServerInitializer.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServerInitializer.java @@ -20,24 +20,32 @@ import org.eclipse.leshan.server.californium.LeshanServer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; import org.thingsboard.server.transport.lwm2m.secure.LWM2MGenerationPSkRPkECC; -import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; + import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @Slf4j -@Service("LwM2MTransportServerInitializer") +@Component("LwM2MTransportServerInitializer") @ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' ) || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')") public class LwM2MTransportServerInitializer { - @Autowired - @Qualifier("LeshanServerCert") - private LeshanServer lhServerCert; @Autowired - @Qualifier("leshanServerNoSecPskRpk") - private LeshanServer lhServerNoSecPskRpk; + private LwM2MTransportServiceImpl service; + + @Autowired(required = false) + @Qualifier("leshanServerX509") + private LeshanServer lhServerX509; + + @Autowired(required = false) + @Qualifier("leshanServerPsk") + private LeshanServer lhServerPsk; + + @Autowired(required = false) + @Qualifier("leshanServerRpk") + private LeshanServer lhServerRpk; @Autowired private LwM2MTransportContextServer context; @@ -45,28 +53,47 @@ public class LwM2MTransportServerInitializer { @PostConstruct public void init() { if (this.context.getCtxServer().getEnableGenPskRpk()) new LWM2MGenerationPSkRPkECC(); - if (this.context.getCtxServer().isServerStartAll()) { - this.lhServerCert.start(); - this.lhServerNoSecPskRpk.start(); + if (this.context.getCtxServer().isServerStartPsk()) { + this.startLhServerPsk(); + } + if (this.context.getCtxServer().isServerStartRpk()) { + this.startLhServerRpk(); } - else { - if (this.context.getCtxServer().getServerDtlsMode() == LwM2MSecurityMode.X509.code) { - this.lhServerCert.start(); - } - else { - this.lhServerNoSecPskRpk.start(); - } + if (this.context.getCtxServer().isServerStartX509()) { + this.startLhServerX509(); } } + private void startLhServerPsk() { + this.lhServerPsk.start(); + LwM2mServerListener lhServerPskListener = new LwM2mServerListener(this.lhServerPsk, service); + this.lhServerPsk.getRegistrationService().addListener(lhServerPskListener.registrationListener); + this.lhServerPsk.getPresenceService().addListener(lhServerPskListener.presenceListener); + this.lhServerPsk.getObservationService().addListener(lhServerPskListener.observationListener); + } + + private void startLhServerRpk() { + this.lhServerRpk.start(); + LwM2mServerListener lhServerRpkListener = new LwM2mServerListener(this.lhServerRpk, service); + this.lhServerRpk.getRegistrationService().addListener(lhServerRpkListener.registrationListener); + this.lhServerRpk.getPresenceService().addListener(lhServerRpkListener.presenceListener); + this.lhServerRpk.getObservationService().addListener(lhServerRpkListener.observationListener); + } + + private void startLhServerX509() { + this.lhServerX509.start(); + LwM2mServerListener lhServerCertListener = new LwM2mServerListener(this.lhServerX509, service); + this.lhServerX509.getRegistrationService().addListener(lhServerCertListener.registrationListener); + this.lhServerX509.getPresenceService().addListener(lhServerCertListener.presenceListener); + this.lhServerX509.getObservationService().addListener(lhServerCertListener.observationListener); + } + @PreDestroy public void shutdown() { log.info("Stopping LwM2M transport Server!"); - try { - lhServerCert.destroy(); - lhServerNoSecPskRpk.destroy(); - } finally { - } + lhServerPsk.destroy(); + lhServerRpk.destroy(); + lhServerX509.destroy(); log.info("LwM2M transport Server stopped!"); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportService.java index efadbd0901..8abfcc6a1d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportService.java @@ -15,985 +15,42 @@ */ package org.thingsboard.server.transport.lwm2m.server; -import com.google.gson.*; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import org.eclipse.leshan.core.model.ResourceModel; -import org.eclipse.leshan.core.node.LwM2mMultipleResource; -import org.eclipse.leshan.core.node.LwM2mObject; -import org.eclipse.leshan.core.node.LwM2mObjectInstance; -import org.eclipse.leshan.core.node.LwM2mSingleResource; -import org.eclipse.leshan.core.node.LwM2mResource; -import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.observation.Observation; -import org.eclipse.leshan.core.request.ContentFormat; -import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.response.ReadResponse; -import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.server.californium.LeshanServer; import org.eclipse.leshan.server.registration.Registration; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.transport.TransportService; -import org.thingsboard.server.common.transport.TransportServiceCallback; -import org.thingsboard.server.common.transport.adaptor.AdaptorException; -import org.thingsboard.server.common.transport.adaptor.JsonConverter; -import org.thingsboard.server.common.transport.service.DefaultTransportService; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.gen.transport.TransportProtos.ToTransportUpdateCredentialsProto; -import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent; -import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; -import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; -import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; -import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; -import org.thingsboard.server.transport.lwm2m.server.adaptors.LwM2MJsonAdaptor; -import org.thingsboard.server.transport.lwm2m.server.client.AttrTelemetryObserveValue; -import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClient; -import org.thingsboard.server.transport.lwm2m.server.client.ModelObject; -import org.thingsboard.server.transport.lwm2m.server.client.ResultsAnalyzerParameters; -import org.thingsboard.server.transport.lwm2m.server.client.ResourceValue; -import org.thingsboard.server.transport.lwm2m.server.secure.LwM2mInMemorySecurityStore; -import javax.annotation.PostConstruct; import java.util.Collection; -import java.util.UUID; -import java.util.Random; -import java.util.Map; -import java.util.HashMap; -import java.util.Arrays; -import java.util.Set; -import java.util.HashSet; -import java.util.NoSuchElementException; import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Predicate; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import static org.eclipse.leshan.core.model.ResourceModel.Type.OPAQUE; -import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.*; +public interface LwM2MTransportService { -@Slf4j -@Service("LwM2MTransportService") -@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' ) || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')") -public class LwM2MTransportService { + void onRegistered(LeshanServer lwServer, Registration registration, Collection previousObsersations); - @Autowired - private LwM2MJsonAdaptor adaptor; + void updatedReg(LeshanServer lwServer, Registration registration); - @Autowired - private TransportService transportService; + void unReg(LeshanServer lwServer, Registration registration, Collection observations); - @Autowired - public LwM2MTransportContextServer context; + void onSleepingDev(Registration registration); - @Autowired - private LwM2MTransportRequest lwM2MTransportRequest; + void setCancelObservations(LeshanServer lwServer, Registration registration); - @Autowired - LwM2mInMemorySecurityStore lwM2mInMemorySecurityStore; + void setCancelObservationRecourse(LeshanServer lwServer, Registration registration, String path); + void onObservationResponse(Registration registration, String path, ReadResponse response); - @PostConstruct - public void init() { - context.getScheduler().scheduleAtFixedRate(() -> checkInactivityAndReportActivity(), new Random().nextInt((int) context.getCtxServer().getSessionReportTimeout()), context.getCtxServer().getSessionReportTimeout(), TimeUnit.MILLISECONDS); - } + void onAttributeUpdate(TransportProtos.AttributeUpdateNotificationMsg msg, TransportProtos.SessionInfoProto sessionInfo); - /** - * Start registration device - * Create session: Map, LwM2MClient> - * 1. replaceNewRegistration -> (solving the problem of incorrect termination of the previous session with this endpoint) - * 1.1 When we initialize the registration, we register the session by endpoint. - * 1.2 If the server has incomplete requests (canceling the registration of the previous session), - * delete the previous session only by the previous registration.getId - * 1.2 Add Model (Entity) for client (from registration & observe) by registration.getId - * 1.2 Remove from sessions Model by enpPoint - * Next -> Create new LwM2MClient for current session -> setModelClient... - * - * @param lwServer - LeshanServer - * @param registration - Registration LwM2M Client - * @param previousObsersations - may be null - */ - public void onRegistered(LeshanServer lwServer, Registration registration, Collection previousObsersations) { - LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getlwM2MClient(lwServer, registration); - if (lwM2MClient != null) { - lwM2MClient.setLwM2MTransportService(this); - lwM2MClient.setLwM2MTransportService(this); - lwM2MClient.setSessionUuid(UUID.randomUUID()); - this.setLwM2MClient(lwServer, registration, lwM2MClient); - SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration.getId()); - if (sessionInfo != null) { - lwM2MClient.setDeviceUuid(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); - lwM2MClient.setProfileUuid(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); - lwM2MClient.setDeviceName(sessionInfo.getDeviceName()); - lwM2MClient.setDeviceProfileName(sessionInfo.getDeviceType()); - transportService.registerAsyncSession(sessionInfo, new LwM2MSessionMsgListener(this, sessionInfo)); - transportService.process(sessionInfo, DefaultTransportService.getSessionEventMsg(SessionEvent.OPEN), null); - transportService.process(sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().build(), null); - this.sentLogsToThingsboard(LOG_LW2M_INFO + ": Client registration", registration.getId()); - } else { - log.error("Client: [{}] onRegistered [{}] name [{}] sessionInfo ", registration.getId(), registration.getEndpoint(), sessionInfo); - } - } else { - log.error("Client: [{}] onRegistered [{}] name [{}] lwM2MClient ", registration.getId(), registration.getEndpoint(), lwM2MClient); - } - } + void onDeviceProfileUpdate(TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile); - /** - * @param lwServer - LeshanServer - * @param registration - Registration LwM2M Client - */ - public void updatedReg(LeshanServer lwServer, Registration registration) { - SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration.getId()); - if (sessionInfo != null) { - log.info("Client: [{}] updatedReg [{}] name [{}] profile ", registration.getId(), registration.getEndpoint(), sessionInfo.getDeviceType()); - } else { - log.error("Client: [{}] updatedReg [{}] name [{}] sessionInfo ", registration.getId(), registration.getEndpoint(), sessionInfo); - } - } + void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional deviceProfileOpt); - /** - * @param registration - Registration LwM2M Client - * @param observations - All paths observations before unReg - * !!! Warn: if have not finishing unReg, then this operation will be finished on next Client`s connect - */ - public void unReg(Registration registration, Collection observations) { - this.sentLogsToThingsboard(LOG_LW2M_INFO + ": Client unRegistration", registration.getId()); - this.closeClientSession(registration); - } + void doTrigger(LeshanServer lwServer, Registration registration, String path); - private void closeClientSession(Registration registration) { - SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration.getId()); - if (sessionInfo != null) { - transportService.deregisterSession(sessionInfo); - this.doCloseSession(sessionInfo); - lwM2mInMemorySecurityStore.delRemoveSessionAndListener(registration.getId()); - if (lwM2mInMemorySecurityStore.getProfiles().size() > 0) { - this.syncSessionsAndProfiles(); - } - log.info("Client: [{}] unReg [{}] name [{}] profile ", registration.getId(), registration.getEndpoint(), sessionInfo.getDeviceType()); - } else { - log.error("Client: [{}] unReg [{}] name [{}] sessionInfo ", registration.getId(), registration.getEndpoint(), sessionInfo); - } - } + void doDisconnect(TransportProtos.SessionInfoProto sessionInfo); - public void onSleepingDev(Registration registration) { - log.info("[{}] [{}] Received endpoint Sleeping version event", registration.getId(), registration.getEndpoint()); - //TODO: associate endpointId with device information. - } - - /** - * Those methods are called by the protocol stage thread pool, this means that execution MUST be done in a short delay, - * * if you need to do long time processing use a dedicated thread pool. - * - * @param registration - */ - - public void onAwakeDev(Registration registration) { - log.info("[{}] [{}] Received endpoint Awake version event", registration.getId(), registration.getEndpoint()); - //TODO: associate endpointId with device information. - } - - /** - * This method is used to sync with sessions - * Removes a profile if not used in sessions - */ - private void syncSessionsAndProfiles() { - Map profilesClone = lwM2mInMemorySecurityStore.getProfiles().entrySet() - .stream() - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - profilesClone.forEach((k, v) -> { - String registrationId = lwM2mInMemorySecurityStore.getSessions().entrySet() - .stream() - .filter(e -> e.getValue().getProfileUuid().equals(k)) - .findFirst() - .map(Map.Entry::getKey) // return the key of the matching entry if found - .orElse(""); - if (registrationId.isEmpty()) { - lwM2mInMemorySecurityStore.getProfiles().remove(k); - } - }); - } - - /** - * Create new LwM2MClient for current session -> setModelClient... - * #1 Add all ObjectLinks (instance) to control the process of executing requests to the client - * to get the client model with current values - * #2 Get the client model with current values. Analyze the response in -> lwM2MTransportRequest.sendResponse - * - * @param lwServer - LeshanServer - * @param registration - Registration LwM2M Client - * @param lwM2MClient - object with All parameters off client - */ - private void setLwM2MClient(LeshanServer lwServer, Registration registration, LwM2MClient lwM2MClient) { - // #1 - Arrays.stream(registration.getObjectLinks()).forEach(url -> { - ResultIds pathIds = new ResultIds(url.getUrl()); - if (pathIds.instanceId > -1 && pathIds.resourceId == -1) { - lwM2MClient.getPendingRequests().add(url.getUrl()); - } - }); - // #2 - Arrays.stream(registration.getObjectLinks()).forEach(url -> { - ResultIds pathIds = new ResultIds(url.getUrl()); - if (pathIds.instanceId > -1 && pathIds.resourceId == -1) { - lwM2MTransportRequest.sendAllRequest(lwServer, registration, url.getUrl(), GET_TYPE_OPER_READ, - ContentFormat.TLV.getName(), lwM2MClient, null, null, this.context.getCtxServer().getTimeout()); - } - }); - } - - /** - * @param registrationId - Id of Registration LwM2M Client - * @return - sessionInfo after access connect client - */ - private SessionInfoProto getValidateSessionInfo(String registrationId) { - SessionInfoProto sessionInfo = null; - LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getlwM2MClient(registrationId); - if (lwM2MClient != null) { - ValidateDeviceCredentialsResponseMsg msg = lwM2MClient.getCredentialsResponse(); - if (msg == null || msg.getDeviceInfo() == null) { - log.error("[{}] [{}]", lwM2MClient.getEndPoint(), CLIENT_NOT_AUTHORIZED); - this.closeClientSession(lwM2MClient.getRegistration()); - } else { - sessionInfo = SessionInfoProto.newBuilder() - .setNodeId(this.context.getNodeId()) - .setSessionIdMSB(lwM2MClient.getSessionUuid().getMostSignificantBits()) - .setSessionIdLSB(lwM2MClient.getSessionUuid().getLeastSignificantBits()) - .setDeviceIdMSB(msg.getDeviceInfo().getDeviceIdMSB()) - .setDeviceIdLSB(msg.getDeviceInfo().getDeviceIdLSB()) - .setTenantIdMSB(msg.getDeviceInfo().getTenantIdMSB()) - .setTenantIdLSB(msg.getDeviceInfo().getTenantIdLSB()) - .setDeviceName(msg.getDeviceInfo().getDeviceName()) - .setDeviceType(msg.getDeviceInfo().getDeviceType()) - .setDeviceProfileIdLSB(msg.getDeviceInfo().getDeviceProfileIdLSB()) - .setDeviceProfileIdMSB(msg.getDeviceInfo().getDeviceProfileIdMSB()) - .build(); - } - } - return sessionInfo; - } - - /** - * Add attribute/telemetry information from Client and credentials/Profile to client model and start observe - * !!! if the resource has an observation, but no telemetry or attribute - the observation will not use - * #1 Client`s starting info to send to thingsboard - * #2 Sending Attribute Telemetry with value to thingsboard only once at the start of the connection - * #3 Start observe - * - * @param lwServer - LeshanServer - * @param registration - Registration LwM2M Client - */ - - public void updatesAndSentModelParameter(LeshanServer lwServer, Registration registration) { - // #1 -// this.setParametersToModelClient(registration, modelClient, deviceProfile); - // #2 - this.updateAttrTelemetry(registration, true, null); - // #3 - this.onSentObserveToClient(lwServer, registration); - } - - - /** - * Sent Attribute and Telemetry to Thingsboard - * #1 - get AttrName/TelemetryName with value: - * #1.1 from Client - * #1.2 from LwM2MClient: - * -- resourceId == path from AttrTelemetryObserveValue.postAttributeProfile/postTelemetryProfile/postObserveProfile - * -- AttrName/TelemetryName == resourceName from ModelObject.objectModel, value from ModelObject.instance.resource(resourceId) - * #2 - set Attribute/Telemetry - * - * @param registration - Registration LwM2M Client - */ - private void updateAttrTelemetry(Registration registration, boolean start, Set paths) { - JsonObject attributes = new JsonObject(); - JsonObject telemetrys = new JsonObject(); - if (start) { - // #1.1 - JsonObject attributeClient = this.getAttributeClient(registration); - if (attributeClient != null) { - attributeClient.entrySet().forEach(p -> { - attributes.add(p.getKey(), p.getValue()); - }); - } - } - // #1.2 - CountDownLatch cancelLatch = new CountDownLatch(1); - this.getParametersFromProfile(attributes, telemetrys, registration, paths); - cancelLatch.countDown(); - try { - cancelLatch.await(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - log.error("[{}] updateAttrTelemetry", e.toString()); - } - if (attributes.getAsJsonObject().entrySet().size() > 0) - this.updateParametersOnThingsboard(attributes, DEVICE_ATTRIBUTES_TOPIC, registration.getId()); - if (telemetrys.getAsJsonObject().entrySet().size() > 0) - this.updateParametersOnThingsboard(telemetrys, DEVICE_TELEMETRY_TOPIC, registration.getId()); - } - - /** - * get AttrName/TelemetryName with value from Client - * - * @param registration - - * @return - JsonObject, format: {name: value}} - */ - private JsonObject getAttributeClient(Registration registration) { - if (registration.getAdditionalRegistrationAttributes().size() > 0) { - JsonObject resNameValues = new JsonObject(); - registration.getAdditionalRegistrationAttributes().entrySet().forEach(entry -> { - resNameValues.addProperty(entry.getKey(), entry.getValue()); - }); - return resNameValues; - } - return null; - } - - /** - * @param attributes - new JsonObject - * @param telemetry - new JsonObject - * @param registration - Registration LwM2M Client - * result: add to JsonObject those resources to which the user is subscribed and they have a value - * if path==null add All resources else only one - * (attributes/telemetry): new {name(Attr/Telemetry):value} - */ - private void getParametersFromProfile(JsonObject attributes, JsonObject telemetry, Registration registration, Set path) { - AttrTelemetryObserveValue attrTelemetryObserveValue = lwM2mInMemorySecurityStore.getProfiles().get(lwM2mInMemorySecurityStore.getSessions().get(registration.getId()).getProfileUuid()); - attrTelemetryObserveValue.getPostAttributeProfile().forEach(p -> { - ResultIds pathIds = new ResultIds(p.getAsString().toString()); - if (pathIds.getResourceId() > -1) { - if (path == null || path.contains(p.getAsString())) { - this.addParameters(pathIds, p.getAsString().toString(), attributes, registration); - } - } - }); - attrTelemetryObserveValue.getPostTelemetryProfile().forEach(p -> { - ResultIds pathIds = new ResultIds(p.getAsString().toString()); - if (pathIds.getResourceId() > -1) { - if (path == null || path.contains(p.getAsString())) { - this.addParameters(pathIds, p.getAsString().toString(), telemetry, registration); - } - } - }); - } - - /** - * @param pathIds - path resource - * @param parameters - JsonObject attributes/telemetry - * @param registration - Registration LwM2M Client - */ - private void addParameters(ResultIds pathIds, String path, JsonObject parameters, Registration registration) { - ModelObject modelObject = lwM2mInMemorySecurityStore.getSessions().get(registration.getId()).getModelObjects().get(pathIds.getObjectId()); - JsonObject names = lwM2mInMemorySecurityStore.getProfiles().get(lwM2mInMemorySecurityStore.getSessions().get(registration.getId()).getProfileUuid()).getPostKeyNameProfile(); - String resName = String.valueOf(names.get(path)); - if (modelObject != null && resName != null && !resName.isEmpty()) { - String resValue = this.getResourceValue(modelObject, pathIds); - if (resValue != null) { - parameters.addProperty(resName, resValue); - } - } - } - - /** - * @param modelObject - ModelObject of Client - * @param pathIds - path resource - * @return - value of Resource or null - */ - private String getResourceValue(ModelObject modelObject, ResultIds pathIds) { - String resValue = null; - if (modelObject.getInstances().get(pathIds.getInstanceId()) != null) { - LwM2mObjectInstance instance = modelObject.getInstances().get(pathIds.getInstanceId()); - if (instance.getResource(pathIds.getResourceId()) != null) { - resValue = instance.getResource(pathIds.getResourceId()).getType() == OPAQUE ? - Hex.encodeHexString((byte[]) instance.getResource(pathIds.getResourceId()).getValue()).toLowerCase() : - (instance.getResource(pathIds.getResourceId()).isMultiInstances()) ? - instance.getResource(pathIds.getResourceId()).getValues().toString() : - instance.getResource(pathIds.getResourceId()).getValue().toString(); - } - } - return resValue; - } - - /** - * Prepare Sent to Thigsboard callback - Attribute or Telemetry - * - * @param msg - JsonArray: [{name: value}] - * @param topicName - Api Attribute or Telemetry - * @param registrationId - Id of Registration LwM2M Client - */ - public void updateParametersOnThingsboard(JsonElement msg, String topicName, String registrationId) { - SessionInfoProto sessionInfo = this.getValidateSessionInfo(registrationId); - if (sessionInfo != null) { - try { - if (topicName.equals(LwM2MTransportHandler.DEVICE_ATTRIBUTES_TOPIC)) { - PostAttributeMsg postAttributeMsg = adaptor.convertToPostAttributes(msg); - TransportServiceCallback call = this.getPubAckCallbackSentAttrTelemetry(-1, postAttributeMsg); - transportService.process(sessionInfo, postAttributeMsg, call); - } else if (topicName.equals(LwM2MTransportHandler.DEVICE_TELEMETRY_TOPIC)) { - PostTelemetryMsg postTelemetryMsg = adaptor.convertToPostTelemetry(msg); - TransportServiceCallback call = this.getPubAckCallbackSentAttrTelemetry(-1, postTelemetryMsg); - transportService.process(sessionInfo, postTelemetryMsg, this.getPubAckCallbackSentAttrTelemetry(-1, call)); - } - } catch (AdaptorException e) { - log.error("[{}] Failed to process publish msg [{}]", topicName, e); - log.info("[{}] Closing current session due to invalid publish", topicName); - } - } else { - log.error("Client: [{}] updateParametersOnThingsboard [{}] sessionInfo ", registrationId, sessionInfo); - } - } - - /** - * Sent to Thingsboard Attribute || Telemetry - * - * @param msgId - always == -1 - * @param msg - JsonObject: [{name: value}] - * @return - dummy - */ - private TransportServiceCallback getPubAckCallbackSentAttrTelemetry(final int msgId, final T msg) { - return new TransportServiceCallback() { - @Override - public void onSuccess(Void dummy) { - log.trace("Success to publish msg: {}, dummy: {}", msg, dummy); - } - - @Override - public void onError(Throwable e) { - log.trace("[{}] Failed to publish msg: {}", msg, e); - } - }; - } - - - /** - * Start observe - * #1 - Analyze: - * #1.1 path in observe == (attribute or telemetry) - * #1.2 recourseValue notNull - * #2 Analyze after sent request (response): - * #2.1 First: lwM2MTransportRequest.sendResponse -> ObservationListener.newObservation - * #2.2 Next: ObservationListener.onResponse * - * - * @param lwServer - LeshanServer - * @param registration - Registration LwM2M Client - */ - private void onSentObserveToClient(LeshanServer lwServer, Registration registration) { - if (lwServer.getObservationService().getObservations(registration).size() > 0) { - this.setCancelObservations(lwServer, registration); - } - UUID profileUUid = lwM2mInMemorySecurityStore.getSessions().get(registration.getId()).getProfileUuid(); - AttrTelemetryObserveValue attrTelemetryObserveValue = lwM2mInMemorySecurityStore.getProfiles().get(profileUUid); - attrTelemetryObserveValue.getPostObserveProfile().forEach(p -> { - // #1.1 - String target = (getValidateObserve(attrTelemetryObserveValue.getPostAttributeProfile(), p.getAsString().toString())) ? - p.getAsString().toString() : (getValidateObserve(attrTelemetryObserveValue.getPostTelemetryProfile(), p.getAsString().toString())) ? - p.getAsString().toString() : null; - if (target != null) { - // #1.2 - ResultIds pathIds = new ResultIds(target); - ModelObject modelObject = lwM2mInMemorySecurityStore.getSessions().get(registration.getId()).getModelObjects().get(pathIds.getObjectId()); - // #2 - if (modelObject != null) { - if (getResourceValue(modelObject, pathIds) != null) { - lwM2MTransportRequest.sendAllRequest(lwServer, registration, target, GET_TYPE_OPER_OBSERVE, - null, null, null, null, this.context.getCtxServer().getTimeout()); - } - } - } - }); - } - - public void setCancelObservations(LeshanServer lwServer, Registration registration) { - if (registration != null) { - Set observations = lwServer.getObservationService().getObservations(registration); - observations.forEach(observation -> { - this.setCancelObservationRecourse(lwServer, registration, observation.getPath().toString()); - }); - } - } - - /** - * lwM2MTransportRequest.sendAllRequest(lwServer, registration, path, POST_TYPE_OPER_OBSERVE_CANCEL, null, null, null, null, context.getTimeout()); - * At server side this will not remove the observation from the observation store, to do it you need to use - * {@code ObservationService#cancelObservation()} - */ - public void setCancelObservationRecourse(LeshanServer lwServer, Registration registration, String path) { - CountDownLatch cancelLatch = new CountDownLatch(1); - lwServer.getObservationService().cancelObservations(registration, path); - cancelLatch.countDown(); - try { - cancelLatch.await(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - } - } - - /** - * @param parameters - JsonArray postAttributeProfile/postTelemetryProfile - * @param path - recourse from postObserveProfile - * @return rez - true if path observe is in attribute/telemetry - */ - private boolean getValidateObserve(JsonElement parameters, String path) { - AtomicBoolean rez = new AtomicBoolean(false); - if (parameters.isJsonArray()) { - parameters.getAsJsonArray().forEach(p -> { - if (p.getAsString().toString().equals(path)) rez.set(true); - } - ); - } else if (parameters.isJsonObject()) { - rez.set((parameters.getAsJsonObject().entrySet()).stream().map(json -> json.toString()) - .filter(path::equals).findAny().orElse(null) != null); - } - return rez.get(); - } - - /** - * Sending observe value to thingsboard from ObservationListener.onResponse: object, instance, SingleResource or MultipleResource - * - * @param registration - Registration LwM2M Client - * @param path - observe - * @param response - observe - */ - @SneakyThrows - public void onObservationResponse(Registration registration, String path, ReadResponse response) { - if (response.getContent() != null) { - if (response.getContent() instanceof LwM2mObject) { - LwM2mObject content = (LwM2mObject) response.getContent(); - String target = "/" + content.getId(); - } else if (response.getContent() instanceof LwM2mObjectInstance) { - LwM2mObjectInstance content = (LwM2mObjectInstance) response.getContent(); - } else if (response.getContent() instanceof LwM2mSingleResource) { - LwM2mSingleResource content = (LwM2mSingleResource) response.getContent(); - this.onObservationSetResourcesValue(registration, content.getValue(), null, path); - } else if (response.getContent() instanceof LwM2mMultipleResource) { - LwM2mSingleResource content = (LwM2mSingleResource) response.getContent(); - this.onObservationSetResourcesValue(registration, null, content.getValues(), path); - } - } - } - - /** - * Sending observe value of resources to thingsboard - * #1 Return old Resource from ModelObject - * #2 Create new Resource with value from observation - * #3 Create new Resources from old Resources - * #4 Update new Resources (replace old Resource on new Resource) - * #5 Remove old Instance from modelClient - * #6 Create new Instance with new Resources values - * #7 Update modelClient.getModelObjects(idObject) (replace old Instance on new Instance) - * - * @param registration - Registration LwM2M Client - * @param value - LwM2mSingleResource response.getContent() - * @param values - LwM2mSingleResource response.getContent() - * @param path - resource - */ - private void onObservationSetResourcesValue(Registration registration, Object value, Map values, String path) { - ResultIds resultIds = new ResultIds(path); - // #1 - LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getlwM2MClient(registration.getId()); - ModelObject modelObject = lwM2MClient.getModelObjects().get(resultIds.getObjectId()); - Map instancesModelObject = modelObject.getInstances(); - LwM2mObjectInstance instanceOld = (instancesModelObject.get(resultIds.instanceId) != null) ? instancesModelObject.get(resultIds.instanceId) : null; - Map resourcesOld = (instanceOld != null) ? instanceOld.getResources() : null; - LwM2mResource resourceOld = (resourcesOld != null && resourcesOld.get(resultIds.getResourceId()) != null) ? resourcesOld.get(resultIds.getResourceId()) : null; - // #2 - LwM2mResource resourceNew; - if (resourceOld.isMultiInstances()) { - resourceNew = LwM2mMultipleResource.newResource(resultIds.getResourceId(), values, resourceOld.getType()); - } else { - resourceNew = LwM2mSingleResource.newResource(resultIds.getResourceId(), value, resourceOld.getType()); - } - //#3 - Map resourcesNew = new HashMap<>(resourcesOld); - // #4 - resourcesNew.remove(resourceOld); - // #5 - resourcesNew.put(resultIds.getResourceId(), resourceNew); - // #6 - LwM2mObjectInstance instanceNew = new LwM2mObjectInstance(resultIds.instanceId, resourcesNew.values()); - // #7 - CountDownLatch respLatch = new CountDownLatch(1); - lwM2MClient.getModelObjects().get(resultIds.getObjectId()).removeInstance(resultIds.instanceId); - instancesModelObject.put(resultIds.instanceId, instanceNew); - respLatch.countDown(); - try { - respLatch.await(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS); - } catch (InterruptedException ex) { - } - Set paths = new HashSet(); - paths.add(path); - this.updateAttrTelemetry(registration, false, paths); - } - - /** - * @param updateCredentials - Credentials include config only security Client (without config attr/telemetry...) - * config attr/telemetry... in profile - */ - public void onToTransportUpdateCredentials(ToTransportUpdateCredentialsProto updateCredentials) { - log.info("[{}] idList [{}] valueList updateCredentials", updateCredentials.getCredentialsIdList(), updateCredentials.getCredentialsValueList()); - } - - /** - * Update - sent request in change value resources in Client (path to resources from profile by keyName) - * Only fo resources W - * Delete - nothing - * - * @param msg - - * @param sessionInfo - - */ - public void onAttributeUpdate(TransportProtos.AttributeUpdateNotificationMsg msg, SessionInfoProto sessionInfo) { - if (msg.getSharedUpdatedCount() > 0) { - JsonElement el = JsonConverter.toJson(msg); - el.getAsJsonObject().entrySet().forEach(de -> { - String profilePath = lwM2mInMemorySecurityStore.getProfiles().get(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())) - .getPostKeyNameProfile().getAsJsonObject().entrySet().stream() - .filter(e -> e.getValue().getAsString().equals(de.getKey())).findFirst().map(Map.Entry::getKey) - .orElse(""); - String path = !profilePath.isEmpty() ? profilePath : this.getPathAttributeUpdate(sessionInfo, de.getKey()); - if (path != null) { - ResultIds resultIds = new ResultIds(path); - LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getSession(new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())).entrySet().iterator().next().getValue(); - ResourceModel.Operations operations = lwM2MClient.getModelObjects().get(resultIds.getObjectId()).getObjectModel().resources.get(resultIds.getResourceId()).operations; - String value = ((JsonPrimitive) de.getValue()).getAsString(); - if (operations.isWritable()) { - lwM2MTransportRequest.sendAllRequest(lwM2MClient.getLwServer(), lwM2MClient.getRegistration(), path, POST_TYPE_OPER_WRITE_REPLACE, - ContentFormat.TLV.getName(), lwM2MClient, null, value, this.context.getCtxServer().getTimeout()); - log.info("[{}] path onAttributeUpdate", path); - } - else { - log.error(LOG_LW2M_ERROR + ": Resource path - [{}] value - [{}] is not Writable and cannot be updated", path, value); - String logMsg = String.format(LOG_LW2M_ERROR + " attributeUpdate: Resource path - %s value - %s is not Writable and cannot be updated", path, value); - this.sentLogsToThingsboard(logMsg, lwM2MClient.getRegistration().getId()); - } - } - }); - } else if (msg.getSharedDeletedCount() > 0) { - log.info("[{}] delete [{}] onAttributeUpdate", msg.getSharedDeletedList(), sessionInfo); - } - } - - private String getPathAttributeUpdate(SessionInfoProto sessionInfo, String keyName) { - try { - LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getSession(new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())).entrySet().iterator().next().getValue(); - Predicate> predicateRes = res -> keyName.equals(splitCamelCaseString(res.getValue().name)); - Predicate> predicateObj = (obj -> { - return obj.getValue().getObjectModel().resources.entrySet().stream().filter(predicateRes).findFirst().isPresent(); - }); - Stream> objectStream = lwM2MClient.getModelObjects().entrySet().stream().filter(predicateObj); - Predicate> predicateResFinal = (objectStream.count() > 0) ? predicateRes : res -> keyName.equals(res.getValue().name); - Predicate> predicateObjFinal = (obj -> { - return obj.getValue().getObjectModel().resources.entrySet().stream().filter(predicateResFinal).findFirst().isPresent(); - }); - Map.Entry object = lwM2MClient.getModelObjects().entrySet().stream().filter(predicateObjFinal).findFirst().get(); - ModelObject modelObject = object.getValue(); - LwM2mObjectInstance instance = modelObject.getInstances().entrySet().stream().findFirst().get().getValue(); - ResourceModel resource = modelObject.getObjectModel().resources.entrySet().stream().filter(predicateResFinal).findFirst().get().getValue(); - return new LwM2mPath(object.getKey(), instance.getId(), resource.id).toString(); - } catch (NoSuchElementException e) { - log.error("[{}] keyName [{}]", keyName, e.toString()); - return null; - } - } - - public void onAttributeUpdateOk(Registration registration, String path, WriteRequest request) { - ResultIds resultIds = new ResultIds(path); - LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getlwM2MClient(registration.getId()); - LwM2mResource resource = lwM2MClient.getModelObjects().get(resultIds.getObjectId()).getInstances().get(resultIds.getInstanceId()).getResource(resultIds.getResourceId()); - if (resource.isMultiInstances()) { - this.onObservationSetResourcesValue(registration, null, ((LwM2mSingleResource) request.getNode()).getValues(), path); - } else { - this.onObservationSetResourcesValue(registration, ((LwM2mSingleResource) request.getNode()).getValue(), null, path); - } - } - - /** - * @param sessionInfo - - * @param deviceProfile - - */ - public void onDeviceProfileUpdate(TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile) { - String registrationId = lwM2mInMemorySecurityStore.getSessions().entrySet() - .stream() - .filter(e -> e.getValue().getDeviceUuid().equals(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB()))) - .findFirst() - .map(Map.Entry::getKey) - .orElse(""); - if (!registrationId.isEmpty()) { - this.onDeviceUpdateChangeProfile(registrationId, deviceProfile); - } - } - - /** - * @param sessionInfo - - * @param device - - * @param deviceProfileOpt - - */ - public void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional deviceProfileOpt) { - Optional registrationIdOpt = lwM2mInMemorySecurityStore.getSessions().entrySet().stream() - .filter(e -> device.getUuidId().equals(e.getValue().getDeviceUuid())) - .map(Map.Entry::getKey) - .findFirst(); - registrationIdOpt.ifPresent(registrationId -> this.onDeviceUpdateLwM2MClient(registrationId, device, deviceProfileOpt)); - } - - /** - * Update parameters device in LwM2MClient - * If new deviceProfile != old deviceProfile => update deviceProfile - * - * @param registrationId - - * @param device - - */ - private void onDeviceUpdateLwM2MClient(String registrationId, Device device, Optional deviceProfileOpt) { - LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getSessions().get(registrationId); - lwM2MClient.setDeviceName(device.getName()); - if (!lwM2MClient.getProfileUuid().equals(device.getDeviceProfileId().getId())) { - deviceProfileOpt.ifPresent(deviceProfile -> this.onDeviceUpdateChangeProfile(registrationId, deviceProfile)); - } - } - - /** - * #1 Read new, old Value (Attribute, Telemetry, Observe, KeyName) - * #2 Update in lwM2MClient: ...Profile - * #3 Equivalence test: old <> new Value (Attribute, Telemetry, Observe, KeyName) - * #3.1 Attribute isChange (add&del) - * #3.2 Telemetry isChange (add&del) - * #3.3 KeyName isChange (add) - * #4 update - * #4.1 add If #3 isChange, then analyze and update Value in Transport form Client and sent Value ti thingsboard - * #4.2 del - * -- if add attributes includes del telemetry - result del for observe - * #5 - * #5.1 Observe isChange (add&del) - * #5.2 Observe.add - * -- path Attr/Telemetry includes newObserve and does not include oldObserve: sent Request observe to Client - * #5.3 Observe.del - * -- different between newObserve and oldObserve: sent Request cancel observe to client - * - * @param registrationId - - * @param deviceProfile - - */ - public void onDeviceUpdateChangeProfile(String registrationId, DeviceProfile deviceProfile) { - LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getlwM2MClient(registrationId); - AttrTelemetryObserveValue attrTelemetryObserveValueOld = lwM2mInMemorySecurityStore.getProfiles().get(lwM2MClient.getProfileUuid()); - if (lwM2mInMemorySecurityStore.addUpdateProfileParameters(deviceProfile)) { - LeshanServer lwServer = lwM2MClient.getLwServer(); - Registration registration = lwM2mInMemorySecurityStore.getByRegistration(registrationId); - // #1 - JsonArray attributeOld = attrTelemetryObserveValueOld.getPostAttributeProfile(); - Set attributeSetOld = new Gson().fromJson(attributeOld, Set.class); - JsonArray telemetryOld = attrTelemetryObserveValueOld.getPostTelemetryProfile(); - Set telemetrySetOld = new Gson().fromJson(telemetryOld, Set.class); - JsonArray observeOld = attrTelemetryObserveValueOld.getPostObserveProfile(); - JsonObject keyNameOld = attrTelemetryObserveValueOld.getPostKeyNameProfile(); - - AttrTelemetryObserveValue attrTelemetryObserveValueNew = lwM2mInMemorySecurityStore.getProfiles().get(deviceProfile.getUuidId()); - JsonArray attributeNew = attrTelemetryObserveValueNew.getPostAttributeProfile(); - Set attributeSetNew = new Gson().fromJson(attributeNew, Set.class); - JsonArray telemetryNew = attrTelemetryObserveValueNew.getPostTelemetryProfile(); - Set telemetrySetNew = new Gson().fromJson(telemetryNew, Set.class); - JsonArray observeNew = attrTelemetryObserveValueNew.getPostObserveProfile(); - JsonObject keyNameNew = attrTelemetryObserveValueNew.getPostKeyNameProfile(); - // #2 - lwM2MClient.setDeviceProfileName(deviceProfile.getName()); - lwM2MClient.setProfileUuid(deviceProfile.getUuidId()); - - // #3 - ResultsAnalyzerParameters sentAttrToThingsboard = new ResultsAnalyzerParameters(); - // #3.1 - if (!attributeOld.equals(attributeNew)) { - ResultsAnalyzerParameters postAttributeAnalyzer = this.getAnalyzerParameters(new Gson().fromJson(attributeOld, Set.class), attributeSetNew); - sentAttrToThingsboard.getPathPostParametersAdd().addAll(postAttributeAnalyzer.getPathPostParametersAdd()); - sentAttrToThingsboard.getPathPostParametersDel().addAll(postAttributeAnalyzer.getPathPostParametersDel()); - } - // #3.2 - if (!attributeOld.equals(attributeNew)) { - ResultsAnalyzerParameters postTelemetryAnalyzer = this.getAnalyzerParameters(new Gson().fromJson(telemetryOld, Set.class), telemetrySetNew); - sentAttrToThingsboard.getPathPostParametersAdd().addAll(postTelemetryAnalyzer.getPathPostParametersAdd()); - sentAttrToThingsboard.getPathPostParametersDel().addAll(postTelemetryAnalyzer.getPathPostParametersDel()); - } - // #3.3 - if (!keyNameOld.equals(keyNameNew)) { - ResultsAnalyzerParameters keyNameChange = this.getAnalyzerKeyName(new Gson().fromJson(keyNameOld.toString(), ConcurrentHashMap.class), - new Gson().fromJson(keyNameNew.toString(), ConcurrentHashMap.class)); - sentAttrToThingsboard.getPathPostParametersAdd().addAll(keyNameChange.getPathPostParametersAdd()); - } - - // #4.1 add - if (sentAttrToThingsboard.getPathPostParametersAdd().size() > 0) { - // update value in Resources - this.updateResourceValueObserve(lwServer, registration, lwM2MClient, sentAttrToThingsboard.getPathPostParametersAdd(), GET_TYPE_OPER_READ); - // sent attr/telemetry to tingsboard for new path - this.updateAttrTelemetry(registration, false, sentAttrToThingsboard.getPathPostParametersAdd()); - } - // #4.2 del - if (sentAttrToThingsboard.getPathPostParametersDel().size() > 0) { - ResultsAnalyzerParameters sentAttrToThingsboardDel = this.getAnalyzerParameters(sentAttrToThingsboard.getPathPostParametersAdd(), sentAttrToThingsboard.getPathPostParametersDel()); - sentAttrToThingsboard.setPathPostParametersDel(sentAttrToThingsboardDel.getPathPostParametersDel()); - } - - // #5.1 - if (!observeOld.equals(observeNew)) { - Set observeSetOld = new Gson().fromJson(observeOld, Set.class); - Set observeSetNew = new Gson().fromJson(observeNew, Set.class); - //#5.2 add - // path Attr/Telemetry includes newObserve - attributeSetOld.addAll(telemetrySetOld); - ResultsAnalyzerParameters sentObserveToClientOld = this.getAnalyzerParametersIn(attributeSetOld, observeSetOld); // add observe - attributeSetNew.addAll(telemetrySetNew); - ResultsAnalyzerParameters sentObserveToClientNew = this.getAnalyzerParametersIn(attributeSetNew, observeSetNew); // add observe - // does not include oldObserve - ResultsAnalyzerParameters postObserveAnalyzer = this.getAnalyzerParameters(sentObserveToClientOld.getPathPostParametersAdd(), sentObserveToClientNew.getPathPostParametersAdd()); - // sent Request observe to Client - this.updateResourceValueObserve(lwServer, registration, lwM2MClient, postObserveAnalyzer.getPathPostParametersAdd(), GET_TYPE_OPER_OBSERVE); - // 5.3 del - // sent Request cancel observe to Client - this.cancelObserveIsValue(lwServer, registration, postObserveAnalyzer.getPathPostParametersDel()); - } - } - } - - /** - * Compare old list with new list after change AttrTelemetryObserve in config Profile - * - * @param parametersOld - - * @param parametersNew - - * @return ResultsAnalyzerParameters: add && new - */ - private ResultsAnalyzerParameters getAnalyzerParameters(Set parametersOld, Set parametersNew) { - ResultsAnalyzerParameters analyzerParameters = null; - if (!parametersOld.equals(parametersNew)) { - analyzerParameters = new ResultsAnalyzerParameters(); - analyzerParameters.setPathPostParametersAdd(parametersNew - .stream().filter(p -> !parametersOld.contains(p)).collect(Collectors.toSet())); - analyzerParameters.setPathPostParametersDel(parametersOld - .stream().filter(p -> !parametersNew.contains(p)).collect(Collectors.toSet())); - } - return analyzerParameters; - } - - private ResultsAnalyzerParameters getAnalyzerKeyName(ConcurrentMap keyNameOld, ConcurrentMap keyNameNew) { - ResultsAnalyzerParameters analyzerParameters = new ResultsAnalyzerParameters(); - Set paths = keyNameNew.entrySet() - .stream() - .filter(e -> !e.getValue().equals(keyNameOld.get(e.getKey()))) - .collect(Collectors.toMap(map -> map.getKey(), map -> map.getValue())).keySet(); - analyzerParameters.setPathPostParametersAdd(paths); - return analyzerParameters; - } - - private ResultsAnalyzerParameters getAnalyzerParametersIn(Set parametersObserve, Set parameters) { - ResultsAnalyzerParameters analyzerParameters = new ResultsAnalyzerParameters(); - analyzerParameters.setPathPostParametersAdd(parametersObserve - .stream().filter(p -> parameters.contains(p)).collect(Collectors.toSet())); - return analyzerParameters; - } - - /** - * Update Resource value after change RezAttrTelemetry in config Profile - * sent response Read to Client and add path to pathResAttrTelemetry in LwM2MClient.getAttrTelemetryObserveValue() - * - * @param lwServer - LeshanServer - * @param registration - Registration LwM2M Client - * @param lwM2MClient - object with All parameters off client - * @param targets - path Resources == [ "/2/0/0", "/2/0/1"] - */ - private void updateResourceValueObserve(LeshanServer lwServer, Registration registration, LwM2MClient lwM2MClient, Set targets, String typeOper) { - targets.stream().forEach(target -> { - ResultIds pathIds = new ResultIds(target); - if (pathIds.resourceId >= 0 && lwM2MClient.getModelObjects().get(pathIds.getObjectId()) - .getInstances().get(pathIds.getInstanceId()).getResource(pathIds.getResourceId()).getValue() != null) { - if (GET_TYPE_OPER_READ.equals(typeOper)) { - lwM2MTransportRequest.sendAllRequest(lwServer, registration, target, typeOper, - ContentFormat.TLV.getName(), null, null, null, this.context.getCtxServer().getTimeout()); - } else if (GET_TYPE_OPER_OBSERVE.equals(typeOper)) { - lwM2MTransportRequest.sendAllRequest(lwServer, registration, target, typeOper, - null, null, null, null, this.context.getCtxServer().getTimeout()); - } - } - }); - } - - private void cancelObserveIsValue(LeshanServer lwServer, Registration registration, Set paramAnallyzer) { - LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getlwM2MClient(registration.getId()); - paramAnallyzer.forEach(p -> { - if (this.getResourceValue(lwM2MClient, p) != null) { - this.setCancelObservationRecourse(lwServer, registration, p); - } - } - ); - } - - private ResourceValue getResourceValue(LwM2MClient lwM2MClient, String path) { - ResourceValue resourceValue = null; - ResultIds pathIds = new ResultIds(path); - if (pathIds.getResourceId() > -1) { - LwM2mResource resource = lwM2MClient.getModelObjects().get(pathIds.getObjectId()).getInstances().get(pathIds.getInstanceId()).getResource(pathIds.getResourceId()); - if (resource.isMultiInstances()) { - Map values = resource.getValues(); - if (resource.getValues().size() > 0) { - resourceValue = new ResourceValue(); - resourceValue.setMultiInstances(resource.isMultiInstances()); - resourceValue.setValues(resource.getValues()); - } - } else { - if (resource.getValue() != null) { - resourceValue = new ResourceValue(); - resourceValue.setMultiInstances(resource.isMultiInstances()); - resourceValue.setValue(resource.getValue()); - } - } - } - return resourceValue; - } - - /** - * Trigger Server path = "/1/0/8" - * - * Trigger bootStrap path = "/1/0/9" - have to implemented on client - */ - public void doTrigger(LeshanServer lwServer, Registration registration, String path) { - lwM2MTransportRequest.sendAllRequest(lwServer, registration, path, POST_TYPE_OPER_EXECUTE, - ContentFormat.TLV.getName(), null, null, null, this.context.getCtxServer().getTimeout()); - } - - /** - * Session device in thingsboard is closed - * - * @param sessionInfo - lwm2m client - */ - private void doCloseSession(SessionInfoProto sessionInfo) { - TransportProtos.SessionEvent event = SessionEvent.CLOSED; - TransportProtos.SessionEventMsg msg = TransportProtos.SessionEventMsg.newBuilder() - .setSessionType(TransportProtos.SessionType.ASYNC) - .setEvent(event).build(); - transportService.process(sessionInfo, msg, null); - } - - /** - * Deregister session in transport - * @param sessionInfo - lwm2m client - */ - private void doDisconnect(SessionInfoProto sessionInfo) { - transportService.process(sessionInfo, DefaultTransportService.getSessionEventMsg(SessionEvent.CLOSED), null); - transportService.deregisterSession(sessionInfo); - } - - private void checkInactivityAndReportActivity() { - lwM2mInMemorySecurityStore.getSessions().forEach((key, value) -> transportService.reportActivity(this.getValidateSessionInfo(key))); - } - - public void sentLogsToThingsboard(String msg, String registrationId) { - if (msg != null) { - JsonObject telemetrys = new JsonObject(); - telemetrys.addProperty(LOG_LW2M_TELEMETRY, msg); - this.updateParametersOnThingsboard(telemetrys, LwM2MTransportHandler.DEVICE_TELEMETRY_TOPIC, registrationId); - } - } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServiceImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServiceImpl.java new file mode 100644 index 0000000000..8a62e5aa2a --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServiceImpl.java @@ -0,0 +1,1159 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.server; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.Link; +import org.eclipse.leshan.core.model.ResourceModel; +import org.eclipse.leshan.core.node.LwM2mMultipleResource; +import org.eclipse.leshan.core.node.LwM2mObject; +import org.eclipse.leshan.core.node.LwM2mObjectInstance; +import org.eclipse.leshan.core.node.LwM2mPath; +import org.eclipse.leshan.core.node.LwM2mSingleResource; +import org.eclipse.leshan.core.observation.Observation; +import org.eclipse.leshan.core.request.ContentFormat; +import org.eclipse.leshan.core.request.WriteRequest; +import org.eclipse.leshan.core.response.ReadResponse; +import org.eclipse.leshan.core.util.NamedThreadFactory; +import org.eclipse.leshan.server.californium.LeshanServer; +import org.eclipse.leshan.server.registration.Registration; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.common.transport.adaptor.AdaptorException; +import org.thingsboard.server.common.transport.adaptor.JsonConverter; +import org.thingsboard.server.common.transport.service.DefaultTransportService; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent; +import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; +import org.thingsboard.server.gen.transport.TransportProtos.ToTransportUpdateCredentialsProto; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; +import org.thingsboard.server.transport.lwm2m.server.client.AttrTelemetryObserveValue; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClient; +import org.thingsboard.server.transport.lwm2m.server.client.ResourceValue; +import org.thingsboard.server.transport.lwm2m.server.client.ResultsAnalyzerParameters; +import org.thingsboard.server.transport.lwm2m.server.secure.LwM2mInMemorySecurityStore; +import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; + +import javax.annotation.PostConstruct; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Random; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.stream.Collectors; + +import static org.thingsboard.server.common.transport.util.JsonUtils.getJsonObject; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.CLIENT_NOT_AUTHORIZED; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.DEVICE_ATTRIBUTES_REQUEST; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.DEVICE_ATTRIBUTES_TOPIC; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.DEVICE_TELEMETRY_TOPIC; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.GET_TYPE_OPER_OBSERVE; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.GET_TYPE_OPER_READ; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.LOG_LW2M_ERROR; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.LOG_LW2M_INFO; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.LOG_LW2M_TELEMETRY; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.POST_TYPE_OPER_EXECUTE; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.POST_TYPE_OPER_WRITE_REPLACE; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.SERVICE_CHANNEL; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.getAckCallback; + +@Slf4j +@Service("LwM2MTransportService") +@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' ) || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')") +public class LwM2MTransportServiceImpl implements LwM2MTransportService { + + private ExecutorService executorRegistered; + private ExecutorService executorUpdateRegistered; + private ExecutorService executorUnRegistered; + private LwM2mValueConverterImpl converter; + protected final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); + protected final Lock writeLock = readWriteLock.writeLock(); + + + @Autowired + private TransportService transportService; + + @Autowired + public LwM2MTransportContextServer context; + + @Autowired + private LwM2MTransportRequest lwM2MTransportRequest; + + @Autowired + LwM2mInMemorySecurityStore lwM2mInMemorySecurityStore; + + @PostConstruct + public void init() { + this.context.getScheduler().scheduleAtFixedRate(this::checkInactivityAndReportActivity, new Random().nextInt((int) context.getCtxServer().getSessionReportTimeout()), context.getCtxServer().getSessionReportTimeout(), TimeUnit.MILLISECONDS); + this.executorRegistered = Executors.newFixedThreadPool(this.context.getCtxServer().getRegisteredPoolSize(), + new NamedThreadFactory(String.format("LwM2M %s channel registered", SERVICE_CHANNEL))); + this.executorUpdateRegistered = Executors.newFixedThreadPool(this.context.getCtxServer().getUpdateRegisteredPoolSize(), + new NamedThreadFactory(String.format("LwM2M %s channel update registered", SERVICE_CHANNEL))); + this.executorUnRegistered = Executors.newFixedThreadPool(this.context.getCtxServer().getUnRegisteredPoolSize(), + new NamedThreadFactory(String.format("LwM2M %s channel un registered", SERVICE_CHANNEL))); + this.converter = LwM2mValueConverterImpl.getInstance(); + } + + /** + * Start registration device + * Create session: Map, LwM2MClient> + * 1. replaceNewRegistration -> (solving the problem of incorrect termination of the previous session with this endpoint) + * 1.1 When we initialize the registration, we register the session by endpoint. + * 1.2 If the server has incomplete requests (canceling the registration of the previous session), + * delete the previous session only by the previous registration.getId + * 1.2 Add Model (Entity) for client (from registration & observe) by registration.getId + * 1.2 Remove from sessions Model by enpPoint + * Next -> Create new LwM2MClient for current session -> setModelClient... + * + * @param lwServer - LeshanServer + * @param registration - Registration LwM2M Client + * @param previousObsersations - may be null + */ + public void onRegistered(LeshanServer lwServer, Registration registration, Collection previousObsersations) { + executorRegistered.submit(() -> { + try { +// log.warn("[{}] [{{}] Client: create after Registration", registration.getEndpoint(), registration.getId()); + LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.updateInSessionsLwM2MClient(lwServer, registration); + if (lwM2MClient != null) { + lwM2MClient.setLwM2MTransportServiceImpl(this); + lwM2MClient.setSessionUuid(UUID.randomUUID()); + this.sentLogsToThingsboard(LOG_LW2M_INFO + ": Client Registered", registration); + this.setLwM2mFromClientValue(lwServer, registration, lwM2MClient); + SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration); + if (sessionInfo != null) { + lwM2MClient.setDeviceUuid(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); + lwM2MClient.setProfileUuid(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); + lwM2MClient.setDeviceName(sessionInfo.getDeviceName()); + lwM2MClient.setDeviceProfileName(sessionInfo.getDeviceType()); + transportService.registerAsyncSession(sessionInfo, new LwM2MSessionMsgListener(this, sessionInfo)); + transportService.process(sessionInfo, DefaultTransportService.getSessionEventMsg(SessionEvent.OPEN), null); + transportService.process(sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().build(), null); + this.sentLogsToThingsboard(LOG_LW2M_INFO + ": Client create after Registration", registration); + this.putDelayedUpdateResourcesThingsboard(lwM2MClient); + } else { + log.error("Client: [{}] onRegistered [{}] name [{}] sessionInfo ", registration.getId(), registration.getEndpoint(), null); + } + } else { + log.error("Client: [{}] onRegistered [{}] name [{}] lwM2MClient ", registration.getId(), registration.getEndpoint(), null); + } + } catch (Throwable t) { + log.error("[{}] endpoint [{}] error Unable registration.", registration.getEndpoint(), t); + } + }); + } + + /** + * if sessionInfo removed from sessions, then new registerAsyncSession + * + * @param lwServer - LeshanServer + * @param registration - Registration LwM2M Client + */ + public void updatedReg(LeshanServer lwServer, Registration registration) { + executorUpdateRegistered.submit(() -> { + try { + SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration); + if (sessionInfo != null) { + this.checkInactivity(sessionInfo); + log.info("Client: [{}] updatedReg [{}] name [{}] profile ", registration.getId(), registration.getEndpoint(), sessionInfo.getDeviceType()); + } else { + log.error("Client: [{}] updatedReg [{}] name [{}] sessionInfo ", registration.getId(), registration.getEndpoint(), null); + } + } catch (Throwable t) { + log.error("[{}] endpoint [{}] error Unable update registration.", registration.getEndpoint(), t); + } + }); + } + + /** + * @param registration - Registration LwM2M Client + * @param observations - All paths observations before unReg + * !!! Warn: if have not finishing unReg, then this operation will be finished on next Client`s connect + */ + public void unReg(LeshanServer lwServer, Registration registration, Collection observations) { + executorUnRegistered.submit(() -> { + try { + this.setCancelObservations(lwServer, registration); + this.sentLogsToThingsboard(LOG_LW2M_INFO + ": Client unRegistration", registration); + this.closeClientSession(registration); + } catch (Throwable t) { + log.error("[{}] endpoint [{}] error Unable un registration.", registration.getEndpoint(), t); + } + }); + } + + private void closeClientSession(Registration registration) { + SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration); + if (sessionInfo != null) { + transportService.deregisterSession(sessionInfo); + this.doCloseSession(sessionInfo); + lwM2mInMemorySecurityStore.delRemoveSessionAndListener(registration.getId()); + if (lwM2mInMemorySecurityStore.getProfiles().size() > 0) { + this.syncSessionsAndProfiles(); + } + log.info("Client close session: [{}] unReg [{}] name [{}] profile ", registration.getId(), registration.getEndpoint(), sessionInfo.getDeviceType()); + } else { + log.error("Client close session: [{}] unReg [{}] name [{}] sessionInfo ", registration.getId(), registration.getEndpoint(), null); + } + } + + public void onSleepingDev(Registration registration) { + log.info("[{}] [{}] Received endpoint Sleeping version event", registration.getId(), registration.getEndpoint()); + //TODO: associate endpointId with device information. + } + + /** + * Those methods are called by the protocol stage thread pool, this means that execution MUST be done in a short delay, + * * if you need to do long time processing use a dedicated thread pool. + * + * @param registration - + */ + protected void onAwakeDev(Registration registration) { + log.info("[{}] [{}] Received endpoint Awake version event", registration.getId(), registration.getEndpoint()); + //TODO: associate endpointId with device information. + } + + /** + * This method is used to sync with sessions + * Removes a profile if not used in sessions + */ + private void syncSessionsAndProfiles() { + Map profilesClone = lwM2mInMemorySecurityStore.getProfiles().entrySet() + .stream() + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + profilesClone.forEach((k, v) -> { + String registrationId = lwM2mInMemorySecurityStore.getSessions().entrySet() + .stream() + .filter(e -> e.getValue().getProfileUuid().equals(k)) + .findFirst() + .map(Map.Entry::getKey) // return the key of the matching entry if found + .orElse(""); + if (registrationId.isEmpty()) { + lwM2mInMemorySecurityStore.getProfiles().remove(k); + } + }); + } + + /** + * #0 Add new ObjectModel to context + * Create new LwM2MClient for current session -> setModelClient... + * #1 Add all ObjectLinks (instance) to control the process of executing requests to the client + * to get the client model with current values + * #2 Get the client model with current values. Analyze the response in -> lwM2MTransportRequest.sendResponse + * + * @param lwServer - LeshanServer + * @param registration - Registration LwM2M Client + * @param lwM2MClient - object with All parameters off client + */ + private void setLwM2mFromClientValue(LeshanServer lwServer, Registration registration, LwM2MClient lwM2MClient) { + Arrays.stream(registration.getObjectLinks()).forEach(url -> { + LwM2mPath pathIds = new LwM2mPath(url.getUrl()); + if (pathIds.isObjectInstance() && !pathIds.isResource()) { + // #1 + lwM2MClient.getPendingRequests().add(url.getUrl()); + // #2 + lwM2MTransportRequest.sendAllRequest(lwServer, registration, url.getUrl(), GET_TYPE_OPER_READ, ContentFormat.TLV.getName(), + lwM2MClient, null, null, this.context.getCtxServer().getTimeout(), false); + } + }); + + // #1 + for (Link url : registration.getObjectLinks()) { + LwM2mPath pathIds = new LwM2mPath(url.getUrl()); + if (pathIds.isObjectInstance() && !pathIds.isResource()) { + lwM2MClient.getPendingRequests().add(url.getUrl()); + } + } + // #2 + for (Link url : registration.getObjectLinks()) { + LwM2mPath pathIds = new LwM2mPath(url.getUrl()); + if (pathIds.isObjectInstance() && !pathIds.isResource()) { + lwM2MTransportRequest.sendAllRequest(lwServer, registration, url.getUrl(), GET_TYPE_OPER_READ, ContentFormat.TLV.getName(), + lwM2MClient, null, null, this.context.getCtxServer().getTimeout(), false); + } + } + + // #1 + Arrays.stream(registration.getObjectLinks()).forEach(url -> { + LwM2mPath pathIds = new LwM2mPath(url.getUrl()); + if (pathIds.isObjectInstance() && !pathIds.isResource()) { + lwM2MClient.getPendingRequests().add(url.getUrl()); + } + }); + + // #2 + Arrays.stream(registration.getObjectLinks()).forEach(url -> { + LwM2mPath pathIds = new LwM2mPath(url.getUrl()); + if (pathIds.isObjectInstance() && !pathIds.isResource()) { + lwM2MTransportRequest.sendAllRequest(lwServer, registration, url.getUrl(), GET_TYPE_OPER_READ, ContentFormat.TLV.getName(), + lwM2MClient, null, null, this.context.getCtxServer().getTimeout(), false); + } + }); + } + + /** + * @param registration - Registration LwM2M Client + * @return - sessionInfo after access connect client + */ + private SessionInfoProto getValidateSessionInfo(Registration registration) { + LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getLwM2MClientWithReg(registration, null); + return getNewSessionInfoProto(lwM2MClient); + + } + + /** + * @param registrationId - + * @return - + */ + private SessionInfoProto getValidateSessionInfo(String registrationId) { + LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getLwM2MClientWithReg(null, registrationId); + return getNewSessionInfoProto(lwM2MClient); + } + + private SessionInfoProto getNewSessionInfoProto(LwM2MClient lwM2MClient) { + if (lwM2MClient != null) { + ValidateDeviceCredentialsResponseMsg msg = lwM2MClient.getCredentialsResponse(); + if (msg == null || msg.getDeviceInfo() == null) { + log.error("[{}] [{}]", lwM2MClient.getEndPoint(), CLIENT_NOT_AUTHORIZED); + this.closeClientSession(lwM2MClient.getRegistration()); + return null; + } else { + return SessionInfoProto.newBuilder() + .setNodeId(this.context.getNodeId()) + .setSessionIdMSB(lwM2MClient.getSessionUuid().getMostSignificantBits()) + .setSessionIdLSB(lwM2MClient.getSessionUuid().getLeastSignificantBits()) + .setDeviceIdMSB(msg.getDeviceInfo().getDeviceIdMSB()) + .setDeviceIdLSB(msg.getDeviceInfo().getDeviceIdLSB()) + .setTenantIdMSB(msg.getDeviceInfo().getTenantIdMSB()) + .setTenantIdLSB(msg.getDeviceInfo().getTenantIdLSB()) + .setDeviceName(msg.getDeviceInfo().getDeviceName()) + .setDeviceType(msg.getDeviceInfo().getDeviceType()) + .setDeviceProfileIdLSB(msg.getDeviceInfo().getDeviceProfileIdLSB()) + .setDeviceProfileIdMSB(msg.getDeviceInfo().getDeviceProfileIdMSB()) + .build(); + } + } + return null; + } + + /** + * Add attribute/telemetry information from Client and credentials/Profile to client model and start observe + * !!! if the resource has an observation, but no telemetry or attribute - the observation will not use + * #1 Sending Attribute Telemetry with value to thingsboard only once at the start of the connection + * #2 Start observe + * + * @param lwM2MClient - LwM2M Client + */ + + public void updatesAndSentModelParameter(LwM2MClient lwM2MClient) { + // #1 + this.updateAttrTelemetry(lwM2MClient.getRegistration(), true, null); + // #2 + this.onSentObserveToClient(lwM2MClient.getLwServer(), lwM2MClient.getRegistration()); + + } + + /** + * If there is a difference in values between the current resource values and the shared attribute values + * when the client connects to the server + * #1 get attributes name from profile include name resources in ModelObject if resource isWritable + * #2.1 #1 size > 0 => send Request getAttributes to thingsboard + * #2.2 #1 size == 0 => continue normal process + * + * @param lwM2MClient - LwM2M Client + */ + public void putDelayedUpdateResourcesThingsboard(LwM2MClient lwM2MClient) { + SessionInfoProto sessionInfo = this.getValidateSessionInfo(lwM2MClient.getRegistration()); + if (sessionInfo != null) { + //#1.1 + #1.2 + List attrSharedNames = this.getNamesAttrFromProfileIsWritable(lwM2MClient); + if (attrSharedNames.size() > 0) { + //#2.1 + try { + TransportProtos.GetAttributeRequestMsg getAttributeMsg = context.getAdaptor().convertToGetAttributes(null, attrSharedNames); + lwM2MClient.getDelayedRequestsId().add(getAttributeMsg.getRequestId()); + transportService.process(sessionInfo, getAttributeMsg, getAckCallback(lwM2MClient, getAttributeMsg.getRequestId(), DEVICE_ATTRIBUTES_REQUEST)); + } catch (AdaptorException e) { + log.warn("Failed to decode get attributes request", e); + } + } + // #2.2 + else { + lwM2MClient.onSuccessOrErrorDelayedRequests(null); + } + } + } + + /** + * Update resource value on client: if there is a difference in values between the current resource values and the shared attribute values + * #1 Get path resource by result attributesResponse + * #1.1 If two names have equal path => last time attribute + * #2.1 if there is a difference in values between the current resource values and the shared attribute values + * => sent to client Request Update of value (new value from shared attribute) + * and LwM2MClient.delayedRequests.add(path) + * #2.1 if there is not a difference in values between the current resource values and the shared attribute values + * + * @param attributesResponse - + * @param sessionInfo - + */ + public void onGetAttributesResponse(TransportProtos.GetAttributeResponseMsg attributesResponse, TransportProtos.SessionInfoProto sessionInfo) { + LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getLwM2MClient(sessionInfo); + if (lwM2MClient.getDelayedRequestsId().contains(attributesResponse.getRequestId())) { + attributesResponse.getSharedAttributeListList().forEach(attr -> { + String path = this.getPathAttributeUpdate(sessionInfo, attr.getKv().getKey()); + // #1.1 + if (lwM2MClient.getDelayedRequests().containsKey(path) && attr.getTs() > lwM2MClient.getDelayedRequests().get(path).getTs()) { + lwM2MClient.getDelayedRequests().put(path, attr); + } else { + lwM2MClient.getDelayedRequests().put(path, attr); + } + }); + // #2.1 + lwM2MClient.getDelayedRequests().forEach((k, v) -> { + ArrayList listV = new ArrayList<>(); + listV.add(v.getKv()); + this.putDelayedUpdateResourcesClient(lwM2MClient, this.getResourceValueToString(lwM2MClient, k), getJsonObject(listV).get(v.getKv().getKey()), k); + }); + lwM2MClient.getDelayedRequestsId().remove(attributesResponse.getRequestId()); + if (lwM2MClient.getDelayedRequests().size() == 0) { + lwM2MClient.onSuccessOrErrorDelayedRequests(null); + } + } + } + + private void putDelayedUpdateResourcesClient(LwM2MClient lwM2MClient, Object valueOld, Object valueNew, String path) { + if (valueNew != null && !valueNew.toString().equals(valueOld.toString())) { + lwM2MTransportRequest.sendAllRequest(lwM2MClient.getLwServer(), lwM2MClient.getRegistration(), path, POST_TYPE_OPER_WRITE_REPLACE, + ContentFormat.TLV.getName(), lwM2MClient, null, valueNew, this.context.getCtxServer().getTimeout(), + true); + } + } + + /** + * Get names and keyNames from profile shared!!!! attr resources IsWritable + * + * @param lwM2MClient - + * @return ArrayList keyNames from profile attr resources shared!!!! && IsWritable + */ + private List getNamesAttrFromProfileIsWritable(LwM2MClient lwM2MClient) { + AttrTelemetryObserveValue profile = lwM2mInMemorySecurityStore.getProfile(lwM2MClient.getProfileUuid()); + Set attrSet = new Gson().fromJson(profile.getPostAttributeProfile(), Set.class); + ConcurrentMap keyNamesMap = new Gson().fromJson(profile.getPostKeyNameProfile().toString(), ConcurrentHashMap.class); + + ConcurrentMap keyNamesIsWritable = keyNamesMap.entrySet() + .stream() + .filter(e -> (attrSet.contains(e.getKey()) && context.getCtxServer().getResourceModel(lwM2MClient.getRegistration(), new LwM2mPath(e.getKey())) != null && + context.getCtxServer().getResourceModel(lwM2MClient.getRegistration(), new LwM2mPath(e.getKey())).operations.isWritable())) + .collect(Collectors.toConcurrentMap(Map.Entry::getKey, Map.Entry::getValue)); + + Set namesIsWritable = ConcurrentHashMap.newKeySet(); + namesIsWritable.addAll(new HashSet<>(keyNamesIsWritable.values())); + return new ArrayList<>(namesIsWritable); + } + + + /** + * Sent Attribute and Telemetry to Thingsboard + * #1 - get AttrName/TelemetryName with value: + * #1.1 from Client + * #1.2 from LwM2MClient: + * -- resourceId == path from AttrTelemetryObserveValue.postAttributeProfile/postTelemetryProfile/postObserveProfile + * -- AttrName/TelemetryName == resourceName from ModelObject.objectModel, value from ModelObject.instance.resource(resourceId) + * #2 - set Attribute/Telemetry + * + * @param registration - Registration LwM2M Client + */ + private void updateAttrTelemetry(Registration registration, boolean start, Set paths) { + JsonObject attributes = new JsonObject(); + JsonObject telemetries = new JsonObject(); + if (start) { + // #1.1 + JsonObject attributeClient = this.getAttributeClient(registration); + if (attributeClient != null) { + attributeClient.entrySet().forEach(p -> attributes.add(p.getKey(), p.getValue())); + } + } + // #1.2 + try { + writeLock.lock(); + this.getParametersFromProfile(attributes, telemetries, registration, paths); + } catch (Exception e) { + log.error("UpdateAttrTelemetry", e); + } finally { + writeLock.unlock(); + } + if (attributes.getAsJsonObject().entrySet().size() > 0) + this.updateParametersOnThingsboard(attributes, DEVICE_ATTRIBUTES_TOPIC, registration); + if (telemetries.getAsJsonObject().entrySet().size() > 0) + this.updateParametersOnThingsboard(telemetries, DEVICE_TELEMETRY_TOPIC, registration); + } + + /** + * get AttrName/TelemetryName with value from Client + * + * @param registration - + * @return - JsonObject, format: {name: value}} + */ + private JsonObject getAttributeClient(Registration registration) { + if (registration.getAdditionalRegistrationAttributes().size() > 0) { + JsonObject resNameValues = new JsonObject(); + registration.getAdditionalRegistrationAttributes().forEach(resNameValues::addProperty); + return resNameValues; + } + return null; + } + + /** + * @param attributes - new JsonObject + * @param telemetry - new JsonObject + * @param registration - Registration LwM2M Client + * result: add to JsonObject those resources to which the user is subscribed and they have a value + * if path==null add All resources else only one + * (attributes/telemetry): new {name(Attr/Telemetry):value} + */ + private void getParametersFromProfile(JsonObject attributes, JsonObject telemetry, Registration registration, Set path) { + AttrTelemetryObserveValue attrTelemetryObserveValue = lwM2mInMemorySecurityStore.getProfiles().get(lwM2mInMemorySecurityStore.getSessions().get(registration.getId()).getProfileUuid()); + attrTelemetryObserveValue.getPostAttributeProfile().forEach(p -> { + LwM2mPath pathIds = new LwM2mPath(p.getAsString().toString()); + if (pathIds.isResource()) { + if (path == null || path.contains(p.getAsString())) { + this.addParameters(p.getAsString().toString(), attributes, registration); + } + } + }); + attrTelemetryObserveValue.getPostTelemetryProfile().forEach(p -> { + LwM2mPath pathIds = new LwM2mPath(p.getAsString().toString()); + if (pathIds.isResource()) { + if (path == null || path.contains(p.getAsString())) { + this.addParameters(p.getAsString().toString(), telemetry, registration); + } + } + }); + } + + /** + * @param parameters - JsonObject attributes/telemetry + * @param registration - Registration LwM2M Client + */ + private void addParameters(String path, JsonObject parameters, Registration registration) { + LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getSessions().get(registration.getId()); + JsonObject names = lwM2mInMemorySecurityStore.getProfiles().get(lwM2MClient.getProfileUuid()).getPostKeyNameProfile(); + String resName = String.valueOf(names.get(path)); + if (resName != null && !resName.isEmpty()) { + try { + String resValue = this.getResourceValueToString(lwM2MClient, path); + if (resValue != null) { + parameters.addProperty(resName, resValue); + } + } catch (Exception e) { + log.error(e.getStackTrace().toString()); + } + } + } + + /** + * Prepare Sent to Thigsboard callback - Attribute or Telemetry + * + * @param msg - JsonArray: [{name: value}] + * @param topicName - Api Attribute or Telemetry + * @param registration - Id of Registration LwM2M Client + */ + public void updateParametersOnThingsboard(JsonElement msg, String topicName, Registration registration) { + SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration); + if (sessionInfo != null) { + context.sentParametersOnThingsboard(msg, topicName, sessionInfo); + } else { + log.error("Client: [{}] updateParametersOnThingsboard [{}] sessionInfo ", registration, null); + } + } + + /** + * Start observe + * #1 - Analyze: + * #1.1 path in observe == (attribute or telemetry) + * #2 Analyze after sent request (response): + * #2.1 First: lwM2MTransportRequest.sendResponse -> ObservationListener.newObservation + * #2.2 Next: ObservationListener.onResponse * + * + * @param lwServer - LeshanServer + * @param registration - Registration LwM2M Client + */ + private void onSentObserveToClient(LeshanServer lwServer, Registration registration) { + if (lwServer.getObservationService().getObservations(registration).size() > 0) { + this.setCancelObservations(lwServer, registration); + } + UUID profileUUid = lwM2mInMemorySecurityStore.getSessions().get(registration.getId()).getProfileUuid(); + AttrTelemetryObserveValue attrTelemetryObserveValue = lwM2mInMemorySecurityStore.getProfiles().get(profileUUid); + attrTelemetryObserveValue.getPostObserveProfile().forEach(p -> { + // #1.1 + String target = (getValidateObserve(attrTelemetryObserveValue.getPostAttributeProfile(), p.getAsString().toString())) ? + p.getAsString().toString() : (getValidateObserve(attrTelemetryObserveValue.getPostTelemetryProfile(), p.getAsString().toString())) ? + p.getAsString().toString() : null; + if (target != null) { + // #2 + if (this.getResourceValueToString(lwM2mInMemorySecurityStore.getSessions().get(registration.getId()), target) != null) { + lwM2MTransportRequest.sendAllRequest(lwServer, registration, target, GET_TYPE_OPER_OBSERVE, + null, null, null, null, this.context.getCtxServer().getTimeout(), + false); + } + } + }); + } + + public void setCancelObservations(LeshanServer lwServer, Registration registration) { + if (registration != null) { + Set observations = lwServer.getObservationService().getObservations(registration); + observations.forEach(observation -> this.setCancelObservationRecourse(lwServer, registration, observation.getPath().toString())); + } + } + + /** + * lwM2MTransportRequest.sendAllRequest(lwServer, registration, path, POST_TYPE_OPER_OBSERVE_CANCEL, null, null, null, null, context.getTimeout()); + * At server side this will not remove the observation from the observation store, to do it you need to use + * {@code ObservationService#cancelObservation()} + */ + public void setCancelObservationRecourse(LeshanServer lwServer, Registration registration, String path) { + lwServer.getObservationService().cancelObservations(registration, path); + } + + /** + * @param parameters - JsonArray postAttributeProfile/postTelemetryProfile + * @param path - recourse from postObserveProfile + * @return rez - true if path observe is in attribute/telemetry + */ + private boolean getValidateObserve(JsonElement parameters, String path) { + AtomicBoolean rez = new AtomicBoolean(false); + if (parameters.isJsonArray()) { + parameters.getAsJsonArray().forEach(p -> { + if (p.getAsString().toString().equals(path)) rez.set(true); + } + ); + } else if (parameters.isJsonObject()) { + rez.set((parameters.getAsJsonObject().entrySet()).stream().map(json -> json.toString()) + .filter(path::equals).findAny().orElse(null) != null); + } + return rez.get(); + } + + /** + * Sending observe value to thingsboard from ObservationListener.onResponse: object, instance, SingleResource or MultipleResource + * + * @param registration - Registration LwM2M Client + * @param path - observe + * @param response - observe + */ + + public void onObservationResponse(Registration registration, String path, ReadResponse response) { + if (response.getContent() != null) { + if (response.getContent() instanceof LwM2mObject) { +// LwM2mObject content = (LwM2mObject) response.getContent(); + } else if (response.getContent() instanceof LwM2mObjectInstance) { +// LwM2mObjectInstance content = (LwM2mObjectInstance) response.getContent(); + } else if (response.getContent() instanceof LwM2mSingleResource) { + LwM2mSingleResource content = (LwM2mSingleResource) response.getContent(); + this.onObservationSetResourcesValue(registration, content.getValue(), null, path); + } else if (response.getContent() instanceof LwM2mMultipleResource) { + LwM2mMultipleResource content = (LwM2mMultipleResource) response.getContent(); + this.onObservationSetResourcesValue(registration, null, content.getValues(), path); + } + } + } + + /** + * Sending observe value of resources to thingsboard + * #1 Return old Value Resource from LwM2MClient + * #2 Update new Resources (replace old Resource Value on new Resource Value) + * + * @param registration - Registration LwM2M Client + * @param value - LwM2mSingleResource response.getContent() + * @param values - LwM2mSingleResource response.getContent() + * @param path - resource + */ + private void onObservationSetResourcesValue(Registration registration, Object value, Map values, String path) { + boolean isChange = false; + try { + writeLock.lock(); + // #1 + LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getLwM2MClientWithReg(registration, null); + LwM2mPath pathIds = new LwM2mPath(path); +// log.warn("#0 nameDevice: [{}] resultIds: [{}] value: [{}], values: [{}] ", lwM2MClient.getDeviceName(), pathIds, value, values); + ResourceModel.Type resModelType = context.getCtxServer().getResourceModelType(registration, pathIds); + ResourceValue resValueOld = lwM2MClient.getResources().get(path); + // #2 + if (resValueOld.isMultiInstances() && !values.toString().equals(resValueOld.getResourceValue().toString())) { + lwM2MClient.getResources().get(path).setValues(values); +// ResourceValue resourceValue = new ResourceValue(values, null, true); +// lwM2MClient.getResources().put(path, resourceValue); + isChange = true; + } else if (!LwM2MTransportHandler.equalsResourceValue(resValueOld.getValue(), value, resModelType, pathIds)) { + lwM2MClient.getResources().get(path).setValue(value); +// ResourceValue resourceValueOld = lwM2MClient.getResources().get(path); +// lwM2MClient.getResources().remove(resourceValueOld); +// ResourceValue resourceValue = new ResourceValue(null, value, false); +// lwM2MClient.getResources().put(path, resourceValue); + log.warn("upDateResize: [{}] [{}] [{}] [{}]", lwM2MClient.getEndPoint(), lwM2MClient.getResources().size(), value, path); + isChange = true; + } + } catch (Exception e) { + log.error("#1_1 Update ResourcesValue after Observation is unsuccessfully path: [{}] value: [{}] [{}]", path, value, e.toString()); + } finally { + writeLock.unlock(); + } + + if (isChange) { + Set paths = new HashSet<>(); + paths.add(path); + this.updateAttrTelemetry(registration, false, paths); + } + } + + /** + * @param updateCredentials - Credentials include config only security Client (without config attr/telemetry...) + * config attr/telemetry... in profile + */ + public void onToTransportUpdateCredentials(ToTransportUpdateCredentialsProto updateCredentials) { + log.info("[{}] idList [{}] valueList updateCredentials", updateCredentials.getCredentialsIdList(), updateCredentials.getCredentialsValueList()); + } + + /** + * Update - sent request in change value resources in Client + * Path to resources from profile equal keyName or from ModelObject equal name + * Only for resources: isWritable && isPresent as attribute in profile -> AttrTelemetryObserveValue (format: CamelCase) + * Delete - nothing * + * + * @param msg - + */ + public void onAttributeUpdate(TransportProtos.AttributeUpdateNotificationMsg msg, TransportProtos.SessionInfoProto sessionInfo) { + if (msg.getSharedUpdatedCount() > 0) { + JsonElement el = JsonConverter.toJson(msg); + el.getAsJsonObject().entrySet().forEach(de -> { + String path = this.getPathAttributeUpdate(sessionInfo, de.getKey()); + String value = de.getValue().getAsString(); + LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getSession(new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())).entrySet().iterator().next().getValue(); + AttrTelemetryObserveValue profile = lwM2mInMemorySecurityStore.getProfile(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); + ResourceModel resourceModel = context.getCtxServer().getResourceModel(lwM2MClient.getRegistration(), new LwM2mPath(path)); + if (!path.isEmpty() && (this.validatePathInAttrProfile(profile, path) || this.validatePathInTelemetryProfile(profile, path))) { + if (resourceModel != null && resourceModel.operations.isWritable()) { + lwM2MTransportRequest.sendAllRequest(lwM2MClient.getLwServer(), lwM2MClient.getRegistration(), path, POST_TYPE_OPER_WRITE_REPLACE, + ContentFormat.TLV.getName(), lwM2MClient, null, value, this.context.getCtxServer().getTimeout(), + false); + } else { + log.error("Resource path - [{}] value - [{}] is not Writable and cannot be updated", path, value); + String logMsg = String.format(LOG_LW2M_ERROR + ": attributeUpdate: Resource path - %s value - %s is not Writable and cannot be updated", path, value); + this.sentLogsToThingsboard(logMsg, lwM2MClient.getRegistration()); + } + } else { + log.error("Attribute name - [{}] value - [{}] is not present as attribute in profile and cannot be updated", de.getKey(), value); + String logMsg = String.format(LOG_LW2M_ERROR + ": attributeUpdate: attribute name - %s value - %s is not present as attribute in profile and cannot be updated", de.getKey(), value); + this.sentLogsToThingsboard(logMsg, lwM2MClient.getRegistration()); + } + }); + } else if (msg.getSharedDeletedCount() > 0) { + log.info("[{}] delete [{}] onAttributeUpdate", msg.getSharedDeletedList(), sessionInfo); + } + } + + /** + * Get path to resource from profile equal keyName or from ModelObject equal name + * Only for resource: isWritable && isPresent as attribute in profile -> AttrTelemetryObserveValue (format: CamelCase) + * + * @param sessionInfo - + * @param name - + * @return path if path isPresent in postProfile + */ + private String getPathAttributeUpdate(TransportProtos.SessionInfoProto sessionInfo, String name) { + String profilePath = this.getPathAttributeUpdateProfile(sessionInfo, name); +// return !profilePath.isEmpty() ? profilePath : this.getPathAttributeUpdateModelObject(name); + return !profilePath.isEmpty() ? profilePath : null; + } + + /** + * @param profile - + * @param path - + * @return true if path isPresent in postAttributeProfile + */ + private boolean validatePathInAttrProfile(AttrTelemetryObserveValue profile, String path) { + Set attributesSet = new Gson().fromJson(profile.getPostAttributeProfile(), Set.class); + return attributesSet.stream().filter(p -> p.equals(path)).findFirst().isPresent(); + } + + /** + * @param profile - + * @param path - + * @return true if path isPresent in postAttributeProfile + */ + private boolean validatePathInTelemetryProfile(AttrTelemetryObserveValue profile, String path) { + Set telemetriesSet = new Gson().fromJson(profile.getPostTelemetryProfile(), Set.class); + return telemetriesSet.stream().filter(p -> p.equals(path)).findFirst().isPresent(); + } + + + /** + * Get path to resource from profile equal keyName + * + * @param sessionInfo - + * @param name - + * @return - + */ + private String getPathAttributeUpdateProfile(TransportProtos.SessionInfoProto sessionInfo, String name) { + AttrTelemetryObserveValue profile = lwM2mInMemorySecurityStore.getProfile(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); + return profile.getPostKeyNameProfile().getAsJsonObject().entrySet().stream() + .filter(e -> e.getValue().getAsString().equals(name)).findFirst().map(Map.Entry::getKey) + .orElse(""); + } + + /** + * Update resource (attribute) value on thingsboard after update value in client + * + * @param registration - + * @param path - + * @param request - + */ + public void onAttributeUpdateOk(Registration registration, String path, WriteRequest request, boolean isDelayedUpdate) { + ResourceModel resource = context.getCtxServer().getResourceModel(registration, new LwM2mPath(path)); + if (resource.multiple) { + this.onObservationSetResourcesValue(registration, null, ((LwM2mSingleResource) request.getNode()).getValues(), path); + } else { + this.onObservationSetResourcesValue(registration, ((LwM2mSingleResource) request.getNode()).getValue(), null, path); + } + if (isDelayedUpdate) lwM2mInMemorySecurityStore.getLwM2MClientWithReg(registration, null) + .onSuccessOrErrorDelayedRequests(request.getPath().toString()); + } + + /** + * @param sessionInfo - + * @param deviceProfile - + */ + public void onDeviceProfileUpdate(TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile) { + Set registrationIds = lwM2mInMemorySecurityStore.getSessions().entrySet() + .stream() + .filter(e -> e.getValue().getProfileUuid().equals(deviceProfile.getUuidId())) + .map(Map.Entry::getKey).sorted().collect(Collectors.toCollection(LinkedHashSet::new)); + if (registrationIds.size() > 0) { + this.onDeviceUpdateChangeProfile(registrationIds, deviceProfile); + } + } + + /** + * @param sessionInfo - + * @param device - + * @param deviceProfileOpt - + */ + public void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional deviceProfileOpt) { + Optional registrationIdOpt = lwM2mInMemorySecurityStore.getSessions().entrySet().stream() + .filter(e -> device.getUuidId().equals(e.getValue().getDeviceUuid())) + .map(Map.Entry::getKey) + .findFirst(); + registrationIdOpt.ifPresent(registrationId -> this.onDeviceUpdateLwM2MClient(registrationId, device, deviceProfileOpt)); + } + + /** + * Update parameters device in LwM2MClient + * If new deviceProfile != old deviceProfile => update deviceProfile + * + * @param registrationId - + * @param device - + */ + private void onDeviceUpdateLwM2MClient(String registrationId, Device device, Optional deviceProfileOpt) { + LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getSessions().get(registrationId); + lwM2MClient.setDeviceName(device.getName()); + if (!lwM2MClient.getProfileUuid().equals(device.getDeviceProfileId().getId())) { + Set registrationIds = new HashSet<>(); + registrationIds.add(registrationId); + deviceProfileOpt.ifPresent(deviceProfile -> this.onDeviceUpdateChangeProfile(registrationIds, deviceProfile)); + } + + lwM2MClient.setProfileUuid(device.getDeviceProfileId().getId()); + } + + /** + * #1 Read new, old Value (Attribute, Telemetry, Observe, KeyName) + * #2 Update in lwM2MClient: ...Profile if changes from update device + * #3 Equivalence test: old <> new Value (Attribute, Telemetry, Observe, KeyName) + * #3.1 Attribute isChange (add&del) + * #3.2 Telemetry isChange (add&del) + * #3.3 KeyName isChange (add) + * #4 update + * #4.1 add If #3 isChange, then analyze and update Value in Transport form Client and sent Value to thingsboard + * #4.2 del + * -- if add attributes includes del telemetry - result del for observe + * #5 + * #5.1 Observe isChange (add&del) + * #5.2 Observe.add + * -- path Attr/Telemetry includes newObserve and does not include oldObserve: sent Request observe to Client + * #5.3 Observe.del + * -- different between newObserve and oldObserve: sent Request cancel observe to client + * + * @param registrationIds - + * @param deviceProfile - + */ + private void onDeviceUpdateChangeProfile(Set registrationIds, DeviceProfile deviceProfile) { + + AttrTelemetryObserveValue attrTelemetryObserveValueOld = lwM2mInMemorySecurityStore.getProfiles().get(deviceProfile.getUuidId()); + if (lwM2mInMemorySecurityStore.addUpdateProfileParameters(deviceProfile)) { + + // #1 + JsonArray attributeOld = attrTelemetryObserveValueOld.getPostAttributeProfile(); + Set attributeSetOld = new Gson().fromJson(attributeOld, Set.class); + JsonArray telemetryOld = attrTelemetryObserveValueOld.getPostTelemetryProfile(); + Set telemetrySetOld = new Gson().fromJson(telemetryOld, Set.class); + JsonArray observeOld = attrTelemetryObserveValueOld.getPostObserveProfile(); + JsonObject keyNameOld = attrTelemetryObserveValueOld.getPostKeyNameProfile(); + + AttrTelemetryObserveValue attrTelemetryObserveValueNew = lwM2mInMemorySecurityStore.getProfiles().get(deviceProfile.getUuidId()); + JsonArray attributeNew = attrTelemetryObserveValueNew.getPostAttributeProfile(); + Set attributeSetNew = new Gson().fromJson(attributeNew, Set.class); + JsonArray telemetryNew = attrTelemetryObserveValueNew.getPostTelemetryProfile(); + Set telemetrySetNew = new Gson().fromJson(telemetryNew, Set.class); + JsonArray observeNew = attrTelemetryObserveValueNew.getPostObserveProfile(); + JsonObject keyNameNew = attrTelemetryObserveValueNew.getPostKeyNameProfile(); + + // #3 + ResultsAnalyzerParameters sentAttrToThingsboard = new ResultsAnalyzerParameters(); + // #3.1 + if (!attributeOld.equals(attributeNew)) { + ResultsAnalyzerParameters postAttributeAnalyzer = this.getAnalyzerParameters(new Gson().fromJson(attributeOld, Set.class), attributeSetNew); + sentAttrToThingsboard.getPathPostParametersAdd().addAll(postAttributeAnalyzer.getPathPostParametersAdd()); + sentAttrToThingsboard.getPathPostParametersDel().addAll(postAttributeAnalyzer.getPathPostParametersDel()); + } + // #3.2 + if (!attributeOld.equals(attributeNew)) { + ResultsAnalyzerParameters postTelemetryAnalyzer = this.getAnalyzerParameters(new Gson().fromJson(telemetryOld, Set.class), telemetrySetNew); + sentAttrToThingsboard.getPathPostParametersAdd().addAll(postTelemetryAnalyzer.getPathPostParametersAdd()); + sentAttrToThingsboard.getPathPostParametersDel().addAll(postTelemetryAnalyzer.getPathPostParametersDel()); + } + // #3.3 + if (!keyNameOld.equals(keyNameNew)) { + ResultsAnalyzerParameters keyNameChange = this.getAnalyzerKeyName(new Gson().fromJson(keyNameOld.toString(), ConcurrentHashMap.class), + new Gson().fromJson(keyNameNew.toString(), ConcurrentHashMap.class)); + sentAttrToThingsboard.getPathPostParametersAdd().addAll(keyNameChange.getPathPostParametersAdd()); + } + + // #4.1 add + if (sentAttrToThingsboard.getPathPostParametersAdd().size() > 0) { + // update value in Resources + registrationIds.forEach(registrationId -> { + LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getLwM2MClientWithReg(null, registrationId); + LeshanServer lwServer = lwM2MClient.getLwServer(); + Registration registration = lwM2mInMemorySecurityStore.getByRegistration(registrationId); + log.warn("[{}] # 4.1", registration.getEndpoint()); + this.updateResourceValueObserve(lwServer, registration, sentAttrToThingsboard.getPathPostParametersAdd(), GET_TYPE_OPER_READ); + // sent attr/telemetry to tingsboard for new path + this.updateAttrTelemetry(registration, false, sentAttrToThingsboard.getPathPostParametersAdd()); + }); + } + // #4.2 del + if (sentAttrToThingsboard.getPathPostParametersDel().size() > 0) { + ResultsAnalyzerParameters sentAttrToThingsboardDel = this.getAnalyzerParameters(sentAttrToThingsboard.getPathPostParametersAdd(), sentAttrToThingsboard.getPathPostParametersDel()); + sentAttrToThingsboard.setPathPostParametersDel(sentAttrToThingsboardDel.getPathPostParametersDel()); + } + + // #5.1 + if (!observeOld.equals(observeNew)) { + Set observeSetOld = new Gson().fromJson(observeOld, Set.class); + Set observeSetNew = new Gson().fromJson(observeNew, Set.class); + //#5.2 add + // path Attr/Telemetry includes newObserve + attributeSetOld.addAll(telemetrySetOld); + ResultsAnalyzerParameters sentObserveToClientOld = this.getAnalyzerParametersIn(attributeSetOld, observeSetOld); // add observe + attributeSetNew.addAll(telemetrySetNew); + ResultsAnalyzerParameters sentObserveToClientNew = this.getAnalyzerParametersIn(attributeSetNew, observeSetNew); // add observe + // does not include oldObserve + ResultsAnalyzerParameters postObserveAnalyzer = this.getAnalyzerParameters(sentObserveToClientOld.getPathPostParametersAdd(), sentObserveToClientNew.getPathPostParametersAdd()); + // sent Request observe to Client + registrationIds.forEach(registrationId -> { + LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getLwM2MClient(null, registrationId); + LeshanServer lwServer = lwM2MClient.getLwServer(); + Registration registration = lwM2mInMemorySecurityStore.getByRegistration(registrationId); + log.warn("[{}] # 5.1", registration.getEndpoint()); + this.updateResourceValueObserve(lwServer, registration, postObserveAnalyzer.getPathPostParametersAdd(), GET_TYPE_OPER_OBSERVE); + // 5.3 del + // sent Request cancel observe to Client + this.cancelObserveIsValue(lwServer, registration, postObserveAnalyzer.getPathPostParametersDel()); + }); + } + } + } + + /** + * Compare old list with new list after change AttrTelemetryObserve in config Profile + * + * @param parametersOld - + * @param parametersNew - + * @return ResultsAnalyzerParameters: add && new + */ + private ResultsAnalyzerParameters getAnalyzerParameters(Set parametersOld, Set parametersNew) { + ResultsAnalyzerParameters analyzerParameters = null; + if (!parametersOld.equals(parametersNew)) { + analyzerParameters = new ResultsAnalyzerParameters(); + analyzerParameters.setPathPostParametersAdd(parametersNew + .stream().filter(p -> !parametersOld.contains(p)).collect(Collectors.toSet())); + analyzerParameters.setPathPostParametersDel(parametersOld + .stream().filter(p -> !parametersNew.contains(p)).collect(Collectors.toSet())); + } + return analyzerParameters; + } + + private ResultsAnalyzerParameters getAnalyzerKeyName(ConcurrentMap keyNameOld, ConcurrentMap keyNameNew) { + ResultsAnalyzerParameters analyzerParameters = new ResultsAnalyzerParameters(); + Set paths = keyNameNew.entrySet() + .stream() + .filter(e -> !e.getValue().equals(keyNameOld.get(e.getKey()))) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)).keySet(); + analyzerParameters.setPathPostParametersAdd(paths); + return analyzerParameters; + } + + private ResultsAnalyzerParameters getAnalyzerParametersIn(Set parametersObserve, Set parameters) { + ResultsAnalyzerParameters analyzerParameters = new ResultsAnalyzerParameters(); + analyzerParameters.setPathPostParametersAdd(parametersObserve + .stream().filter(parameters::contains).collect(Collectors.toSet())); + return analyzerParameters; + } + + /** + * Update Resource value after change RezAttrTelemetry in config Profile + * sent response Read to Client and add path to pathResAttrTelemetry in LwM2MClient.getAttrTelemetryObserveValue() + * + * @param lwServer - LeshanServer + * @param registration - Registration LwM2M Client + * @param targets - path Resources == [ "/2/0/0", "/2/0/1"] + */ + private void updateResourceValueObserve(LeshanServer lwServer, Registration registration, Set targets, String typeOper) { + targets.forEach(target -> { + LwM2mPath pathIds = new LwM2mPath(target); + if (pathIds.isResource()) { + if (GET_TYPE_OPER_READ.equals(typeOper)) { + lwM2MTransportRequest.sendAllRequest(lwServer, registration, target, typeOper, + ContentFormat.TLV.getName(), null, null, null, this.context.getCtxServer().getTimeout(), + false); + } else if (GET_TYPE_OPER_OBSERVE.equals(typeOper)) { + lwM2MTransportRequest.sendAllRequest(lwServer, registration, target, typeOper, + null, null, null, null, this.context.getCtxServer().getTimeout(), + false); + } + } + }); + } + + private void cancelObserveIsValue(LeshanServer lwServer, Registration registration, Set paramAnallyzer) { + LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getLwM2MClientWithReg(registration, null); + paramAnallyzer.forEach(p -> { + if (this.getResourceValue(lwM2MClient, new LwM2mPath(p)) != null) { + this.setCancelObservationRecourse(lwServer, registration, p); + } + } + ); + } + + private ResourceValue getResourceValue(LwM2MClient lwM2MClient, LwM2mPath pathIds) { + ResourceValue resourceValue = null; + if (pathIds.isResource()) { + resourceValue = lwM2MClient.getResources().get(pathIds.toString()); + } + return resourceValue; + } + + /** + * Trigger Server path = "/1/0/8" + * + * Trigger bootStrap path = "/1/0/9" - have to implemented on client + */ + public void doTrigger(LeshanServer lwServer, Registration registration, String path) { + lwM2MTransportRequest.sendAllRequest(lwServer, registration, path, POST_TYPE_OPER_EXECUTE, + ContentFormat.TLV.getName(), null, null, null, this.context.getCtxServer().getTimeout(), + false); + } + + /** + * Session device in thingsboard is closed + * + * @param sessionInfo - lwm2m client + */ + private void doCloseSession(SessionInfoProto sessionInfo) { + TransportProtos.SessionEvent event = SessionEvent.CLOSED; + TransportProtos.SessionEventMsg msg = TransportProtos.SessionEventMsg.newBuilder() + .setSessionType(TransportProtos.SessionType.ASYNC) + .setEvent(event).build(); + transportService.process(sessionInfo, msg, null); + } + + /** + * Deregister session in transport + * + * @param sessionInfo - lwm2m client + */ + public void doDisconnect(SessionInfoProto sessionInfo) { + transportService.process(sessionInfo, DefaultTransportService.getSessionEventMsg(SessionEvent.CLOSED), null); + transportService.deregisterSession(sessionInfo); + } + + private void checkInactivityAndReportActivity() { + lwM2mInMemorySecurityStore.getSessions().forEach((key, value) -> this.checkInactivity(this.getValidateSessionInfo(key))); + } + + /** + * if sessionInfo removed from sessions, then new registerAsyncSession + * + * @param sessionInfo - + */ + private void checkInactivity(SessionInfoProto sessionInfo) { + if (transportService.reportActivity(sessionInfo) == null) { + transportService.registerAsyncSession(sessionInfo, new LwM2MSessionMsgListener(this, sessionInfo)); + } + } + + public void sentLogsToThingsboard(String msg, Registration registration) { + if (msg != null) { + JsonObject telemetries = new JsonObject(); + telemetries.addProperty(LOG_LW2M_TELEMETRY, msg); + this.updateParametersOnThingsboard(telemetries, LwM2MTransportHandler.DEVICE_TELEMETRY_TOPIC, registration); + } + } + + /** + * @param path - path resource + * @return - value of Resource or null + */ + private String getResourceValueToString(LwM2MClient lwM2MClient, String path) { + LwM2mPath pathIds = new LwM2mPath(path); + ResourceValue resourceValue = this.getResourceValue(lwM2MClient, pathIds); + return (resourceValue == null) ? null : + (String) this.converter.convertValue(resourceValue.getResourceValue(), this.context.getCtxServer().getResourceModelType(lwM2MClient.getRegistration(), pathIds), ResourceModel.Type.STRING, pathIds); + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java index 53dee9046f..937d2c7dcd 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java @@ -29,10 +29,11 @@ import java.util.Collection; @Slf4j public class LwM2mServerListener { - private LeshanServer lhServer; - private LwM2MTransportService service; - public LwM2mServerListener(LeshanServer lhServer, LwM2MTransportService service) { + private final LeshanServer lhServer; + private final LwM2MTransportServiceImpl service; + + public LwM2mServerListener(LeshanServer lhServer, LwM2MTransportServiceImpl service) { this.lhServer = lhServer; this.service = service; } @@ -43,9 +44,8 @@ public class LwM2mServerListener { */ @Override public void registered(Registration registration, Registration previousReg, - Collection previousObsersations) { - - service.onRegistered(lhServer, registration, previousObsersations); + Collection previousObservations) { + service.onRegistered(lhServer, registration, previousObservations); } /** @@ -63,7 +63,7 @@ public class LwM2mServerListener { @Override public void unregistered(Registration registration, Collection observations, boolean expired, Registration newReg) { - service.unReg(registration, observations); + service.unReg(lhServer, registration, observations); } }; @@ -71,11 +71,13 @@ public class LwM2mServerListener { public final PresenceListener presenceListener = new PresenceListener() { @Override public void onSleeping(Registration registration) { + log.info("onSleeping"); service.onSleepingDev(registration); } @Override public void onAwake(Registration registration) { + log.info("onAwake"); service.onAwakeDev(registration); } }; @@ -92,8 +94,9 @@ public class LwM2mServerListener { if (registration != null) { try { service.onObservationResponse(registration, observation.getPath().toString(), response); - } catch (java.lang.NullPointerException e) { - log.error(e.toString()); + } catch (Exception e) { + log.error("[{}] onResponse", e.toString()); + } } } @@ -108,4 +111,5 @@ public class LwM2mServerListener { log.info("Received newObservation from [{}] endpoint [{}] ", observation.getPath(), registration.getEndpoint()); } }; + } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ResultIds.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ResultIds.java deleted file mode 100644 index 95b3f0e3f2..0000000000 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ResultIds.java +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright © 2016-2021 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.transport.lwm2m.server; - -import lombok.Builder; -import lombok.Data; - -@Data -public class ResultIds { - @Builder.Default - int objectId = -1; - @Builder.Default - int instanceId = -1; - @Builder.Default - int resourceId = -1; - - public ResultIds (String path) { - String[] paths = path.split("/"); - if (paths != null && paths.length > 1) { - this.objectId = (paths.length > 1) ? Integer.parseInt(paths[1]) : this.objectId; - this.instanceId = (paths.length > 2) ? Integer.parseInt(paths[2]) : this.instanceId; - this.resourceId = (paths.length > 3) ? Integer.parseInt(paths[3]) : this.resourceId; - } - } -} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/adaptors/LwM2MJsonAdaptor.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/adaptors/LwM2MJsonAdaptor.java index b517cabc73..b228a52b94 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/adaptors/LwM2MJsonAdaptor.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/adaptors/LwM2MJsonAdaptor.java @@ -24,6 +24,12 @@ import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.gen.transport.TransportProtos; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; + @Slf4j @Component("LwM2MJsonAdaptor") @ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' )|| ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')") @@ -46,4 +52,37 @@ public class LwM2MJsonAdaptor implements LwM2MTransportAdaptor { throw new AdaptorException(ex); } } + + @Override + public TransportProtos.GetAttributeRequestMsg convertToGetAttributes(List clientKeys, List sharedKeys) throws AdaptorException { + return processGetAttributeRequestMsg(clientKeys, sharedKeys); + } + + protected TransportProtos.GetAttributeRequestMsg processGetAttributeRequestMsg(List clientKeys, List sharedKeys) throws AdaptorException { + try { + TransportProtos.GetAttributeRequestMsg.Builder result = TransportProtos.GetAttributeRequestMsg.newBuilder(); + Random random = new Random(); + result.setRequestId(random.nextInt()); + if (clientKeys != null) { + result.addAllClientAttributeNames(clientKeys); + } + if (sharedKeys != null) { + result.addAllSharedAttributeNames(sharedKeys); + } + return result.build(); + } catch (RuntimeException e) { + log.warn("Failed to decode get attributes request", e); + throw new AdaptorException(e); + } + } + + private Set toStringSet(JsonElement requestBody, String name) { + JsonElement element = requestBody.getAsJsonObject().get(name); + if (element != null) { + return new HashSet<>(Arrays.asList(element.getAsString().split(","))); + } else { + return null; + } + } + } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/adaptors/LwM2MTransportAdaptor.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/adaptors/LwM2MTransportAdaptor.java index c360aae7d9..4b6d54c874 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/adaptors/LwM2MTransportAdaptor.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/adaptors/LwM2MTransportAdaptor.java @@ -19,9 +19,13 @@ import com.google.gson.JsonElement; import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.gen.transport.TransportProtos; +import java.util.List; + public interface LwM2MTransportAdaptor { TransportProtos.PostTelemetryMsg convertToPostTelemetry(JsonElement jsonElement) throws AdaptorException; TransportProtos.PostAttributeMsg convertToPostAttributes(JsonElement jsonElement) throws AdaptorException; + + TransportProtos.GetAttributeRequestMsg convertToGetAttributes(List clientKeys, List sharedKeys) throws AdaptorException; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2MClient.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2MClient.java index 3214e3cdcc..5ccfbff18a 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2MClient.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2MClient.java @@ -18,20 +18,22 @@ package org.thingsboard.server.transport.lwm2m.server.client; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.model.ObjectModel; +import org.eclipse.leshan.core.node.LwM2mMultipleResource; import org.eclipse.leshan.core.node.LwM2mObjectInstance; +import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.response.LwM2mResponse; import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.server.californium.LeshanServer; import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.security.SecurityInfo; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; -import org.thingsboard.server.transport.lwm2m.server.LwM2MTransportService; -import org.thingsboard.server.transport.lwm2m.server.ResultIds; +import org.thingsboard.server.transport.lwm2m.server.LwM2MTransportServiceImpl; +import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; -import java.util.UUID; import java.util.Map; import java.util.Set; -import java.util.Collection; +import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; @@ -47,36 +49,43 @@ public class LwM2MClient implements Cloneable { private UUID sessionUuid; private UUID profileUuid; private LeshanServer lwServer; - private LwM2MTransportService lwM2MTransportService; + private LwM2MTransportServiceImpl lwM2MTransportServiceImpl; private Registration registration; private ValidateDeviceCredentialsResponseMsg credentialsResponse; private Map attributes; - private Map modelObjects; + private Map resources; private Set pendingRequests; + private Map delayedRequests; + private Set delayedRequestsId; private Map responses; + private final LwM2mValueConverterImpl converter; public Object clone() throws CloneNotSupportedException { return super.clone(); } - public LwM2MClient(String endPoint, String identity, SecurityInfo info, ValidateDeviceCredentialsResponseMsg credentialsResponse, Map attributes, Map modelObjects, UUID profileUuid) { + public LwM2MClient(String endPoint, String identity, SecurityInfo info, ValidateDeviceCredentialsResponseMsg credentialsResponse, UUID profileUuid) { this.endPoint = endPoint; this.identity = identity; this.info = info; this.credentialsResponse = credentialsResponse; - this.attributes = (attributes != null && attributes.size() > 0) ? attributes : new ConcurrentHashMap(); - this.modelObjects = (modelObjects != null && modelObjects.size() > 0) ? modelObjects : new ConcurrentHashMap(); + this.attributes = new ConcurrentHashMap<>(); this.pendingRequests = ConcurrentHashMap.newKeySet(); + this.delayedRequests = new ConcurrentHashMap<>(); + this.resources = new ConcurrentHashMap<>(); + this.delayedRequestsId = ConcurrentHashMap.newKeySet(); this.profileUuid = profileUuid; /** * Key , response instance -> resources: value...> */ this.responses = new ConcurrentHashMap<>(); + this.converter = LwM2mValueConverterImpl.getInstance(); } /** - * Fill with data -> Model client - * @param path - + * Fill with data -> Model client + * + * @param path - * @param response - */ public void onSuccessHandler(String path, LwM2mResponse response) { @@ -84,24 +93,41 @@ public class LwM2MClient implements Cloneable { this.pendingRequests.remove(path); if (this.pendingRequests.size() == 0) { this.initValue(); - this.lwM2MTransportService.updatesAndSentModelParameter(this.lwServer, this.registration); + this.lwM2MTransportServiceImpl.putDelayedUpdateResourcesThingsboard(this); } } private void initValue() { this.responses.forEach((key, resp) -> { - ResultIds pathIds = new ResultIds(key); - if (pathIds.getObjectId() > -1) { - ObjectModel objectModel = ((Collection) this.lwServer.getModelProvider().getObjectModel(registration).getObjectModels()).stream().filter(v -> v.id == pathIds.getObjectId()).collect(Collectors.toList()).get(0); - if (this.modelObjects.get(pathIds.getObjectId()) != null) { - this.modelObjects.get(pathIds.getObjectId()).getInstances().put(((ReadResponse) resp).getContent().getId(), (LwM2mObjectInstance) ((ReadResponse) resp).getContent()); - } else { - Map instances = new ConcurrentHashMap<>(); - instances.put(((ReadResponse) resp).getContent().getId(), (LwM2mObjectInstance) ((ReadResponse) resp).getContent()); - ModelObject modelObject = new ModelObject(objectModel, instances); - this.modelObjects.put(pathIds.getObjectId(), modelObject); + LwM2mPath pathIds = new LwM2mPath(key); + if (pathIds.isObject() || pathIds.isObjectInstance() || pathIds.isResource()) { + ObjectModel objectModel = this.lwServer.getModelProvider().getObjectModel(registration).getObjectModels().stream().filter(v -> v.id == pathIds.getObjectId()).collect(Collectors.toList()).get(0); + if (objectModel != null) { + ((LwM2mObjectInstance)((ReadResponse)resp).getContent()).getResources().forEach((k, v) -> { + String rez = pathIds.toString() + "/" + k; + if (((LwM2mObjectInstance) ((ReadResponse) resp).getContent()).getResource(k) instanceof LwM2mMultipleResource){ + this.resources.put(rez, new ResourceValue(v.getValues(), null, true)); + } + else { + this.resources.put(rez, new ResourceValue(null, v.getValue(), false)); + } + }); } } }); + if (this.responses.size() == 0) this.responses = new ConcurrentHashMap<>(); } + + /** + * if path != null + * @param path + */ + public void onSuccessOrErrorDelayedRequests(String path) { + if (path != null) this.delayedRequests.remove(path); + if (this.delayedRequests.size() == 0 && this.getDelayedRequestsId().size() == 0) { + this.lwM2MTransportServiceImpl.updatesAndSentModelParameter(this); + } + } + } + diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ModelObject.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ModelObject.java index 4e586c7f90..5d8fedc9c7 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ModelObject.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ModelObject.java @@ -21,7 +21,7 @@ import org.eclipse.leshan.core.node.LwM2mObjectInstance; import java.util.Map; @Data -public class ModelObject { +public class ModelObject implements Cloneable { /** * model one on all instance * for each instance only id resource with parameters of resources (observe, attr, telemetry) @@ -38,4 +38,8 @@ public class ModelObject { LwM2mObjectInstance instance = this.instances.get(id); return this.instances.remove(id, instance); } + + public ModelObject clone() throws CloneNotSupportedException { + return (ModelObject) super.clone(); + } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResourceValue.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResourceValue.java index cc3b17c759..3ff04f288b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResourceValue.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResourceValue.java @@ -16,11 +16,23 @@ package org.thingsboard.server.transport.lwm2m.server.client; import lombok.Data; + import java.util.Map; @Data public class ResourceValue { - Map values; + Map values; Object value; boolean multiInstances; + + public ResourceValue(Map values, Object value, boolean multiInstances) { + this.values = values; + this.value = value; + this.multiInstances = multiInstances; + } + + public Object getResourceValue() { + return this.multiInstances ? this.values : this.value; + } + } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/secure/LwM2MSetSecurityStoreServer.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/secure/LwM2MSetSecurityStoreServer.java deleted file mode 100644 index 9f3b52aa25..0000000000 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/secure/LwM2MSetSecurityStoreServer.java +++ /dev/null @@ -1,234 +0,0 @@ -/** - * Copyright © 2016-2021 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.transport.lwm2m.server.secure; - -import lombok.Data; -import lombok.extern.slf4j.Slf4j; -import org.eclipse.leshan.core.util.Hex; -import org.eclipse.leshan.server.californium.LeshanServerBuilder; -import org.eclipse.leshan.server.redis.RedisRegistrationStore; -import org.eclipse.leshan.server.redis.RedisSecurityStore; -import org.eclipse.leshan.server.security.DefaultAuthorizer; -import org.eclipse.leshan.server.security.EditableSecurityStore; -import org.eclipse.leshan.server.security.SecurityChecker; -import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; -import org.thingsboard.server.transport.lwm2m.server.LwM2MTransportContextServer; -import redis.clients.jedis.Jedis; -import redis.clients.jedis.JedisPool; -import redis.clients.jedis.util.Pool; - -import java.math.BigInteger; -import java.net.URI; -import java.net.URISyntaxException; -import java.security.KeyStore; -import java.security.PublicKey; -import java.security.PrivateKey; -import java.security.AlgorithmParameters; -import java.security.KeyFactory; -import java.security.GeneralSecurityException; -import java.security.KeyStoreException; -import java.security.cert.X509Certificate; -import java.security.interfaces.ECPublicKey; -import java.security.spec.*; -import java.util.Arrays; - -import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.*; - -@Slf4j -@Data -public class LwM2MSetSecurityStoreServer { - - private KeyStore keyStore; - private X509Certificate certificate; - private PublicKey publicKey; - private PrivateKey privateKey; - private LwM2MTransportContextServer context; - private LwM2mInMemorySecurityStore lwM2mInMemorySecurityStore; - - private LeshanServerBuilder builder; - EditableSecurityStore securityStore; - - public LwM2MSetSecurityStoreServer(LeshanServerBuilder builder, LwM2MTransportContextServer context, LwM2mInMemorySecurityStore lwM2mInMemorySecurityStore, LwM2MSecurityMode dtlsMode) { - this.builder = builder; - this.context = context; - this.lwM2mInMemorySecurityStore = lwM2mInMemorySecurityStore; - /** Set securityStore with new registrationStore */ - switch (dtlsMode) { - /** Use PSK only */ - case PSK: - generatePSK_RPK(); - if (this.privateKey != null && this.privateKey.getEncoded().length > 0) { - builder.setPrivateKey(this.privateKey); - builder.setPublicKey(null); - getParamsPSK(); - } - break; - /** Use RPK only */ - case RPK: - generatePSK_RPK(); - if (this.publicKey != null && this.publicKey.getEncoded().length > 0 && - this.privateKey != null && this.privateKey.getEncoded().length > 0) { - builder.setPublicKey(this.publicKey); - builder.setPrivateKey(this.privateKey); - getParamsRPK(); - } - break; - /** Use x509 only */ - case X509: - setServerWithX509Cert(); - break; - /** No security */ - case NO_SEC: - builder.setTrustedCertificates(new X509Certificate[0]); - break; - /** Use x509 with EST */ - case X509_EST: - // TODO support sentinel pool and make pool configurable - break; - case REDIS: - /** - * Set securityStore with new registrationStore (if use redis store) - * Connect to redis - */ - Pool jedis = null; - try { - jedis = new JedisPool(new URI(this.context.getCtxServer().getRedisUrl())); - securityStore = new RedisSecurityStore(jedis); - builder.setRegistrationStore(new RedisRegistrationStore(jedis)); - } catch (URISyntaxException e) { - e.printStackTrace(); - } - break; - default: - } - - /** Set securityStore with new registrationStore (if not redis)*/ - if (dtlsMode.code < REDIS.code) { - securityStore = lwM2mInMemorySecurityStore; - if (dtlsMode == X509) { - builder.setAuthorizer(new DefaultAuthorizer(securityStore, new SecurityChecker() { - @Override - protected boolean matchX509Identity(String endpoint, String receivedX509CommonName, - String expectedX509CommonName) { - return endpoint.startsWith(expectedX509CommonName); - } - })); - } - } - - /** Set securityStore with new registrationStore */ - builder.setSecurityStore(securityStore); - } - - private void generatePSK_RPK() { - try { - /** Get Elliptic Curve Parameter spec for secp256r1 */ - AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC"); - algoParameters.init(new ECGenParameterSpec("secp256r1")); - ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class); - if (this.context.getCtxServer().getServerPublicX() != null && !this.context.getCtxServer().getServerPublicX().isEmpty() && this.context.getCtxServer().getServerPublicY() != null && !this.context.getCtxServer().getServerPublicY().isEmpty()) { - /** Get point values */ - byte[] publicX = Hex.decodeHex(this.context.getCtxServer().getServerPublicX().toCharArray()); - byte[] publicY = Hex.decodeHex(this.context.getCtxServer().getServerPublicY().toCharArray()); - /** Create key specs */ - KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)), - parameterSpec); - /** Get keys */ - this.publicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec); - } - if (this.context.getCtxServer().getServerPrivateS() != null && !this.context.getCtxServer().getServerPrivateS().isEmpty()) { - /** Get point values */ - byte[] privateS = Hex.decodeHex(this.context.getCtxServer().getServerPrivateS().toCharArray()); - /** Create key specs */ - KeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(privateS), parameterSpec); - /** Get keys */ - this.privateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec); - } - } catch (GeneralSecurityException | IllegalArgumentException e) { - log.error("[{}] Failed generate Server PSK/RPK", e.getMessage()); - throw new RuntimeException(e); - } - } - - private void setServerWithX509Cert() { - try { - if (this.context.getCtxServer().getKeyStoreValue() != null) { - setBuilderX509(); - X509Certificate rootCAX509Cert = (X509Certificate) this.context.getCtxServer().getKeyStoreValue().getCertificate(this.context.getCtxServer().getRootAlias()); - if (rootCAX509Cert != null) { - X509Certificate[] trustedCertificates = new X509Certificate[1]; - trustedCertificates[0] = rootCAX509Cert; - builder.setTrustedCertificates(trustedCertificates); - } else { - /** by default trust all */ - builder.setTrustedCertificates(new X509Certificate[0]); - } - } - else { - /** by default trust all */ - this.builder.setTrustedCertificates(new X509Certificate[0]); - log.error("Unable to load X509 files for LWM2MServer"); - } - } catch (KeyStoreException ex) { - log.error("[{}] Unable to load X509 files server", ex.getMessage()); - } - } - - private void setBuilderX509() { - /** - * For deb => KeyStorePathFile == yml or commandline: KEY_STORE_PATH_FILE - * For idea => KeyStorePathResource == common/transport/lwm2m/src/main/resources/credentials: in LwM2MTransportContextServer: credentials/serverKeyStore.jks - */ - try { - X509Certificate serverCertificate = (X509Certificate) this.context.getCtxServer().getKeyStoreValue().getCertificate(this.context.getCtxServer().getServerAlias()); - PrivateKey privateKey = (PrivateKey) this.context.getCtxServer().getKeyStoreValue().getKey(this.context.getCtxServer().getServerAlias(), this.context.getCtxServer().getKeyStorePasswordServer() == null ? null : this.context.getCtxServer().getKeyStorePasswordServer().toCharArray()); - this.builder.setPrivateKey(privateKey); - this.builder.setCertificateChain(new X509Certificate[]{serverCertificate}); - } catch (Exception ex) { - log.error("[{}] Unable to load KeyStore files server", ex.getMessage()); - } - } - - private void getParamsPSK() { - log.info("\nServer uses PSK -> private key : \n security key : [{}] \n serverSecureURI : [{}]", - Hex.encodeHexString(this.privateKey.getEncoded()), - this.context.getCtxServer().getServerSecureHost() + ":" + Integer.toString(this.context.getCtxServer().getServerSecurePort())); - } - - private void getParamsRPK() { - if (this.publicKey instanceof ECPublicKey) { - /** Get x coordinate */ - byte[] x = ((ECPublicKey) this.publicKey).getW().getAffineX().toByteArray(); - if (x[0] == 0) - x = Arrays.copyOfRange(x, 1, x.length); - - /** Get Y coordinate */ - byte[] y = ((ECPublicKey) this.publicKey).getW().getAffineY().toByteArray(); - if (y[0] == 0) - y = Arrays.copyOfRange(y, 1, y.length); - - /** Get Curves params */ - String params = ((ECPublicKey) this.publicKey).getParams().toString(); - log.info( - " \nServer uses RPK : \n Elliptic Curve parameters : [{}] \n Public x coord : [{}] \n Public y coord : [{}] \n Public Key (Hex): [{}] \n Private Key (Hex): [{}]", - params, Hex.encodeHexString(x), Hex.encodeHexString(y), - Hex.encodeHexString(this.publicKey.getEncoded()), - Hex.encodeHexString(this.privateKey.getEncoded())); - } else { - throw new IllegalStateException("Unsupported Public Key Format (only ECPublicKey supported)."); - } - } -} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/secure/LwM2mInMemorySecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/secure/LwM2mInMemorySecurityStore.java index db02210913..5f0e82126b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/secure/LwM2mInMemorySecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/secure/LwM2mInMemorySecurityStore.java @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server.secure; import com.google.gson.JsonObject; import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.server.californium.LeshanServer; import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.security.InMemorySecurityStore; @@ -24,61 +25,75 @@ import org.eclipse.leshan.server.security.SecurityInfo; import org.eclipse.leshan.server.security.SecurityStoreListener; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.transport.lwm2m.secure.LwM2MGetSecurityInfo; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; +import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator; import org.thingsboard.server.transport.lwm2m.secure.ReadResultSecurityStore; import org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler; import org.thingsboard.server.transport.lwm2m.server.client.AttrTelemetryObserveValue; import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClient; import org.thingsboard.server.transport.lwm2m.utils.TypeServer; -import java.util.Map; -import java.util.UUID; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.stream.Collectors; -import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.*; +import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.NO_SEC; @Slf4j -@Component("LwM2mInMemorySecurityStore") +@Service("LwM2mInMemorySecurityStore") @ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' )|| ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')") public class LwM2mInMemorySecurityStore extends InMemorySecurityStore { + private static final boolean INFOS_ARE_COMPROMISED = false; + // lock for the two maps - protected final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); - protected final Lock readLock = readWriteLock.readLock(); - protected final Lock writeLock = readWriteLock.writeLock(); - private final boolean infosAreCompromised = false; - protected Map sessions = new ConcurrentHashMap<>(); - protected Map profiles = new ConcurrentHashMap<>(); + private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); + private final Lock readLock = readWriteLock.readLock(); + private final Lock writeLock = readWriteLock.writeLock(); + private final Map sessions = new ConcurrentHashMap<>(); + private Map profiles = new ConcurrentHashMap<>(); private SecurityStoreListener listener; @Autowired - LwM2MGetSecurityInfo lwM2MGetSecurityInfo; + LwM2mCredentialsSecurityInfoValidator lwM2MCredentialsSecurityInfoValidator; + /** + * Start after DefaultAuthorizer or LwM2mPskStore + * @param endPoint - + * @return SecurityInfo + */ @Override public SecurityInfo getByEndpoint(String endPoint) { readLock.lock(); try { String registrationId = this.getByRegistrationId(endPoint, null); - return (registrationId != null && sessions.size() > 0 && sessions.get(registrationId) != null) ? sessions.get(registrationId).getInfo() : this.add(endPoint); + return (registrationId != null && sessions.size() > 0 && sessions.get(registrationId) != null) ? + sessions.get(registrationId).getInfo() : this.addLwM2MClientToSession(endPoint); } finally { readLock.unlock(); } } + /** + * Start after LwM2mPskStore + * @param identity - + * @return SecurityInfo + */ @Override public SecurityInfo getByIdentity(String identity) { readLock.lock(); try { String integrationId = this.getByRegistrationId(null, identity); - return (integrationId != null) ? sessions.get(integrationId).getInfo() : add(identity); + return (integrationId != null) ? sessions.get(integrationId).getInfo() : this.addLwM2MClientToSession(identity); } finally { readLock.unlock(); } @@ -88,7 +103,7 @@ public class LwM2mInMemorySecurityStore extends InMemorySecurityStore { public Collection getAll() { readLock.lock(); try { - return Collections.unmodifiableCollection(this.sessions.entrySet().stream().map(model -> model.getValue().getInfo()).collect(Collectors.toList())); + return Collections.unmodifiableCollection(this.sessions.values().stream().map(LwM2MClient::getInfo).collect(Collectors.toList())); } finally { readLock.unlock(); } @@ -104,7 +119,7 @@ public class LwM2mInMemorySecurityStore extends InMemorySecurityStore { LwM2MClient lwM2MClient = (sessions.get(registrationId) != null) ? sessions.get(registrationId) : null; if (lwM2MClient != null) { if (listener != null) { - listener.securityInfoRemoved(infosAreCompromised, lwM2MClient.getInfo()); + listener.securityInfoRemoved(INFOS_ARE_COMPROMISED, lwM2MClient.getInfo()); } sessions.remove(registrationId); } @@ -118,22 +133,38 @@ public class LwM2mInMemorySecurityStore extends InMemorySecurityStore { this.listener = listener; } - public LwM2MClient getlwM2MClient(String endPoint, String identity) { + public LwM2MClient getLwM2MClient(String endPoint, String identity) { Map.Entry modelClients = (endPoint != null) ? this.sessions.entrySet().stream().filter(model -> endPoint.equals(model.getValue().getEndPoint())).findAny().orElse(null) : this.sessions.entrySet().stream().filter(model -> identity.equals(model.getValue().getIdentity())).findAny().orElse(null); return (modelClients != null) ? modelClients.getValue() : null; } - public LwM2MClient getlwM2MClient(String registrationId) { - return this.sessions.get(registrationId); + public LwM2MClient getLwM2MClientWithReg(Registration registration, String registrationId) { + return registrationId != null ? + this.sessions.get(registrationId) : + this.sessions.containsKey(registration.getId()) ? + this.sessions.get(registration.getId()) : + this.sessions.get(registration.getEndpoint()); + } + + public LwM2MClient getLwM2MClient(TransportProtos.SessionInfoProto sessionInfo) { + return this.getSession(new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())).entrySet().iterator().next().getValue(); + } - public LwM2MClient getlwM2MClient(LeshanServer lwServer, Registration registration) { + /** + * Update in sessions (LwM2MClient for key registration_Id) after starting registration LwM2MClient in LwM2MTransportServiceImpl + * Remove from sessions LwM2MClient with key registration_Endpoint + * @param lwServer - + * @param registration - + * @return LwM2MClient after adding it to session + */ + public LwM2MClient updateInSessionsLwM2MClient(LeshanServer lwServer, Registration registration) { writeLock.lock(); try { if (this.sessions.get(registration.getEndpoint()) == null) { - this.add(registration.getEndpoint()); + this.addLwM2MClientToSession(registration.getEndpoint()); } LwM2MClient lwM2MClient = this.sessions.get(registration.getEndpoint()); lwM2MClient.setLwServer(lwServer); @@ -154,35 +185,43 @@ public class LwM2mInMemorySecurityStore extends InMemorySecurityStore { return (registrationIds != null && registrationIds.size() > 0) ? registrationIds.get(0) : null; } - public String getByRegistrationId(String credentialsId) { - List registrationIds = (this.sessions.entrySet().stream().filter(model -> credentialsId.equals(model.getValue().getEndPoint())).map(model -> model.getKey()).collect(Collectors.toList()).size() > 0) ? - this.sessions.entrySet().stream().filter(model -> credentialsId.equals(model.getValue().getEndPoint())).map(model -> model.getKey()).collect(Collectors.toList()) : - this.sessions.entrySet().stream().filter(model -> credentialsId.equals(model.getValue().getIdentity())).map(model -> model.getKey()).collect(Collectors.toList()); - return (registrationIds != null && registrationIds.size() > 0) ? registrationIds.get(0) : null; - } - public Registration getByRegistration(String registrationId) { return this.sessions.get(registrationId).getRegistration(); } - private SecurityInfo add(String identity) { - ReadResultSecurityStore store = lwM2MGetSecurityInfo.getSecurityInfo(identity, TypeServer.CLIENT); - UUID profileUuid = (addUpdateProfileParameters(store.getDeviceProfile())) ? store.getDeviceProfile().getUuidId() : null; - if (store.getSecurityInfo() != null) { - if (store.getSecurityMode() < DEFAULT_MODE.code) { + /** + * Add new LwM2MClient to session + * @param identity- + * @return SecurityInfo. If error - SecurityInfoError + * and log: + * - FORBIDDEN - if there is no authorization + * - profileUuid - if the device does not have a profile + * - device - if the thingsboard does not have a device with a name equal to the identity + */ + private SecurityInfo addLwM2MClientToSession(String identity) { + ReadResultSecurityStore store = lwM2MCredentialsSecurityInfoValidator.createAndValidateCredentialsSecurityInfo(identity, TypeServer.CLIENT); + if (store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) { + UUID profileUuid = (store.getDeviceProfile() != null && addUpdateProfileParameters(store.getDeviceProfile())) ? store.getDeviceProfile().getUuidId() : null; + if (store.getSecurityInfo() != null && profileUuid != null) { String endpoint = store.getSecurityInfo().getEndpoint(); - sessions.put(endpoint, new LwM2MClient(endpoint, store.getSecurityInfo().getIdentity(), store.getSecurityInfo(), store.getMsg(), null, null, profileUuid)); + sessions.put(endpoint, new LwM2MClient(endpoint, store.getSecurityInfo().getIdentity(), store.getSecurityInfo(), store.getMsg(), profileUuid)); + } else if (store.getSecurityMode() == NO_SEC.code && profileUuid != null) { + sessions.put(identity, new LwM2MClient(identity, null, null, store.getMsg(), profileUuid)); + } else { + log.error("Registration failed: FORBIDDEN/profileUuid/device [{}] , endpointId: [{}]", profileUuid, identity); + /** + * Return Error securityInfo + */ + byte[] preSharedKey = Hex.decodeHex("0A0B".toCharArray()); + SecurityInfo infoError = SecurityInfo.newPreSharedKeyInfo("error", "error_identity", preSharedKey); + return infoError; + } } - } else { - if (store.getSecurityMode() == NO_SEC.code) - sessions.put(identity, new LwM2MClient(identity, null, null, store.getMsg(), null, null, profileUuid)); - else log.error("Registration failed: FORBIDDEN, endpointId: [{}]", identity); - } - return store.getSecurityInfo(); + return store.getSecurityInfo(); } - public Map getSession (UUID sessionUuId){ - return this.sessions.entrySet().stream().filter(e -> e.getValue().getSessionUuid().equals(sessionUuId)).collect(Collectors.toMap(map -> map.getKey(), map -> map.getValue())); + public Map getSession(UUID sessionUuId) { + return this.sessions.entrySet().stream().filter(e -> e.getValue().getSessionUuid().equals(sessionUuId)).collect(Collectors.toMap(map -> map.getKey(), map -> map.getValue())); } public Map getSessions() { @@ -193,14 +232,14 @@ public class LwM2mInMemorySecurityStore extends InMemorySecurityStore { return this.profiles; } - public MapsetProfiles(Map profiles) { + public AttrTelemetryObserveValue getProfile(UUID profileUuId) { + return this.profiles.get(profileUuId); + } + + public Map setProfiles(Map profiles) { return this.profiles = profiles; } - /** - * - * @param deviceProfile - */ public boolean addUpdateProfileParameters(DeviceProfile deviceProfile) { JsonObject profilesConfigData = LwM2MTransportHandler.getObserveAttrTelemetryFromThingsboard(deviceProfile); if (profilesConfigData != null) { @@ -208,5 +247,4 @@ public class LwM2mInMemorySecurityStore extends InMemorySecurityStore { } return (profilesConfigData != null); } - } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java index ba3f0dc142..67ce459d9b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java @@ -26,11 +26,19 @@ import org.eclipse.leshan.core.util.StringUtils; import javax.xml.datatype.DatatypeConfigurationException; import javax.xml.datatype.DatatypeFactory; import javax.xml.datatype.XMLGregorianCalendar; +import java.text.DateFormat; +import java.text.SimpleDateFormat; import java.util.Date; @Slf4j public class LwM2mValueConverterImpl implements LwM2mValueConverter { + private static final LwM2mValueConverterImpl INSTANCE = new LwM2mValueConverterImpl(); + + public static LwM2mValueConverterImpl getInstance() { + return INSTANCE; + } + @Override public Object convertValue(Object value, Type currentType, Type expectedType, LwM2mPath resourcePath) throws CodecException { @@ -120,6 +128,11 @@ public class LwM2mValueConverterImpl implements LwM2mValueConverter { case INTEGER: case FLOAT: return String.valueOf(value); + case TIME: + String DATE_FORMAT = "MMM d, yyyy HH:mm a"; + Long timeValue = ((Date) value).getTime(); + DateFormat formatter = new SimpleDateFormat(DATE_FORMAT); + return formatter.format(new Date(timeValue)); default: break; } diff --git a/common/transport/lwm2m/src/main/resources/credentials/serverKeyStore.jks b/common/transport/lwm2m/src/main/resources/credentials/serverKeyStore.jks index b5b6c9fa3c..9336dd1168 100644 Binary files a/common/transport/lwm2m/src/main/resources/credentials/serverKeyStore.jks and b/common/transport/lwm2m/src/main/resources/credentials/serverKeyStore.jks differ diff --git a/common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_credentials.sh b/common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_credentials.sh index 27a1ed845d..3167cca2b5 100755 --- a/common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_credentials.sh +++ b/common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_credentials.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # # Copyright © 2016-2021 The Thingsboard Authors # @@ -15,9 +15,147 @@ # limitations under the License. # +#p) CLIENT_CN=LwX50900000000 +#s) client_start=0 +#f) client_finish=1 +#a) CLIENT_ALIAS=client_alias_00000000 +#b) BOOTSTRAP_ALIAS=bootstrap +#d) SERVER_ALIAS=server +#j) SERVER_STORE=serverKeyStore.jks +#k) CLIENT_STORE=clientKeyStore.jks +#c) CLIENT_STORE_PWD=client_ks_password +#w) SERVER_STORE_PWD=server_ks_password + +#while test $# -gt 0; do +# case "$1" in +# -h|--help) +# echo "$package - attempt to capture frames" +# echo " " +# echo "$package [options] application [arguments]" +# echo " " +# echo "options:" +# echo "-h, --help show brief help" +# echo "-a, --action=ACTION specify an action to use" +# echo "-o, --output-dir=DIR specify a directory to store output in" +# exit 0 +# ;; +# -a) +# shift +# if test $# -gt 0; then +# export PROCESS=$1 +# else +# echo "no process specified" +# exit 1 +# fi +# shift +# ;; +# --action*) +# export PROCESS=`echo $1 | sed -e 's/^[^=]*=//g'` +# shift +# ;; +# -o) +# shift +# if test $# -gt 0; then +# export OUTPUT=$1 +# else +# echo "no output dir specified" +# exit 1 +# fi +# shift +# ;; +# --output-dir*) +# export OUTPUT=`echo $1 | sed -e 's/^[^=]*=//g'` +# shift +# ;; +# *) +# break +# ;; +# esac +#done + + +while getopts p:s:f:a:b:d:j:k:c:w: flag; do + case "${flag}" in + p) client_prefix=${OPTARG} ;; + s) client_start=${OPTARG} ;; + f) client_finish=${OPTARG} ;; + a) client_alias=${OPTARG} ;; + b) bootstrap_alias=${OPTARG} ;; + d) server_alias=${OPTARG} ;; + j) key_store_server_file=${OPTARG} ;; + k) key_store_client_file=${OPTARG} ;; + c) client_key_store_pwd=${OPTARG} ;; + w) server_key_store_pwd=${OPTARG} ;; + esac +done + +# cd to dir of script +script_dir=$(dirname $0) +echo "script_dir: $script_dir" +cd $script_dir # source the properties: . ./lwM2M_keygen.properties + +if [ -n "$client_prefix" ]; then + CLIENT_PREFIX=$client_prefix +fi + +if [ -z "$client_start" ]; then + client_start=0 +fi + +if [ -z "$client_finish" ]; then + client_finish=1 +fi + +if [ -n "$client_alias" ]; then + CLIENT_ALIAS=$client_alias +fi + +if [ -n "$bootstrap_alias" ]; then + BOOTSTRAP_ALIAS=$bootstrap_alias +fi + +if [ -n "$server_alias" ]; then + SERVER_ALIAS=$server_alias +fi + +if [ -n "$key_store_server_file" ]; then + SERVER_STORE=$key_store_server_file +fi + +if [ -n "$key_store_client_file" ]; then + CLIENT_STORE=$key_store_client_file +fi + +if [ -n "$client_key_store_pwd" ]; then + CLIENT_STORE_PWD=$client_key_store_pwd +fi + +if [ -n "$server_key_store_pwd" ]; then + SERVER_STORE_PWD=$server_key_store_pwd +fi + +echo "==Start==" +echo "CLIENT_PREFIX: $CLIENT_PREFIX" +echo "client_start: $client_start" +echo "client_finish: $client_finish" +echo "CLIENT_ALIAS: $CLIENT_ALIAS" +echo "BOOTSTRAP_ALIAS: $BOOTSTRAP_ALIAS" +echo "SERVER_ALIAS: $SERVER_ALIAS" +echo "SERVER_STORE: $SERVER_STORE" +echo "CLIENT_STORE: $CLIENT_STORE" +echo "CLIENT_STORE_PWD: $CLIENT_STORE_PWD" +echo "SERVER_STORE_PWD: $SERVER_STORE_PWD" + +end_point() { + echo "$CLIENT_PREFIX$(printf "%08d" $CLIENT_NUMBER)" +} +client_alias_point() { + echo "$CLIENT_ALIAS$(printf "%08d" $CLIENT_NUMBER)" +} + # Generation of the keystore. echo "${H0}====START========${RESET}" echo "${H1}Server Keystore : ${RESET}" @@ -52,7 +190,7 @@ keytool \ -exportcert \ -alias $SERVER_ALIAS \ -keystore $SERVER_STORE \ - -storepass $SERVER_STORE_PWD | \ + -storepass $SERVER_STORE_PWD | keytool \ -importcert \ -alias $SERVER_SELF_ALIAS \ @@ -67,22 +205,22 @@ keytool \ -alias $SERVER_ALIAS \ -dname "CN=$SERVER_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \ -keystore $SERVER_STORE \ - -storepass $SERVER_STORE_PWD | \ + -storepass $SERVER_STORE_PWD | keytool \ -gencert \ -alias $ROOT_KEY_ALIAS \ -keystore $SERVER_STORE \ -storepass $SERVER_STORE_PWD \ -storetype $STORETYPE \ - -validity $VALIDITY | \ - keytool \ - -importcert \ - -alias $SERVER_ALIAS \ - -keystore $SERVER_STORE \ - -storepass $SERVER_STORE_PWD + -validity $VALIDITY | + keytool \ + -importcert \ + -alias $SERVER_ALIAS \ + -keystore $SERVER_STORE \ + -storepass $SERVER_STORE_PWD echo -echo "${H2}Creating server key and self-signed certificate ...${RESET}" +echo "${H2}Creating bootstrap key and self-signed certificate ...${RESET}" keytool \ -genkeypair \ -alias $BOOTSTRAP_ALIAS \ @@ -97,7 +235,7 @@ keytool \ -exportcert \ -alias $BOOTSTRAP_ALIAS \ -keystore $SERVER_STORE \ - -storepass $SERVER_STORE_PWD | \ + -storepass $SERVER_STORE_PWD | keytool \ -importcert \ -alias $BOOTSTRAP_SELF_ALIAS \ @@ -112,54 +250,53 @@ keytool \ -alias $BOOTSTRAP_ALIAS \ -dname "CN=$BOOTSTRAP_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \ -keystore $SERVER_STORE \ - -storepass $SERVER_STORE_PWD | \ + -storepass $SERVER_STORE_PWD | keytool \ -gencert \ -alias $ROOT_KEY_ALIAS \ -keystore $SERVER_STORE \ -storepass $SERVER_STORE_PWD \ -storetype $STORETYPE \ - -validity $VALIDITY | \ - keytool \ - -importcert \ - -alias $BOOTSTRAP_ALIAS \ - -keystore $SERVER_STORE \ - -storepass $SERVER_STORE_PWD - + -validity $VALIDITY | + keytool \ + -importcert \ + -alias $BOOTSTRAP_ALIAS \ + -keystore $SERVER_STORE \ + -storepass $SERVER_STORE_PWD echo echo "${H1}Client Keystore : ${RESET}" echo "${H1}==================${RESET}" -echo "${H2}Creating client key and self-signed certificate with expected CN...${RESET}" -keytool \ - -genkeypair \ - -alias $CLIENT_ALIAS \ - -keyalg EC \ - -dname "CN=$CLIENT_SELF_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \ - -validity $VALIDITY \ - -storetype $STORETYPE \ - -keypass $CLIENT_STORE_PWD \ - -keystore $CLIENT_STORE \ - -storepass $CLIENT_STORE_PWD -keytool \ - -exportcert \ - -alias $CLIENT_ALIAS \ - -keystore $CLIENT_STORE \ - -storepass $CLIENT_STORE_PWD | \ - keytool \ - -importcert \ - -alias $CLIENT_SELF_ALIAS \ - -keystore $CLIENT_STORE \ - -storepass $CLIENT_STORE_PWD \ - -noprompt +#echo "${H2}Creating client key and self-signed certificate with expected CN...${RESET}" +#keytool \ +# -genkeypair \ +# -alias $CLIENT_ALIAS \ +# -keyalg EC \ +# -dname "CN=$CLIENT_SELF_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \ +# -validity $VALIDITY \ +# -storetype $STORETYPE \ +# -keypass $CLIENT_STORE_PWD \ +# -keystore $CLIENT_STORE \ +# -storepass $CLIENT_STORE_PWD +#keytool \ +# -exportcert \ +# -alias $CLIENT_ALIAS \ +# -keystore $CLIENT_STORE \ +# -storepass $CLIENT_STORE_PWD | \ +# keytool \ +# -importcert \ +# -alias $CLIENT_SELF_ALIAS \ +# -keystore $CLIENT_STORE \ +# -storepass $CLIENT_STORE_PWD \ +# -noprompt echo -echo "${H2}Import root certificate just to be able to import ned by root CA with expected CN...${RESET}" +echo "${H2}Import root certificate just to be able to import need by root CA with expected CN...${RESET}" keytool \ -exportcert \ -alias $ROOT_KEY_ALIAS \ -keystore $SERVER_STORE \ - -storepass $SERVER_STORE_PWD | \ + -storepass $SERVER_STORE_PWD | keytool \ -importcert \ -alias $ROOT_KEY_ALIAS \ @@ -167,27 +304,85 @@ keytool \ -storepass $CLIENT_STORE_PWD \ -noprompt -echo -echo "${H2}Creating client certificate signed by root CA with expected CN...${RESET}" -keytool \ - -certreq \ - -alias $CLIENT_ALIAS \ - -dname "CN=$CLIENT_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \ - -keystore $CLIENT_STORE \ - -storepass $CLIENT_STORE_PWD | \ +#echo +#echo "${H2}Creating client certificate signed by root CA with expected CN...${RESET}" +#keytool \ +# -certreq \ +# -alias $CLIENT_ALIAS \ +# -dname "CN=$CLIENT_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \ +# -keystore $CLIENT_STORE \ +# -storepass $CLIENT_STORE_PWD | \ +# keytool \ +# -gencert \ +# -alias $ROOT_KEY_ALIAS \ +# -keystore $SERVER_STORE \ +# -storepass $SERVER_STORE_PWD \ +# -storetype $STORETYPE \ +# -validity $VALIDITY | \ +# keytool \ +# -importcert \ +# -alias $CLIENT_ALIAS \ +# -keystore $CLIENT_STORE \ +# -storepass $CLIENT_STORE_PWD \ +# -noprompt + +cert_end_point() { + echo "${H2}Creating client key and self-signed certificate with expected CN $CLIENT_SELF_CN ${RESET}" keytool \ - -gencert \ - -alias $ROOT_KEY_ALIAS \ - -keystore $SERVER_STORE \ - -storepass $SERVER_STORE_PWD \ + -genkeypair \ + -alias $CLIENT_CN_ALIAS \ + -keyalg EC \ + -dname "CN=$CLIENT_SELF_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \ + -validity $VALIDITY \ -storetype $STORETYPE \ - -validity $VALIDITY | \ + -keypass $CLIENT_STORE_PWD \ + -keystore $CLIENT_STORE \ + -storepass $CLIENT_STORE_PWD + keytool \ + -exportcert \ + -alias $CLIENT_CN_ALIAS \ + -keystore $CLIENT_STORE \ + -storepass $CLIENT_STORE_PWD | + keytool \ + -importcert \ + -alias $CLIENT_SELF_ALIAS \ + -keystore $CLIENT_STORE \ + -storepass $CLIENT_STORE_PWD \ + -noprompt + + echo + echo "${H2}Creating client certificate signed by root CA with expected $CLIENT_CN_NAME ${RESET}" + keytool \ + -certreq \ + -alias $CLIENT_CN_ALIAS \ + -dname "CN=$CLIENT_CN_NAME, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \ + -keystore $CLIENT_STORE \ + -storepass $CLIENT_STORE_PWD | + keytool \ + -gencert \ + -alias $ROOT_KEY_ALIAS \ + -keystore $SERVER_STORE \ + -storepass $SERVER_STORE_PWD \ + -storetype $STORETYPE \ + -validity $VALIDITY | keytool \ -importcert \ - -alias $CLIENT_ALIAS \ + -alias $CLIENT_CN_ALIAS \ -keystore $CLIENT_STORE \ -storepass $CLIENT_STORE_PWD \ -noprompt +} + +while [ "$CLIENT_NUMBER" != "$client_finish" ]; do + CLIENT_CN_NAME=$(end_point) + CLIENT_CN_ALIAS=$(client_alias_point) + echo "$CLIENT_CN_NAME" + echo "$CLIENT_CN_ALIAS" + cert_end_point + CLIENT_NUMBER=$(($CLIENT_NUMBER + 1)) + echo "number $CLIENT_NUMBER" + echo "finish $client_finish" +done echo echo "${H0}!!! Warning ${H2}Migrate ${H1}${SERVER_STORE} ${H2}to ${H1}PKCS12 ${H2}which is an industry standard format..${RESET}" diff --git a/common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_keygen.properties b/common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_keygen.properties index 8dd2c30a9a..18f851e4dd 100644 --- a/common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_keygen.properties +++ b/common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_keygen.properties @@ -17,7 +17,7 @@ # Keystore common parameters ROOT_KEY_ALIAS=rootCA DOMAIN_SUFFIX="$(hostname)" -ROOT_CN="$DOMAIN_SUFFIX rootCA" +ROOT_CN="$DOMAIN_SUFFIX $ROOT_KEY_ALIAS" ORGANIZATIONAL_UNIT=Thingsboard ORGANIZATION=Thingsboard CITY=SF @@ -27,23 +27,22 @@ VALIDITY=36500 #days STORETYPE="JKS" #Server -SERVER_STORE=serverKeyStore.jks -SERVER_STORE_PWD=server_ks_password -SERVER_ALIAS=server +SERVER_STORE=serverKeyStore1.jks +SERVER_STORE_PWD=server_ks_password1 +SERVER_ALIAS=server1 SERVER_CN="$DOMAIN_SUFFIX server LwM2M signed by root CA" SERVER_SELF_ALIAS=server_self_signed SERVER_SELF_CN="$DOMAIN_SUFFIX server LwM2M self-signed" -BOOTSTRAP_ALIAS=bootstrap +BOOTSTRAP_ALIAS=bootstrap1 BOOTSTRAP_CN="$DOMAIN_SUFFIX bootstrap server LwM2M signed by root CA" BOOTSTRAP_SELF_ALIAS=bootstrap_self_signed BOOTSTRAP_SELF_CN="$DOMAIN_SUFFIX bootstrap server LwM2M self-signed" # Client -CLIENT_STORE=clientKeyStore.jks -CLIENT_STORE_PWD=client_ks_password -CLIENT_ALIAS=client -#CLIENT_CN=client_lwm2m_x509 -CLIENT_CN=mobile_lwm2m_x509 +CLIENT_STORE=clientKeyStore1.jks +CLIENT_STORE_PWD=client_ks_password1 +CLIENT_ALIAS=client_alias_1 +CLIENT_PREFIX=LwX509_ CLIENT_SELF_ALIAS=client_self_signed CLIENT_SELF_CN="$DOMAIN_SUFFIX client LwM2M self-signed" diff --git a/common/transport/lwm2m/src/main/resources/models/LWM2M_Connectivity_Monitoring-v1_0_2.xml b/common/transport/lwm2m/src/main/resources/models/LWM2M_Connectivity_Monitoring-v1_0_2.xml deleted file mode 100644 index 28957f5a5d..0000000000 --- a/common/transport/lwm2m/src/main/resources/models/LWM2M_Connectivity_Monitoring-v1_0_2.xml +++ /dev/null @@ -1,208 +0,0 @@ - - - - - Connectivity Monitoring - - This LwM2M Object enables monitoring of parameters related to network connectivity. In this general connectivity Object, the Resources are limited to the most general cases common to most network bearers. It is recommended to read the description, which refers to relevant standard development organizations (e.g. 3GPP, IEEE). The goal of the Connectivity Monitoring Object is to carry information reflecting the more up to date values of the current connection for monitoring purposes. Resources such as Link Quality, Radio Signal Strength, Cell ID are retrieved during connected mode at least for cellular networks. - - 4 - urn:oma:lwm2m:oma:4 - 1.0 - 1.0 - Single - Optional - - - Network Bearer - R - Single - Mandatory - Integer - 0-50 - - - Indicates the network bearer used for the current LwM2M communication session from the network bearer list below. The number range is split into three categories: 0 - 20 are Cellular Bearers 21 - 40 are Wireless Bearers 41 - 50 are Wireline Bearers More specifically: 0: GSM cellular network 1: TD-SCDMA cellular network 2: WCDMA cellular network 3: CDMA2000 cellular network 4: WiMAX cellular network 5: LTE-TDD cellular network 6: LTE-FDD cellular network 7: NB-IoT 8 - 20: Reserved for other types of cellular network 21: WLAN network 22: Bluetooth network 23: IEEE 802.15.4 network 24 - 40: Reserved for other types of local wireless network 41: Ethernet 42: DSL 43: PLC 44 - 50: reserved for other types of wireline networks. - - - - Available Network Bearer - R - Multiple - Mandatory - Integer - 0-50 - - - Indicates a list of current available network bearer. Each Resource Instance has a value from the network bearer list. - - - - Radio Signal Strength - R - Single - Mandatory - Integer - - dBm - - Indicates the average value of the received signal strength indication used in the current network bearer (as indicated by Resource 0 of this Object). For the following network bearers the signal strength parameters indicated below are represented by this resource: GSM: RSSI UMTS: RSCP LTE: RSRP NB-IoT: NRSRP For more details on Network Measurement Report, refer to the appropriate Cellular or Wireless Network standards, (e.g. for LTE Cellular Network refer to 3GPP TS 36.133 specification). - - - - Link Quality - R - Single - Optional - Integer - - - - This contains received link quality e.g. LQI for IEEE 802.15.4 (range 0...255), RxQual Downlink for GSM (range 0...7, refer to [3GPP 44.018] for more details on Network Measurement Report encoding), RSRQ for LTE, (refer to [3GPP 36.214]), NRSRQ for NB-IoT (refer to [3GPP 36.214]). - - - - IP Addresses - R - Multiple - Mandatory - String - - - - The IP addresses assigned to the connectivity interface. (e.g. IPv4, IPv6, etc.) - - - - Router IP Addresses - R - Multiple - Optional - String - - - - The IP address of the next-hop IP router, on each of the interfaces specified in resource 4 (IP Addresses). Note: This IP Address doesn’t indicate the Server IP address. - - - - Link Utilization - R - Single - Optional - Integer - 0-100 - % - - The percentage indicating the average utilization of the link to the next-hop IP router. - - - - APN - R - Multiple - Optional - String - - - - Access Point Name in case Network Bearer Resource is a Cellular Network. - - - - Cell ID - R - Single - Optional - Integer - - - - Serving Cell ID in case Network Bearer Resource is a Cellular Network. As specified in TS [3GPP 23.003] and in [3GPP. 24.008]. Range (0...65535) in GSM/EDGE UTRAN Cell ID has a length of 28 bits. Cell Identity in WCDMA/TD-SCDMA. Range: (0...268435455). LTE Cell ID has a length of 28 bits. Parameter definitions in [3GPP 25.331]. - - - - SMNC - R - Single - Optional - Integer - 0-999 - % - - Serving Mobile Network Code. This is applicable when the Network Bearer Resource value is referring to a cellular network. As specified in TS [3GPP 23.003]. - - - - SMCC - R - Single - Optional - Integer - 0-999 - - - Serving Mobile Country Code. This is applicable when the Network Bearer Resource value is referring to a cellular network. As specified in TS [3GPP 23.003]. - - - - - - diff --git a/common/transport/lwm2m/src/main/resources/models/LwM2M_BinaryAppDataContainer-v1_0_1.xml b/common/transport/lwm2m/src/main/resources/models/LwM2M_BinaryAppDataContainer-v1_0_1.xml deleted file mode 100644 index b111d3b321..0000000000 --- a/common/transport/lwm2m/src/main/resources/models/LwM2M_BinaryAppDataContainer-v1_0_1.xml +++ /dev/null @@ -1,147 +0,0 @@ - - - - - BinaryAppDataContainer - - 19 - urn:oma:lwm2m:oma:19 - 1.0 - 1.0 - Multiple - Optional - - Data - RW - - - - Multiple - Mandatory - Opaque - - - - - Data Priority - RW - Single - Optional - Integer - 1 bytes - - - - Data Creation Time - RW - Single - Optional - Time - - - - - Data Description - RW - Single - Optional - String - 32 bytes - - - - Data Format - RW - Single - Optional - String - 32 bytes - - - - App ID - RW - Single - Optional - Integer - 2 bytes - - - - - - diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index e7c295b9e6..8a8fcff2a4 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -55,6 +55,7 @@ import org.thingsboard.server.common.transport.auth.SessionInfoCreator; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.common.transport.service.DefaultTransportService; +import org.thingsboard.server.common.transport.service.SessionMetaData; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent; @@ -596,7 +597,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } } - private void checkGatewaySession() { + private void checkGatewaySession(SessionMetaData sessionMetaData) { TransportDeviceInfo device = deviceSessionCtx.getDeviceInfo(); try { JsonNode infoNode = context.getMapper().readTree(device.getAdditionalInfo()); @@ -604,6 +605,9 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement JsonNode gatewayNode = infoNode.get("gateway"); if (gatewayNode != null && gatewayNode.asBoolean()) { gatewaySessionHandler = new GatewaySessionHandler(deviceSessionCtx, sessionId); + if (infoNode.has(DefaultTransportService.OVERWRITE_ACTIVITY_TIME) && infoNode.get(DefaultTransportService.OVERWRITE_ACTIVITY_TIME).isBoolean()) { + sessionMetaData.setOverwriteActivityTime(infoNode.get(DefaultTransportService.OVERWRITE_ACTIVITY_TIME).asBoolean()); + } } } } catch (IOException e) { @@ -639,8 +643,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement transportService.process(deviceSessionCtx.getSessionInfo(), DefaultTransportService.getSessionEventMsg(SessionEvent.OPEN), new TransportServiceCallback() { @Override public void onSuccess(Void msg) { - transportService.registerAsyncSession(deviceSessionCtx.getSessionInfo(), MqttTransportHandler.this); - checkGatewaySession(); + SessionMetaData sessionMetaData = transportService.registerAsyncSession(deviceSessionCtx.getSessionInfo(), MqttTransportHandler.this); + checkGatewaySession(sessionMetaData); ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_ACCEPTED, connectMessage)); log.info("[{}] Client connected!", sessionId); } diff --git a/common/transport/transport-api/pom.xml b/common/transport/transport-api/pom.xml index 5b58201f46..6e8f9e10ec 100644 --- a/common/transport/transport-api/pom.xml +++ b/common/transport/transport-api/pom.xml @@ -114,9 +114,12 @@ org.eclipse.leshan leshan-core - 1.0.1 compile + + org.eclipse.leshan + leshan-server-cf + diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java index 7ac1805621..b835f7ba84 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java @@ -43,8 +43,10 @@ public abstract class TransportContext { @Autowired private TransportService transportService; + @Autowired private TbServiceInfoProvider serviceInfoProvider; + @Autowired private SchedulerComponent scheduler; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java index 9b12675e1c..e642e7f7b2 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java @@ -19,7 +19,7 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; -import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.common.transport.service.SessionMetaData; import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg; @@ -94,11 +94,11 @@ public interface TransportService { void process(SessionInfoProto sessionInfo, ClaimDeviceMsg msg, TransportServiceCallback callback); - void registerAsyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener); + SessionMetaData registerAsyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener); - void registerSyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout); + SessionMetaData registerSyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout); - void reportActivity(SessionInfoProto sessionInfo); + SessionMetaData reportActivity(SessionInfoProto sessionInfo); void deregisterSession(SessionInfoProto sessionInfo); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index 1328e1b29e..d81344fe5c 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -59,7 +59,6 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; -import java.util.UUID; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -430,6 +429,8 @@ public class JsonConverter { case MQTT_BASIC: result.add("credentialsValue", JSON_PARSER.parse(payload.getCredentialsValue()).getAsJsonObject()); break; + case LWM2M_CREDENTIALS: + break; } result.addProperty("credentialsType", payload.getCredentialsType().name()); result.addProperty("status", ProvisionResponseStatus.SUCCESS.name()); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/SessionInfoCreator.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/SessionInfoCreator.java index 235ae6b3a3..ab18b930f9 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/SessionInfoCreator.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/SessionInfoCreator.java @@ -15,16 +15,17 @@ */ package org.thingsboard.server.common.transport.auth; +import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.transport.TransportContext; import org.thingsboard.server.gen.transport.TransportProtos; import java.util.UUID; +@Slf4j public class SessionInfoCreator { public static TransportProtos.SessionInfoProto create(ValidateDeviceCredentialsResponse msg, TransportContext context, UUID sessionId) { - return TransportProtos.SessionInfoProto.newBuilder() - .setNodeId(context.getNodeId()) + return TransportProtos.SessionInfoProto.newBuilder().setNodeId(context.getNodeId()) .setSessionIdMSB(sessionId.getMostSignificantBits()) .setSessionIdLSB(sessionId.getLeastSignificantBits()) .setDeviceIdMSB(msg.getDeviceInfo().getDeviceId().getId().getMostSignificantBits()) diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/lwm2m/LwM2MTransportConfigBootstrap.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/lwm2m/LwM2MTransportConfigBootstrap.java index 05ab1afecf..48f31edc97 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/lwm2m/LwM2MTransportConfigBootstrap.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/lwm2m/LwM2MTransportConfigBootstrap.java @@ -24,7 +24,6 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.gen.transport.TransportProtos; import java.security.PublicKey; -import java.security.cert.X509Certificate; import java.util.Map; @Slf4j @@ -32,39 +31,57 @@ import java.util.Map; @ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || '${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core'") public class LwM2MTransportConfigBootstrap { - @Getter - @Value("${transport.lwm2m.bootstrap.bind_address:}") - private String bootstrapHost; + @Value("${transport.lwm2m.bootstrap.enable:}") + private Boolean bootstrapEnable; @Getter - @Value("${transport.lwm2m.bootstrap.bind_port:}") - private Integer bootstrapPort; + @Value("${transport.lwm2m.bootstrap.id:}") + private Integer bootstrapServerId; @Getter - @Value("${transport.lwm2m.bootstrap.bind_port_cert:}") - private Integer bootstrapPortCert; + @Value("${transport.lwm2m.bootstrap.secure.start_psk:}") + private Boolean bootstrapStartPsk; + @Getter + @Value("${transport.lwm2m.bootstrap.secure.start_rpk:}") + private Boolean bootstrapStartRpk; @Getter - @Value("${transport.lwm2m.bootstrap.secure.start_all:}") - private boolean bootstrapStartAll; + @Value("${transport.lwm2m.bootstrap.secure.start_x509:}") + private Boolean bootstrapStartX509; @Getter - @Value("${transport.lwm2m.bootstrap.secure.dtls_mode:}") - private Integer bootStrapDtlsMode; + @Value("${transport.lwm2m.bootstrap.bind_address:}") + private String bootstrapHost; @Getter @Value("${transport.lwm2m.bootstrap.secure.bind_address:}") private String bootstrapSecureHost; @Getter - @Value("${transport.lwm2m.bootstrap.secure.bind_port:}") - private Integer bootstrapSecurePort; + @Value("${transport.lwm2m.bootstrap.bind_port_no_sec_psk:}") + private Integer bootstrapPortNoSecPsk; + + @Getter + @Value("${transport.lwm2m.bootstrap.bind_port_no_sec_rpk:}") + private Integer bootstrapPortNoSecRpk; @Getter - @Value("${transport.lwm2m.bootstrap.secure.bind_port_cert:}") - private Integer bootstrapSecurePortCert; + @Value("${transport.lwm2m.bootstrap.bind_port_no_sec_x509:}") + private Integer bootstrapPortNoSecX509; + + @Getter + @Value("${transport.lwm2m.bootstrap.secure.bind_port_psk:}") + private Integer bootstrapSecurePortPsk; + + @Getter + @Value("${transport.lwm2m.bootstrap.secure.bind_port_rpk:}") + private Integer bootstrapSecurePortRpk; + + @Getter + @Value("${transport.lwm2m.bootstrap.secure.bind_port_x509:}") + private Integer bootstrapSecurePortX509; @Getter @Value("${transport.lwm2m.bootstrap.secure.public_x:}") @@ -86,10 +103,6 @@ public class LwM2MTransportConfigBootstrap { @Value("${transport.lwm2m.bootstrap.secure.alias:}") private String bootstrapAlias; - @Getter - @Setter - private X509Certificate bootstrapCertificate; - @Getter @Setter private Map sessions; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/lwm2m/LwM2MTransportConfigServer.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/lwm2m/LwM2MTransportConfigServer.java index bc02cce31b..dfe2b75fef 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/lwm2m/LwM2MTransportConfigServer.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/lwm2m/LwM2MTransportConfigServer.java @@ -20,32 +20,33 @@ import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.model.ObjectLoader; import org.eclipse.leshan.core.model.ObjectModel; +import org.eclipse.leshan.core.model.ResourceModel; +import org.eclipse.leshan.core.node.LwM2mPath; +import org.eclipse.leshan.server.registration.Registration; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; -import java.io.*; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; import java.nio.file.Path; import java.nio.file.Paths; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; +import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; @Slf4j @Component @ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || '${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core'") public class LwM2MTransportConfigServer { - @Getter - @Value("${transport.lwm2m.timeout:}") - private Long timeout; - - @Getter - @Value("${transport.sessions.report_timeout}") - private long sessionReportTimeout; @Getter private String MODEL_PATH_DEFAULT = "models"; @@ -77,10 +78,6 @@ public class LwM2MTransportConfigServer { @Getter private String BASE_DIR_PATH = System.getProperty("user.dir"); - @Getter - @Value("${transport.lwm2m.model_path_file:}") - private String modelPathFile; - @Getter // private String PATH_DATA_MICROSERVICE = "/usr/share/tb-lwm2m-transport/data$"; private String PATH_DATA = "data"; @@ -90,8 +87,44 @@ public class LwM2MTransportConfigServer { private List modelsValue; @Getter - @Value("${transport.lwm2m.support_deprecated_ciphers_enable:}") - private boolean supportDeprecatedCiphersEnable; + @Value("${transport.lwm2m.timeout:}") + private Long timeout; + + @Getter + @Value("${transport.sessions.report_timeout}") + private long sessionReportTimeout; + + @Getter + @Value("${transport.lwm2m.model_path_file:}") + private String modelPathFile; + + @Getter + @Value("${transport.lwm2m.recommended_ciphers:}") + private boolean recommendedCiphers; + + @Getter + @Value("${transport.lwm2m.recommended_supported_groups:}") + private boolean recommendedSupportedGroups; + + @Getter + @Value("${transport.lwm2m.request_pool_size:}") + private int requestPoolSize; + + @Getter + @Value("${transport.lwm2m.request_error_pool_size:}") + private int requestErrorPoolSize; + + @Getter + @Value("${transport.lwm2m.registered_pool_size:}") + private int registeredPoolSize; + + @Getter + @Value("${transport.lwm2m.update_registered_pool_size:}") + private int updateRegisteredPoolSize; + + @Getter + @Value("${transport.lwm2m.un_registered_pool_size:}") + private int unRegisteredPoolSize; @Getter @Value("${transport.lwm2m.secure.key_store_type:}") @@ -113,6 +146,18 @@ public class LwM2MTransportConfigServer { @Value("${transport.lwm2m.secure.root_alias:}") private String rootAlias; + @Getter + @Value("${transport.lwm2m.server.secure.start_psk:}") + private boolean serverStartPsk; + + @Getter + @Value("${transport.lwm2m.server.secure.start_rpk:}") + private boolean serverStartRpk; + + @Getter + @Value("${transport.lwm2m.server.secure.start_x509:}") + private boolean serverStartX509; + @Getter @Value("${transport.lwm2m.secure.enable_gen_psk_rpk:}") private Boolean enableGenPskRpk; @@ -122,32 +167,37 @@ public class LwM2MTransportConfigServer { private String serverHost; @Getter - @Value("${transport.lwm2m.server.bind_port:}") - private Integer serverPort; + @Value("${transport.lwm2m.server.id:}") + private Integer serverId; + + @Getter + @Value("${transport.lwm2m.server.secure.bind_address:}") + private String serverSecureHost; + @Getter - @Value("${transport.lwm2m.server.bind_port_cert:}") - private Integer serverPortCert; + @Value("${transport.lwm2m.server.bind_port_no_sec_psk:}") + private Integer serverPortNoSecPsk; @Getter - @Value("${transport.lwm2m.server.secure.start_all:}") - private boolean serverStartAll; + @Value("${transport.lwm2m.server.bind_port_no_sec_rpk:}") + private Integer serverPortNoSecRpk; @Getter - @Value("${transport.lwm2m.server.secure.dtls_mode:}") - private Integer serverDtlsMode; + @Value("${transport.lwm2m.server.bind_port_no_sec_x509:}") + private Integer serverPortNoSecX509; @Getter - @Value("${transport.lwm2m.server.secure.bind_address:}") - private String serverSecureHost; + @Value("${transport.lwm2m.server.secure.bind_port_psk:}") + private Integer serverPortPsk; @Getter - @Value("${transport.lwm2m.server.secure.bind_port:}") - private Integer serverSecurePort; + @Value("${transport.lwm2m.server.secure.bind_port_rpk:}") + private Integer serverPortRpk; @Getter - @Value("${transport.lwm2m.server.secure.bind_port_cert:}") - private Integer serverSecurePortCert; + @Value("${transport.lwm2m.server.secure.bind_port_x509:}") + private Integer serverPortX509; @Getter @Value("${transport.lwm2m.server.secure.public_x:}") @@ -165,10 +215,6 @@ public class LwM2MTransportConfigServer { @Value("${transport.lwm2m.server.secure.alias:}") private String serverAlias; - @Getter - @Value("${transport.lwm2m.bootstrap.enable:}") - private Boolean bootstrapEnable; - @Getter @Value("${transport.lwm2m.secure.redis_url:}") private String redisUrl; @@ -187,7 +233,7 @@ public class LwM2MTransportConfigServer { } else { log.error(" [{}] Read Models", path.getAbsoluteFile()); } - getInKeyStore(); + this.getInKeyStore(); } private File getPathModels() { @@ -238,4 +284,26 @@ public class LwM2MTransportConfigServer { } return FULL_FILE_PATH.toUri().getPath(); } + + public ResourceModel getResourceModel(Registration registration, LwM2mPath pathIds) { + String pathLink = "/" + pathIds.getObjectId() + "/" + pathIds.getObjectInstanceId(); + return (Arrays.stream(registration.getObjectLinks()).filter(p-> p.getUrl().equals(pathLink)).findFirst().isPresent() && + this.modelsValue.stream().filter(v -> v.id == pathIds.getObjectId()).collect(Collectors.toList()).size() > 0) && + this.modelsValue.stream().filter(v -> v.id == pathIds.getObjectId()).collect(Collectors.toList()).get(0).resources.containsKey(pathIds.getResourceId()) ? + this.modelsValue.stream().filter(v -> v.id == pathIds.getObjectId()).collect(Collectors.toList()).get(0).resources.get(pathIds.getResourceId()) : + null; + } + + public ResourceModel.Type getResourceModelType(Registration registration, LwM2mPath pathIds) { + ResourceModel resource = this.getResourceModel(registration, pathIds); + return (resource == null) ? null : resource.type; + } + + public ResourceModel.Operations getOperation(Registration registration, LwM2mPath pathIds) { + ResourceModel resource = this.getResourceModel(registration, pathIds); + return (resource == null) ? ResourceModel.Operations.NONE : resource.operations; + } + + + } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 43afd6d78d..893a93e31a 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -61,6 +61,7 @@ import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; @@ -107,6 +108,8 @@ import java.util.concurrent.atomic.AtomicInteger; @TbTransportComponent public class DefaultTransportService implements TransportService { + public static final String OVERWRITE_ACTIVITY_TIME = "overwriteActivityTime"; + @Value("${transport.sessions.inactivity_timeout}") private long sessionInactivityTimeout; @Value("${transport.sessions.report_timeout}") @@ -233,8 +236,10 @@ public class DefaultTransportService implements TransportService { } @Override - public void registerAsyncSession(TransportProtos.SessionInfoProto sessionInfo, SessionMsgListener listener) { - sessions.putIfAbsent(toSessionId(sessionInfo), new SessionMetaData(sessionInfo, TransportProtos.SessionType.ASYNC, listener)); + public SessionMetaData registerAsyncSession(TransportProtos.SessionInfoProto sessionInfo, SessionMsgListener listener) { + SessionMetaData newValue = new SessionMetaData(sessionInfo, TransportProtos.SessionType.ASYNC, listener); + SessionMetaData oldValue = sessions.putIfAbsent(toSessionId(sessionInfo), newValue); + return oldValue != null ? oldValue : newValue; } @Override @@ -509,8 +514,8 @@ public class DefaultTransportService implements TransportService { } @Override - public void reportActivity(TransportProtos.SessionInfoProto sessionInfo) { - reportActivityInternal(sessionInfo); + public SessionMetaData reportActivity(TransportProtos.SessionInfoProto sessionInfo) { + return reportActivityInternal(sessionInfo); } private SessionMetaData reportActivityInternal(TransportProtos.SessionInfoProto sessionInfo) { @@ -530,7 +535,7 @@ public class DefaultTransportService implements TransportService { if (sessionInfo.getGwSessionIdMSB() != 0 && sessionInfo.getGwSessionIdLSB() != 0) { SessionMetaData gwMetaData = sessions.get(new UUID(sessionInfo.getGwSessionIdMSB(), sessionInfo.getGwSessionIdLSB())); - if (gwMetaData != null) { + if (gwMetaData != null && gwMetaData.isOverwriteActivityTime()) { lastActivityTime = Math.max(gwMetaData.getLastActivityTime(), lastActivityTime); } } @@ -564,7 +569,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void registerSyncSession(TransportProtos.SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout) { + public SessionMetaData registerSyncSession(TransportProtos.SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout) { SessionMetaData currentSession = new SessionMetaData(sessionInfo, TransportProtos.SessionType.SYNC, listener); sessions.putIfAbsent(toSessionId(sessionInfo), currentSession); @@ -574,6 +579,7 @@ public class DefaultTransportService implements TransportService { }, timeout, TimeUnit.MILLISECONDS); currentSession.setScheduledFuture(executorFuture); + return currentSession; } @Override @@ -728,8 +734,13 @@ public class DefaultTransportService implements TransportService { .setDeviceProfileIdMSB(deviceProfileIdMSB) .setDeviceProfileIdLSB(deviceProfileIdLSB) .setDeviceName(device.getName()) - .setDeviceType(device.getType()) - .build(); + .setDeviceType(device.getType()).build(); + if (device.getAdditionalInfo().has("gateway") + && device.getAdditionalInfo().get("gateway").asBoolean() + && device.getAdditionalInfo().has(OVERWRITE_ACTIVITY_TIME) + && device.getAdditionalInfo().get(OVERWRITE_ACTIVITY_TIME).isBoolean()) { + md.setOverwriteActivityTime(device.getAdditionalInfo().get(OVERWRITE_ACTIVITY_TIME).asBoolean()); + } md.setSessionInfo(newSessionInfo); transportCallbackExecutor.submit(() -> md.getListener().onDeviceUpdate(newSessionInfo, device, Optional.ofNullable(newDeviceProfile))); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/SessionMetaData.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/SessionMetaData.java index c81ca05bc1..bb0ed7aa58 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/SessionMetaData.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/SessionMetaData.java @@ -25,7 +25,7 @@ import java.util.concurrent.ScheduledFuture; * Created by ashvayka on 15.10.18. */ @Data -class SessionMetaData { +public class SessionMetaData { private volatile TransportProtos.SessionInfoProto sessionInfo; private final TransportProtos.SessionType sessionType; @@ -36,6 +36,7 @@ class SessionMetaData { private volatile long lastReportedActivityTime; private volatile boolean subscribedToAttributes; private volatile boolean subscribedToRPC; + private volatile boolean overwriteActivityTime; SessionMetaData(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SessionType sessionType, SessionMsgListener listener) { this.sessionInfo = sessionInfo; diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java index dc531c8afb..2bcef7cc88 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java @@ -382,7 +382,9 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ private Set getPropagationEntityIds(Alarm alarm) { if (alarm.isPropagate()) { List relations = relationService.findByTo(alarm.getTenantId(), alarm.getId(), RelationTypeGroup.ALARM); - return relations.stream().map(EntityRelation::getFrom).collect(Collectors.toSet()); + Set propagationEntityIds = relations.stream().map(EntityRelation::getFrom).collect(Collectors.toSet()); + propagationEntityIds.add(alarm.getOriginator()); + return propagationEntityIds; } else { return Collections.singleton(alarm.getOriginator()); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java index 9e0af81823..ccd9bffcf5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java @@ -21,7 +21,6 @@ import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.Device; @@ -87,6 +86,9 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen case MQTT_BASIC: formatSimpleMqttCredentials(deviceCredentials); break; + case LWM2M_CREDENTIALS: + formatSimpleLwm2mCredentials(deviceCredentials); + break; } log.trace("Executing updateDeviceCredentials [{}]", deviceCredentials); credentialsValidator.validate(deviceCredentials, id -> tenantId); @@ -129,6 +131,7 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen deviceCredentials.setCredentialsValue(JacksonUtil.toString(mqttCredentials)); } + private void formatCertData(DeviceCredentials deviceCredentials) { String cert = EncryptionUtil.trimNewLines(deviceCredentials.getCredentialsValue()); String sha3Hash = EncryptionUtil.getSha3Hash(cert); @@ -136,6 +139,10 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen deviceCredentials.setCredentialsValue(cert); } + private void formatSimpleLwm2mCredentials(DeviceCredentials deviceCredentials) { + + } + @Override @CacheEvict(cacheNames = DEVICE_CREDENTIALS_CACHE, key = "'deviceCredentials_' + #deviceCredentials.credentialsId") public void deleteDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index 2c7e985863..ec39ed8e0a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -180,10 +180,40 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe return doSaveDevice(device, null); } + @CacheEvict(cacheNames = DEVICE_CACHE, key = "{#device.tenantId, #device.name}") + @Override + public Device saveDeviceWithCredentials(Device device, DeviceCredentials deviceCredentials) { + if (device.getId() == null) { + Device deviceWithName = this.findDeviceByTenantIdAndName(device.getTenantId(), device.getName()); + device = deviceWithName == null ? device : deviceWithName.updateDevice(device); + } + Device savedDevice = this.saveDeviceWithoutCredentials(device); + deviceCredentials.setDeviceId(savedDevice.getId()); + if (device.getId() == null) { + deviceCredentials = deviceCredentialsService.createDeviceCredentials(savedDevice.getTenantId(), deviceCredentials); + } + else { + deviceCredentials.setId(deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), savedDevice.getId()).getId()); + deviceCredentials = deviceCredentialsService.updateDeviceCredentials(device.getTenantId(), deviceCredentials); + } + return savedDevice; + } + private Device doSaveDevice(Device device, String accessToken) { + Device savedDevice = this.saveDeviceWithoutCredentials(device); + if (device.getId() == null) { + DeviceCredentials deviceCredentials = new DeviceCredentials(); + deviceCredentials.setDeviceId(new DeviceId(savedDevice.getUuidId())); + deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); + deviceCredentials.setCredentialsId(!StringUtils.isEmpty(accessToken) ? accessToken : RandomStringUtils.randomAlphanumeric(20)); + deviceCredentialsService.createDeviceCredentials(device.getTenantId(), deviceCredentials); + } + return savedDevice; + } + + private Device saveDeviceWithoutCredentials(Device device) { log.trace("Executing saveDevice [{}]", device); deviceValidator.validate(device, Device::getTenantId); - Device savedDevice; try { DeviceProfile deviceProfile; if (device.getDeviceProfileId() == null) { @@ -201,8 +231,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe } device.setType(deviceProfile.getName()); device.setDeviceData(syncDeviceData(deviceProfile, device.getDeviceData())); - - savedDevice = deviceDao.save(device.getTenantId(), device); + return deviceDao.save(device.getTenantId(), device); } catch (Exception t) { ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("device_name_unq_key")) { @@ -211,14 +240,6 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe throw t; } } - if (device.getId() == null) { - DeviceCredentials deviceCredentials = new DeviceCredentials(); - deviceCredentials.setDeviceId(new DeviceId(savedDevice.getUuidId())); - deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); - deviceCredentials.setCredentialsId(!StringUtils.isEmpty(accessToken) ? accessToken : RandomStringUtils.randomAlphanumeric(20)); - deviceCredentialsService.createDeviceCredentials(device.getTenantId(), deviceCredentials); - } - return savedDevice; } private DeviceData syncDeviceData(DeviceProfile deviceProfile, DeviceData deviceData) { @@ -515,6 +536,8 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe case X509_CERTIFICATE: deviceCredentials.setCredentialsValue(provisionRequest.getCredentialsData().getX509CertHash()); break; + case LWM2M_CREDENTIALS: + break; } try { deviceCredentialsService.updateDeviceCredentials(savedDevice.getTenantId(), deviceCredentials); @@ -588,7 +611,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe @Override protected void validateCreate(TenantId tenantId, Device device) { DefaultTenantProfileConfiguration profileConfiguration = - (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); + (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); long maxDevices = profileConfiguration.getMaxDevices(); validateNumberOfEntitiesPerTenant(tenantId, deviceDao, maxDevices, EntityType.DEVICE); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java index 1f25a0f290..519092274b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java @@ -186,6 +186,7 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic public UserCredentials requestPasswordReset(TenantId tenantId, String email) { log.trace("Executing requestPasswordReset email [{}]", email); validateString(email, "Incorrect email " + email); + DataValidator.validateEmail(email); User user = userDao.findByEmail(tenantId, email); if (user == null) { throw new IncorrectParameterException(String.format("Unable to find user by email [%s]", email)); diff --git a/netty-mqtt/pom.xml b/netty-mqtt/pom.xml index 46ab5af314..9bd14a9b91 100644 --- a/netty-mqtt/pom.xml +++ b/netty-mqtt/pom.xml @@ -67,11 +67,6 @@ org.apache.maven.plugins maven-compiler-plugin - 3.1 - - 1.8 - 1.8 - org.apache.maven.plugins @@ -87,4 +82,4 @@ - \ No newline at end of file + diff --git a/pom.xml b/pom.xml index dcb8bc52a0..56d848f4c9 100755 --- a/pom.xml +++ b/pom.xml @@ -62,9 +62,10 @@ 2.11.3 2.11.3 2.2.6 - 2.2.3 - 1.0.1 - 1.0.0 + 2.6.0 + 1.3.0 + 1.3.0 + 1.3.0 2.6.2 2.3.30 1.6.2 @@ -578,7 +579,7 @@ org.apache.maven.plugins maven-compiler-plugin - 2.5.1 + 3.8.1 1.8 1.8 @@ -1166,11 +1167,30 @@ leshan-server-redis ${leshan-server.version} + + org.eclipse.leshan + leshan-core + ${leshan-core.version} + + + org.eclipse.californium + californium-core + ${californium.version} + test-jar + test + org.eclipse.californium californium-core ${californium.version} + + org.eclipse.californium + element-connector + ${californium.version} + test-jar + test + com.google.code.gson gson diff --git a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java index 0522983c40..cc7b57fbe8 100644 --- a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java +++ b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java @@ -127,6 +127,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; @@ -1106,6 +1107,24 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { return restTemplate.postForEntity(baseURL + "/api/device/credentials", deviceCredentials, DeviceCredentials.class).getBody(); } + public Optional saveDeviceWithCredentials(Device device, DeviceCredentials credentials) { + try { + Map, Object> deviceCredentials = new ConcurrentHashMap<>(); + deviceCredentials.put(Device.class, device); + deviceCredentials.put(DeviceCredentials.class, credentials); +// return restTemplate.postForEntity(baseURL + "/api/lwm2m/device-credentials", deviceCredentials, Device.class).getBody(); + ResponseEntity deviceOpt = restTemplate.postForEntity(baseURL + "/api/lwm2m/device-credentials", deviceCredentials, Device.class); + return Optional.ofNullable(deviceOpt.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public PageData getTenantDevices(String type, PageLink pageLink) { Map params = new HashMap<>(); params.put("type", type); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/credentials/AnonymousCredentials.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/credentials/AnonymousCredentials.java new file mode 100644 index 0000000000..6670254fd1 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/credentials/AnonymousCredentials.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.rule.engine.credentials; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class AnonymousCredentials implements ClientCredentials { + @Override + public CredentialsType getType() { + return CredentialsType.ANONYMOUS; + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/credentials/AnonymousCredentials.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/credentials/BasicCredentials.java similarity index 61% rename from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/credentials/AnonymousCredentials.java rename to rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/credentials/BasicCredentials.java index 46fe584a63..c958d9300f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/credentials/AnonymousCredentials.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/credentials/BasicCredentials.java @@ -13,23 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.rule.engine.mqtt.credentials; +package org.thingsboard.rule.engine.credentials; -import io.netty.handler.ssl.SslContext; -import org.thingsboard.mqtt.MqttClientConfig; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; -import java.util.Optional; - -public class AnonymousCredentials implements MqttClientCredentials { - - @Override - public Optional initSslContext() { - return Optional.empty(); - } +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class BasicCredentials implements ClientCredentials { + private String username; + private String password; @Override - public void configure(MqttClientConfig config) { - + public CredentialsType getType() { + return CredentialsType.BASIC; } } - diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/credentials/CertPemClientCredentials.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/credentials/CertPemCredentials.java similarity index 90% rename from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/credentials/CertPemClientCredentials.java rename to rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/credentials/CertPemCredentials.java index 18c0b132f6..0a97be3bd0 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/credentials/CertPemClientCredentials.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/credentials/CertPemCredentials.java @@ -13,10 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.rule.engine.mqtt.credentials; +package org.thingsboard.rule.engine.credentials; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import io.netty.handler.ssl.ClientAuth; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import lombok.Data; @@ -29,7 +28,6 @@ import org.bouncycastle.openssl.PEMKeyPair; import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; import org.springframework.util.StringUtils; -import org.thingsboard.mqtt.MqttClientConfig; import javax.crypto.Cipher; import javax.crypto.EncryptedPrivateKeyInfo; @@ -51,13 +49,11 @@ import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.spec.KeySpec; import java.security.spec.PKCS8EncodedKeySpec; -import java.util.Optional; @Data @Slf4j @JsonIgnoreProperties(ignoreUnknown = true) -public class CertPemClientCredentials implements MqttClientCredentials { - +public class CertPemCredentials implements ClientCredentials { private static final String TLS_VERSION = "TLSv1.2"; private String caCert; @@ -66,25 +62,28 @@ public class CertPemClientCredentials implements MqttClientCredentials { private String password; @Override - public Optional initSslContext() { + public CredentialsType getType() { + return CredentialsType.CERT_PEM; + } + + @Override + public SslContext initSslContext() { try { Security.addProvider(new BouncyCastleProvider()); - return Optional.of(SslContextBuilder.forClient() - .keyManager(createAndInitKeyManagerFactory()) - .trustManager(createAndInitTrustManagerFactory()) - .clientAuth(ClientAuth.REQUIRE) - .build()); + SslContextBuilder builder = SslContextBuilder.forClient(); + if (StringUtils.hasLength(caCert)) { + builder.trustManager(createAndInitTrustManagerFactory()); + } + if (StringUtils.hasLength(cert) && StringUtils.hasLength(privateKey)) { + builder.keyManager(createAndInitKeyManagerFactory()); + } + return builder.build(); } catch (Exception e) { log.error("[{}:{}] Creating TLS factory failed!", caCert, cert, e); throw new RuntimeException("Creating TLS factory failed!", e); } } - @Override - public void configure(MqttClientConfig config) { - - } - private KeyManagerFactory createAndInitKeyManagerFactory() throws Exception { X509Certificate certHolder = readCertFile(cert); Object keyObject = readPrivateKeyFile(privateKey); @@ -122,7 +121,7 @@ public class CertPemClientCredentials implements MqttClientCredentials { return keyManagerFactory; } - private TrustManagerFactory createAndInitTrustManagerFactory() throws Exception { + protected TrustManagerFactory createAndInitTrustManagerFactory() throws Exception { X509Certificate caCertHolder; caCertHolder = readCertFile(caCert); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/credentials/MqttClientCredentials.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/credentials/ClientCredentials.java similarity index 66% rename from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/credentials/MqttClientCredentials.java rename to rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/credentials/ClientCredentials.java index 3b6b3b9026..8f4b20a5c4 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/credentials/MqttClientCredentials.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/credentials/ClientCredentials.java @@ -13,29 +13,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.rule.engine.mqtt.credentials; +package org.thingsboard.rule.engine.credentials; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; import io.netty.handler.ssl.SslContext; -import org.thingsboard.mqtt.MqttClientConfig; +import io.netty.handler.ssl.SslContextBuilder; import org.thingsboard.rule.engine.mqtt.azure.AzureIotHubSasCredentials; -import java.util.Optional; +import javax.net.ssl.SSLException; -@JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.PROPERTY, - property = "type") +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") @JsonSubTypes({ @JsonSubTypes.Type(value = AnonymousCredentials.class, name = "anonymous"), @JsonSubTypes.Type(value = BasicCredentials.class, name = "basic"), @JsonSubTypes.Type(value = AzureIotHubSasCredentials.class, name = "sas"), - @JsonSubTypes.Type(value = CertPemClientCredentials.class, name = "cert.PEM")}) -public interface MqttClientCredentials { + @JsonSubTypes.Type(value = CertPemCredentials.class, name = "cert.PEM")}) +public interface ClientCredentials { + @JsonIgnore + CredentialsType getType(); - Optional initSslContext(); - - void configure(MqttClientConfig config); + @JsonIgnore + default SslContext initSslContext() throws SSLException{ + return SslContextBuilder.forClient().build(); + } } - diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances-list.component.scss b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/credentials/CredentialsType.java similarity index 69% rename from ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances-list.component.scss rename to rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/credentials/CredentialsType.java index 1db364b550..a0d298a9be 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances-list.component.scss +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/credentials/CredentialsType.java @@ -13,10 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -:host ::ng-deep { - mat-form-field.lwm2m-instances-list { - & > .mat-form-field-wrapper > .mat-form-field-underline { - display: none; +package org.thingsboard.rule.engine.credentials; + +public enum CredentialsType { + ANONYMOUS("anonymous"), + BASIC("basic"), + SAS("sas"), + CERT_PEM("cert.PEM"); + + private final String label; + + CredentialsType(String label) { + this.label = label; } - } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java index ff83e4823d..629b5cb86c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java @@ -18,7 +18,6 @@ package org.thingsboard.rule.engine.mqtt; import io.netty.buffer.Unpooled; import io.netty.handler.codec.mqtt.MqttQoS; import io.netty.handler.ssl.SslContext; -import io.netty.handler.ssl.SslContextBuilder; import io.netty.util.concurrent.Future; import lombok.extern.slf4j.Slf4j; import org.springframework.util.StringUtils; @@ -31,13 +30,15 @@ import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; +import org.thingsboard.rule.engine.credentials.BasicCredentials; +import org.thingsboard.rule.engine.credentials.ClientCredentials; +import org.thingsboard.rule.engine.credentials.CredentialsType; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import javax.net.ssl.SSLException; import java.nio.charset.Charset; -import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -77,14 +78,14 @@ public class TbMqttNode implements TbNode { String topic = TbNodeUtils.processPattern(this.mqttNodeConfiguration.getTopicPattern(), msg.getMetaData()); this.mqttClient.publish(topic, Unpooled.wrappedBuffer(msg.getData().getBytes(UTF8)), MqttQoS.AT_LEAST_ONCE) .addListener(future -> { - if (future.isSuccess()) { - ctx.tellSuccess(msg); - } else { - TbMsg next = processException(ctx, msg, future.cause()); - ctx.tellFailure(next, future.cause()); - } - } - ); + if (future.isSuccess()) { + ctx.tellSuccess(msg); + } else { + TbMsg next = processException(ctx, msg, future.cause()); + ctx.tellFailure(next, future.cause()); + } + } + ); } private TbMsg processException(TbContext ctx, TbMsg origMsg, Throwable e) { @@ -101,13 +102,13 @@ public class TbMqttNode implements TbNode { } protected MqttClient initClient(TbContext ctx) throws Exception { - Optional sslContextOpt = initSslContext(); - MqttClientConfig config = sslContextOpt.isPresent() ? new MqttClientConfig(sslContextOpt.get()) : new MqttClientConfig(); + MqttClientConfig config = new MqttClientConfig(getSslContext()); if (!StringUtils.isEmpty(this.mqttNodeConfiguration.getClientId())) { config.setClientId(this.mqttNodeConfiguration.getClientId()); } config.setCleanSession(this.mqttNodeConfiguration.isCleanSession()); - this.mqttNodeConfiguration.getCredentials().configure(config); + + prepareMqttClientConfig(config); MqttClient client = MqttClient.create(config, null); client.setEventLoop(ctx.getSharedEventLoop()); Future connectFuture = client.connect(this.mqttNodeConfiguration.getHost(), this.mqttNodeConfiguration.getPort()); @@ -129,12 +130,17 @@ public class TbMqttNode implements TbNode { return client; } - private Optional initSslContext() throws SSLException { - Optional result = this.mqttNodeConfiguration.getCredentials().initSslContext(); - if (this.mqttNodeConfiguration.isSsl() && !result.isPresent()) { - result = Optional.of(SslContextBuilder.forClient().build()); + protected void prepareMqttClientConfig(MqttClientConfig config) throws SSLException { + ClientCredentials credentials = this.mqttNodeConfiguration.getCredentials(); + if (credentials.getType() == CredentialsType.BASIC) { + BasicCredentials basicCredentials = (BasicCredentials) credentials; + config.setUsername(basicCredentials.getUsername()); + config.setPassword(basicCredentials.getPassword()); } - return result; + } + + private SslContext getSslContext() throws SSLException { + return this.mqttNodeConfiguration.isSsl() ? this.mqttNodeConfiguration.getCredentials().initSslContext() : null; } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNodeConfiguration.java index 675fb019c5..e06824180e 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNodeConfiguration.java @@ -17,8 +17,8 @@ package org.thingsboard.rule.engine.mqtt; import lombok.Data; import org.thingsboard.rule.engine.api.NodeConfiguration; -import org.thingsboard.rule.engine.mqtt.credentials.AnonymousCredentials; -import org.thingsboard.rule.engine.mqtt.credentials.MqttClientCredentials; +import org.thingsboard.rule.engine.credentials.AnonymousCredentials; +import org.thingsboard.rule.engine.credentials.ClientCredentials; @Data public class TbMqttNodeConfiguration implements NodeConfiguration { @@ -31,7 +31,7 @@ public class TbMqttNodeConfiguration implements NodeConfiguration initSslContext() { + public SslContext initSslContext() { try { Security.addProvider(new BouncyCastleProvider()); if (caCert == null || caCert.isEmpty()) { caCert = AzureIotHubUtil.getDefaultCaCert(); } - return Optional.of(SslContextBuilder.forClient() + return SslContextBuilder.forClient() .trustManager(createAndInitTrustManagerFactory()) .clientAuth(ClientAuth.REQUIRE) - .build()); + .build(); } catch (Exception e) { log.error("[{}] Creating TLS factory failed!", caCert, e); throw new RuntimeException("Creating TLS factory failed!", e); @@ -60,32 +60,8 @@ public class AzureIotHubSasCredentials implements MqttClientCredentials { } @Override - public void configure(MqttClientConfig config) { + public CredentialsType getType() { + return CredentialsType.SAS; } - private TrustManagerFactory createAndInitTrustManagerFactory() throws Exception { - X509Certificate caCertHolder; - caCertHolder = readCertFile(caCert); - - KeyStore caKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); - caKeyStore.load(null, null); - caKeyStore.setCertificateEntry("caCert-cert", caCertHolder); - - TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init(caKeyStore); - return trustManagerFactory; - } - - private X509Certificate readCertFile(String fileContent) throws Exception { - X509Certificate certificate = null; - if (fileContent != null && !fileContent.trim().isEmpty()) { - fileContent = fileContent.replace("-----BEGIN CERTIFICATE-----", "") - .replace("-----END CERTIFICATE-----", "") - .replaceAll("\\s", ""); - byte[] decoded = Base64.decodeBase64(fileContent); - CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); - certificate = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(decoded)); - } - return certificate; - } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/azure/TbAzureIotHubNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/azure/TbAzureIotHubNode.java index 9d233c3378..9718b10f9c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/azure/TbAzureIotHubNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/azure/TbAzureIotHubNode.java @@ -18,6 +18,7 @@ package org.thingsboard.rule.engine.mqtt.azure; import io.netty.handler.codec.mqtt.MqttVersion; import io.netty.handler.ssl.SslContext; import lombok.extern.slf4j.Slf4j; +import org.springframework.util.StringUtils; import org.thingsboard.common.util.AzureIotHubUtil; import org.thingsboard.mqtt.MqttClientConfig; import org.thingsboard.rule.engine.api.RuleNode; @@ -25,13 +26,15 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; +import org.thingsboard.rule.engine.credentials.BasicCredentials; +import org.thingsboard.rule.engine.credentials.CertPemCredentials; +import org.thingsboard.rule.engine.credentials.ClientCredentials; +import org.thingsboard.rule.engine.credentials.CredentialsType; import org.thingsboard.rule.engine.mqtt.TbMqttNode; import org.thingsboard.rule.engine.mqtt.TbMqttNodeConfiguration; -import org.thingsboard.rule.engine.mqtt.credentials.CertPemClientCredentials; -import org.thingsboard.rule.engine.mqtt.credentials.MqttClientCredentials; import org.thingsboard.server.common.data.plugin.ComponentType; -import java.util.Optional; +import javax.net.ssl.SSLException; @Slf4j @RuleNode( @@ -50,37 +53,25 @@ public class TbAzureIotHubNode extends TbMqttNode { this.mqttNodeConfiguration = TbNodeUtils.convert(configuration, TbMqttNodeConfiguration.class); mqttNodeConfiguration.setPort(8883); mqttNodeConfiguration.setCleanSession(true); - MqttClientCredentials credentials = mqttNodeConfiguration.getCredentials(); - mqttNodeConfiguration.setCredentials(new MqttClientCredentials() { - @Override - public Optional initSslContext() { - if (credentials instanceof AzureIotHubSasCredentials) { - AzureIotHubSasCredentials sasCredentials = (AzureIotHubSasCredentials) credentials; - if (sasCredentials.getCaCert() == null || sasCredentials.getCaCert().isEmpty()) { - sasCredentials.setCaCert(AzureIotHubUtil.getDefaultCaCert()); - } - } else if (credentials instanceof CertPemClientCredentials) { - CertPemClientCredentials pemCredentials = (CertPemClientCredentials) credentials; - if (pemCredentials.getCaCert() == null || pemCredentials.getCaCert().isEmpty()) { - pemCredentials.setCaCert(AzureIotHubUtil.getDefaultCaCert()); - } - } - return credentials.initSslContext(); + ClientCredentials credentials = mqttNodeConfiguration.getCredentials(); + if (CredentialsType.CERT_PEM == credentials.getType()) { + CertPemCredentials pemCredentials = (CertPemCredentials) credentials; + if (pemCredentials.getCaCert() == null || pemCredentials.getCaCert().isEmpty()) { + pemCredentials.setCaCert(AzureIotHubUtil.getDefaultCaCert()); } - - @Override - public void configure(MqttClientConfig config) { - config.setProtocolVersion(MqttVersion.MQTT_3_1_1); - config.setUsername(AzureIotHubUtil.buildUsername(mqttNodeConfiguration.getHost(), config.getClientId())); - if (credentials instanceof AzureIotHubSasCredentials) { - AzureIotHubSasCredentials sasCredentials = (AzureIotHubSasCredentials) credentials; - config.setPassword(AzureIotHubUtil.buildSasToken(mqttNodeConfiguration.getHost(), sasCredentials.getSasKey())); - } - } - }); - + } this.mqttClient = initClient(ctx); } catch (Exception e) { throw new TbNodeException(e); - } } + } + } + + protected void prepareMqttClientConfig(MqttClientConfig config) throws SSLException { + config.setProtocolVersion(MqttVersion.MQTT_3_1_1); + config.setUsername(AzureIotHubUtil.buildUsername(mqttNodeConfiguration.getHost(), config.getClientId())); + ClientCredentials credentials = mqttNodeConfiguration.getCredentials(); + if (CredentialsType.SAS == credentials.getType()) { + config.setPassword(AzureIotHubUtil.buildSasToken(mqttNodeConfiguration.getHost(), ((AzureIotHubSasCredentials) credentials).getSasKey())); + } + } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/azure/TbAzureIotHubNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/azure/TbAzureIotHubNodeConfiguration.java index ebc88768af..5da95b30d1 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/azure/TbAzureIotHubNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/azure/TbAzureIotHubNodeConfiguration.java @@ -16,10 +16,7 @@ package org.thingsboard.rule.engine.mqtt.azure; import lombok.Data; -import org.thingsboard.rule.engine.api.NodeConfiguration; import org.thingsboard.rule.engine.mqtt.TbMqttNodeConfiguration; -import org.thingsboard.rule.engine.mqtt.credentials.AnonymousCredentials; -import org.thingsboard.rule.engine.mqtt.credentials.MqttClientCredentials; @Data public class TbAzureIotHubNodeConfiguration extends TbMqttNodeConfiguration { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/credentials/BasicCredentials.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/credentials/BasicCredentials.java deleted file mode 100644 index 69334f2e8f..0000000000 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/credentials/BasicCredentials.java +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright © 2016-2021 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.rule.engine.mqtt.credentials; - -import io.netty.handler.ssl.SslContext; -import lombok.Data; -import org.thingsboard.mqtt.MqttClientConfig; - -import java.util.Optional; - -@Data -public class BasicCredentials implements MqttClientCredentials { - - private String username; - private String password; - - @Override - public Optional initSslContext() { - return Optional.empty(); - } - - @Override - public void configure(MqttClientConfig config) { - config.setUsername(username); - config.setPassword(password); - } - -} - diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index ed613dd55f..e864167c0d 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java @@ -291,4 +291,11 @@ class AlarmState { } return updated; } + + public void processAckAlarm(Alarm alarm) { + if (currentAlarm != null && currentAlarm.getId().equals(alarm.getId())) { + currentAlarm.setStatus(alarm.getStatus()); + currentAlarm.setAckTs(alarm.getAckTs()); + } + } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java index e078df6156..13dd0ea66d 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java @@ -140,6 +140,8 @@ class DeviceState { stateChanged = processAttributesDeleteNotification(ctx, msg); } else if (msg.getType().equals(DataConstants.ALARM_CLEAR)) { stateChanged = processAlarmClearNotification(ctx, msg); + } else if (msg.getType().equals(DataConstants.ALARM_ACK)) { + processAlarmAckNotification(ctx, msg); } else { ctx.tellSuccess(msg); } @@ -161,6 +163,16 @@ class DeviceState { return stateChanged; } + private void processAlarmAckNotification(TbContext ctx, TbMsg msg) { + Alarm alarmNf = JacksonUtil.fromString(msg.getData(), Alarm.class); + for (DeviceProfileAlarm alarm : deviceProfile.getAlarmSettings()) { + AlarmState alarmState = alarmStates.computeIfAbsent(alarm.getId(), + a -> new AlarmState(this.deviceProfile, deviceId, alarm, getOrInitPersistedAlarmState(alarm))); + alarmState.processAckAlarm(alarmNf); + } + ctx.tellSuccess(msg); + } + private boolean processAttributesUpdateNotification(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException { Set attributes = JsonConverter.convertToAttributes(new JsonParser().parse(msg.getData())); String scope = msg.getMetaData().getValue("scope"); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java index 562a6b9709..17dc6b5bce 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java @@ -17,9 +17,9 @@ package org.thingsboard.rule.engine.rest; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.handler.ssl.SslContextBuilder; import lombok.Data; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.binary.Base64; import org.apache.http.HttpHost; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; @@ -35,6 +35,7 @@ import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.http.client.HttpComponentsAsyncClientHttpRequestFactory; import org.springframework.http.client.Netty4ClientHttpRequestFactory; +import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.util.StringUtils; import org.springframework.util.concurrent.ListenableFuture; import org.springframework.util.concurrent.ListenableFutureCallback; @@ -44,6 +45,9 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.TbRelationTypes; import org.thingsboard.rule.engine.api.util.TbNodeUtils; +import org.thingsboard.rule.engine.credentials.BasicCredentials; +import org.thingsboard.rule.engine.credentials.ClientCredentials; +import org.thingsboard.rule.engine.credentials.CredentialsType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -51,8 +55,10 @@ import javax.net.ssl.SSLContext; import javax.net.ssl.SSLException; import java.net.Authenticator; import java.net.PasswordAuthentication; +import java.nio.charset.StandardCharsets; import java.security.NoSuchAlgorithmException; import java.util.Deque; +import java.util.Optional; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.TimeUnit; @@ -129,11 +135,14 @@ public class TbHttpClient { requestFactory.setReadTimeout(config.getReadTimeoutMs()); httpClient = new AsyncRestTemplate(requestFactory); } else if (config.isUseSimpleClientHttpFactory()) { + if (CredentialsType.CERT_PEM == config.getCredentials().getType()) { + throw new TbNodeException("Simple HTTP Factory does not support CERT PEM credentials!"); + } httpClient = new AsyncRestTemplate(); } else { this.eventLoopGroup = new NioEventLoopGroup(); Netty4ClientHttpRequestFactory nettyFactory = new Netty4ClientHttpRequestFactory(this.eventLoopGroup); - nettyFactory.setSslContext(SslContextBuilder.forClient().build()); + nettyFactory.setSslContext(config.getCredentials().initSslContext()); nettyFactory.setReadTimeout(config.getReadTimeoutMs()); httpClient = new AsyncRestTemplate(nettyFactory); } @@ -226,6 +235,13 @@ public class TbHttpClient { private HttpHeaders prepareHeaders(TbMsgMetaData metaData) { HttpHeaders headers = new HttpHeaders(); config.getHeaders().forEach((k, v) -> headers.add(TbNodeUtils.processPattern(k, metaData), TbNodeUtils.processPattern(v, metaData))); + ClientCredentials credentials = config.getCredentials(); + if (CredentialsType.BASIC == credentials.getType()) { + BasicCredentials basicCredentials = (BasicCredentials) credentials; + String authString = basicCredentials.getUsername() + ":" + basicCredentials.getPassword(); + String encodedAuthString = new String(Base64.encodeBase64(authString.getBytes(StandardCharsets.UTF_8))); + headers.add("Authorization", "Basic " + encodedAuthString); + } return headers; } @@ -259,4 +275,5 @@ public class TbHttpClient { throw new TbNodeException("Proxy port out of range:" + proxyPort); } } + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java index 92a8c59b6c..40b79163b8 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java @@ -18,6 +18,8 @@ package org.thingsboard.rule.engine.rest; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.Data; import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.rule.engine.credentials.AnonymousCredentials; +import org.thingsboard.rule.engine.credentials.ClientCredentials; import java.util.Collections; import java.util.Map; @@ -42,6 +44,7 @@ public class TbRestApiCallNodeConfiguration implements NodeConfiguration=0;l--)(a=e[l])&&(i=(o<3?a(i):o>3?a(t,r,i):a(t,r))||i);return o>3&&i&&Object.defineProperty(t,r,i),i}function h(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)}Object.create;function C(e){var t="function"==typeof Symbol&&Symbol.iterator,r=t&&e[t],n=0;if(r)return r.call(e);if(e&&"number"==typeof e.length)return{next:function(){return e&&n>=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}Object.create;var v,F=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.emptyConfigForm},r.prototype.onConfigurationSet=function(e){this.emptyConfigForm=this.fb.group({})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-node-empty-config",template:"
"}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),x=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.attributeScopes=Object.keys(a.AttributeScope),n.telemetryTypeTranslationsMap=a.telemetryTypeTranslations,n}return y(r,e),r.prototype.configForm=function(){return this.attributesConfigForm},r.prototype.onConfigurationSet=function(e){this.attributesConfigForm=this.fb.group({scope:[e?e.scope:null,[i.Validators.required]],notifyDevice:[!e||e.scope,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-attributes-config",template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.notify-device\' | translate }}\n \n
tb.rulenode.notify-device-hint
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),T=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.timeseriesConfigForm},r.prototype.onConfigurationSet=function(e){this.timeseriesConfigForm=this.fb.group({defaultTTL:[e?e.defaultTTL:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-timeseries-config",template:'
\n \n tb.rulenode.default-ttl\n \n \n {{ \'tb.rulenode.default-ttl-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-default-ttl-message\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),q=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.rpcRequestConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcRequestConfigForm=this.fb.group({timeoutInSeconds:[e?e.timeoutInSeconds:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rpc-request-config",template:'
\n \n tb.rulenode.timeout-sec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-message\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),S=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.logConfigForm},r.prototype.onConfigurationSet=function(e){this.logConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.logConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"string",this.translate.instant("tb.rulenode.to-string"),"ToString",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.logConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-log-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),I=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.assignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.assignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],createCustomerIfNotExists:[!!e&&e.createCustomerIfNotExists,[]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-assign-to-customer-config",template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.create-customer-if-not-exists\' | translate }}\n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),k=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.clearAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.clearAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],alarmType:[e?e.alarmType:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.clearAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.clearAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-clear-alarm-config",template:'
\n \n \n \n
\n \n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),N=function(e){function r(t,r,n,o){var i=e.call(this,t)||this;return i.store=t,i.fb=r,i.nodeScriptTestService=n,i.translate=o,i.alarmSeverities=Object.keys(a.AlarmSeverity),i.alarmSeverityTranslationMap=a.alarmSeverityTranslations,i.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],i}return y(r,e),r.prototype.configForm=function(){return this.createAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.createAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],useMessageAlarmData:[!!e&&e.useMessageAlarmData,[]],alarmType:[e?e.alarmType:null,[]],severity:[e?e.severity:null,[]],propagate:[!!e&&e.propagate,[]],relationTypes:[e?e.relationTypes:null,[]]})},r.prototype.validatorTriggers=function(){return["useMessageAlarmData"]},r.prototype.updateValidators=function(e){this.createAlarmConfigForm.get("useMessageAlarmData").value?(this.createAlarmConfigForm.get("alarmType").setValidators([]),this.createAlarmConfigForm.get("severity").setValidators([])):(this.createAlarmConfigForm.get("alarmType").setValidators([i.Validators.required]),this.createAlarmConfigForm.get("severity").setValidators([i.Validators.required])),this.createAlarmConfigForm.get("alarmType").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("severity").updateValueAndValidity({emitEvent:e})},r.prototype.testScript=function(){var e=this,t=this.createAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.createAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.removeKey=function(e,t){var r=this.createAlarmConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.createAlarmConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.createAlarmConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.createAlarmConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-create-alarm-config",template:'
\n \n \n \n
\n \n
\n \n {{ \'tb.rulenode.use-message-alarm-data\' | translate }}\n \n
\n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \n \n \n tb.rulenode.alarm-severity\n \n \n {{ alarmSeverityTranslationMap.get(severity) | translate }}\n \n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \n \n
\n \n {{ \'tb.rulenode.propagate\' | translate }}\n \n
\n \n tb.rulenode.relation-types-list\n \n \n {{key}}\n close\n \n \n \n \n \n
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),V=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return y(r,e),r.prototype.configForm=function(){return this.createRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.createRelationConfigForm=this.fb.group({direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[i.Validators.required]],entityNamePattern:[e?e.entityNamePattern:null,[]],entityTypePattern:[e?e.entityTypePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],createEntityIfNotExists:[!!e&&e.createEntityIfNotExists,[]],removeCurrentRelations:[!!e&&e.removeCurrentRelations,[]],changeOriginatorToRelatedEntity:[!!e&&e.changeOriginatorToRelatedEntity,[]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["entityType"]},r.prototype.updateValidators=function(e){var t=this.createRelationConfigForm.get("entityType").value;t?this.createRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.createRelationConfigForm.get("entityNamePattern").setValidators([]),!t||t!==a.EntityType.DEVICE&&t!==a.EntityType.ASSET?this.createRelationConfigForm.get("entityTypePattern").setValidators([]):this.createRelationConfigForm.get("entityTypePattern").setValidators([i.Validators.required]),this.createRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e}),this.createRelationConfigForm.get("entityTypePattern").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-create-relation-config",template:'
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.entity-type-pattern\n \n \n {{ \'tb.rulenode.entity-type-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.create-entity-if-not-exists\' | translate }}\n \n
tb.rulenode.create-entity-if-not-exists-hint
\n
\n \n {{ \'tb.rulenode.remove-current-relations\' | translate }}\n \n
tb.rulenode.remove-current-relations-hint
\n \n {{ \'tb.rulenode.change-originator-to-related-entity\' | translate }}\n \n
tb.rulenode.change-originator-to-related-entity-hint
\n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),E=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.msgDelayConfigForm},r.prototype.onConfigurationSet=function(e){this.msgDelayConfigForm=this.fb.group({useMetadataPeriodInSecondsPatterns:[!!e&&e.useMetadataPeriodInSecondsPatterns,[]],periodInSeconds:[e?e.periodInSeconds:null,[]],periodInSecondsPattern:[e?e.periodInSecondsPattern:null,[]],maxPendingMsgs:[e?e.maxPendingMsgs:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(1e5)]]})},r.prototype.validatorTriggers=function(){return["useMetadataPeriodInSecondsPatterns"]},r.prototype.updateValidators=function(e){this.msgDelayConfigForm.get("useMetadataPeriodInSecondsPatterns").value?(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([i.Validators.required]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([])):(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([i.Validators.required,i.Validators.min(0)])),this.msgDelayConfigForm.get("periodInSecondsPattern").updateValueAndValidity({emitEvent:e}),this.msgDelayConfigForm.get("periodInSeconds").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-msg-delay-config",template:'
\n \n {{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-period-in-seconds-patterns-hint
\n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-0-seconds-message\' | translate }}\n \n \n \n \n tb.rulenode.period-in-seconds-pattern\n \n \n {{ \'tb.rulenode.period-in-seconds-pattern-required\' | translate }}\n \n \n \n \n \n tb.rulenode.max-pending-messages\n \n \n {{ \'tb.rulenode.max-pending-messages-required\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),A=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return y(r,e),r.prototype.configForm=function(){return this.deleteRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.deleteRelationConfigForm=this.fb.group({deleteForSingleEntity:[!!e&&e.deleteForSingleEntity,[]],direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["deleteForSingleEntity","entityType"]},r.prototype.updateValidators=function(e){var t=this.deleteRelationConfigForm.get("deleteForSingleEntity").value,r=this.deleteRelationConfigForm.get("entityType").value;t?this.deleteRelationConfigForm.get("entityType").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityType").setValidators([]),t&&r?this.deleteRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityNamePattern").setValidators([]),this.deleteRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:!1}),this.deleteRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-delete-relation-config",template:'
\n \n {{ \'tb.rulenode.delete-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.delete-relation-hint
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),L=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.generatorConfigForm},r.prototype.onConfigurationSet=function(e){this.generatorConfigForm=this.fb.group({msgCount:[e?e.msgCount:null,[i.Validators.required,i.Validators.min(0)]],periodInSeconds:[e?e.periodInSeconds:null,[i.Validators.required,i.Validators.min(1)]],originator:[e?e.originator:null,[]],jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.prepareInputConfig=function(e){return e&&(e.originatorId&&e.originatorType?e.originator={id:e.originatorId,entityType:e.originatorType}:e.originator=null,delete e.originatorId,delete e.originatorType),e},r.prototype.prepareOutputConfig=function(e){return e.originator?(e.originatorId=e.originator.id,e.originatorType=e.originator.entityType):(e.originatorId=null,e.originatorType=null),delete e.originator,e},r.prototype.testScript=function(){var e=this,t=this.generatorConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"generate",this.translate.instant("tb.rulenode.generator"),"Generate",["prevMsg","prevMetadata","prevMsgType"],this.ruleNodeId).subscribe((function(t){t&&e.generatorConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-generator-config",template:'
\n \n tb.rulenode.message-count\n \n \n {{ \'tb.rulenode.message-count-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-message-count-message\' | translate }}\n \n \n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-seconds-message\' | translate }}\n \n \n
\n \n \n \n
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent);!function(e){e.CUSTOMER="CUSTOMER",e.TENANT="TENANT",e.RELATED="RELATED",e.ALARM_ORIGINATOR="ALARM_ORIGINATOR"}(v||(v={}));var M,P=new Map([[v.CUSTOMER,"tb.rulenode.originator-customer"],[v.TENANT,"tb.rulenode.originator-tenant"],[v.RELATED,"tb.rulenode.originator-related"],[v.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator"]]);!function(e){e.CIRCLE="CIRCLE",e.POLYGON="POLYGON"}(M||(M={}));var R,w=new Map([[M.CIRCLE,"tb.rulenode.perimeter-circle"],[M.POLYGON,"tb.rulenode.perimeter-polygon"]]);!function(e){e.MILLISECONDS="MILLISECONDS",e.SECONDS="SECONDS",e.MINUTES="MINUTES",e.HOURS="HOURS",e.DAYS="DAYS"}(R||(R={}));var O,D=new Map([[R.MILLISECONDS,"tb.rulenode.time-unit-milliseconds"],[R.SECONDS,"tb.rulenode.time-unit-seconds"],[R.MINUTES,"tb.rulenode.time-unit-minutes"],[R.HOURS,"tb.rulenode.time-unit-hours"],[R.DAYS,"tb.rulenode.time-unit-days"]]);!function(e){e.METER="METER",e.KILOMETER="KILOMETER",e.FOOT="FOOT",e.MILE="MILE",e.NAUTICAL_MILE="NAUTICAL_MILE"}(O||(O={}));var K,B=new Map([[O.METER,"tb.rulenode.range-unit-meter"],[O.KILOMETER,"tb.rulenode.range-unit-kilometer"],[O.FOOT,"tb.rulenode.range-unit-foot"],[O.MILE,"tb.rulenode.range-unit-mile"],[O.NAUTICAL_MILE,"tb.rulenode.range-unit-nautical-mile"]]);!function(e){e.TITLE="TITLE",e.COUNTRY="COUNTRY",e.STATE="STATE",e.ZIP="ZIP",e.ADDRESS="ADDRESS",e.ADDRESS2="ADDRESS2",e.PHONE="PHONE",e.EMAIL="EMAIL",e.ADDITIONAL_INFO="ADDITIONAL_INFO"}(K||(K={}));var U,j,H,G=new Map([[K.TITLE,"tb.rulenode.entity-details-title"],[K.COUNTRY,"tb.rulenode.entity-details-country"],[K.STATE,"tb.rulenode.entity-details-state"],[K.ZIP,"tb.rulenode.entity-details-zip"],[K.ADDRESS,"tb.rulenode.entity-details-address"],[K.ADDRESS2,"tb.rulenode.entity-details-address2"],[K.PHONE,"tb.rulenode.entity-details-phone"],[K.EMAIL,"tb.rulenode.entity-details-email"],[K.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]);!function(e){e.FIRST="FIRST",e.LAST="LAST",e.ALL="ALL"}(U||(U={})),function(e){e.ASC="ASC",e.DESC="DESC"}(j||(j={})),function(e){e.STANDARD="STANDARD",e.FIFO="FIFO"}(H||(H={}));var z,$=new Map([[H.STANDARD,"tb.rulenode.sqs-queue-standard"],[H.FIFO,"tb.rulenode.sqs-queue-fifo"]]),Q=["anonymous","basic","cert.PEM"],_=new Map([["anonymous","tb.rulenode.credentials-anonymous"],["basic","tb.rulenode.credentials-basic"],["cert.PEM","tb.rulenode.credentials-pem"]]),W=["sas","cert.PEM"],J=new Map([["sas","tb.rulenode.credentials-sas"],["cert.PEM","tb.rulenode.credentials-pem"]]);!function(e){e.GET="GET",e.POST="POST",e.PUT="PUT",e.DELETE="DELETE"}(z||(z={}));var Y=["US-ASCII","ISO-8859-1","UTF-8","UTF-16BE","UTF-16LE","UTF-16"],Z=new Map([["US-ASCII","tb.rulenode.charset-us-ascii"],["ISO-8859-1","tb.rulenode.charset-iso-8859-1"],["UTF-8","tb.rulenode.charset-utf-8"],["UTF-16BE","tb.rulenode.charset-utf-16be"],["UTF-16LE","tb.rulenode.charset-utf-16le"],["UTF-16","tb.rulenode.charset-utf-16"]]),X=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=M,n.perimeterTypes=Object.keys(M),n.perimeterTypeTranslationMap=w,n.rangeUnits=Object.keys(O),n.rangeUnitTranslationMap=B,n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=D,n}return y(r,e),r.prototype.configForm=function(){return this.geoActionConfigForm},r.prototype.onConfigurationSet=function(e){this.geoActionConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType:null,[]],centerLatitude:[e?e.centerLatitude:null,[]],centerLongitude:[e?e.centerLatitude:null,[]],range:[e?e.range:null,[]],rangeUnit:[e?e.rangeUnit:null,[]],polygonsDefinition:[e?e.polygonsDefinition:null,[]],minInsideDuration:[e?e.minInsideDuration:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minInsideDurationTimeUnit:[e?e.minInsideDurationTimeUnit:null,[i.Validators.required]],minOutsideDuration:[e?e.minOutsideDuration:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minOutsideDurationTimeUnit:[e?e.minOutsideDurationTimeUnit:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoActionConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoActionConfigForm.get("perimeterType").value;t?this.geoActionConfigForm.get("perimeterType").setValidators([]):this.geoActionConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==M.CIRCLE?(this.geoActionConfigForm.get("centerLatitude").setValidators([]),this.geoActionConfigForm.get("centerLongitude").setValidators([]),this.geoActionConfigForm.get("range").setValidators([]),this.geoActionConfigForm.get("rangeUnit").setValidators([])):(this.geoActionConfigForm.get("centerLatitude").setValidators([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoActionConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoActionConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoActionConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==M.POLYGON?this.geoActionConfigForm.get("polygonsDefinition").setValidators([]):this.geoActionConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoActionConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),this.geoActionConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-gps-geofencing-config",template:'
\n \n tb.rulenode.latitude-key-name\n \n \n {{ \'tb.rulenode.latitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.longitude-key-name\n \n \n {{ \'tb.rulenode.longitude-key-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.circle-center-latitude\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n tb.rulenode.circle-center-longitude\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.range\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n tb.rulenode.range-units\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.min-inside-duration\n \n \n {{ \'tb.rulenode.min-inside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-inside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.min-outside-duration\n \n \n {{ \'tb.rulenode.min-outside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-outside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ee=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.msgCountConfigForm},r.prototype.onConfigurationSet=function(e){this.msgCountConfigForm=this.fb.group({interval:[e?e.interval:null,[i.Validators.required,i.Validators.min(1)]],telemetryPrefix:[e?e.telemetryPrefix:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-msg-count-config",template:'
\n \n tb.rulenode.interval-seconds\n \n \n {{ \'tb.rulenode.interval-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-interval-seconds-message\' | translate }}\n \n \n \n tb.rulenode.output-timeseries-key-prefix\n \n \n {{ \'tb.rulenode.output-timeseries-key-prefix-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.rpcReplyConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcReplyConfigForm=this.fb.group({requestIdMetaDataAttribute:[e?e.requestIdMetaDataAttribute:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rpc-reply-config",template:'
\n \n tb.rulenode.request-id-metadata-attribute\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),re=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.saveToCustomTableConfigForm},r.prototype.onConfigurationSet=function(e){this.saveToCustomTableConfigForm=this.fb.group({tableName:[e?e.tableName:null,[i.Validators.required]],fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-custom-table-config",template:'
\n \n tb.rulenode.custom-table-name\n \n \n {{ \'tb.rulenode.custom-table-name-required\' | translate }}\n \n \n \n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ne=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.translate=r,o.injector=n,o.fb=a,o.propagateChange=null,o.valueChangeSubscription=null,o}var a;return y(r,e),a=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){this.ngControl=this.injector.get(i.NgControl),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({}),this.kvListFormGroup.addControl("keyVals",this.fb.array([]))},r.prototype.keyValsFormArray=function(){return this.kvListFormGroup.get("keyVals")},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t,r,n=this;this.valueChangeSubscription&&this.valueChangeSubscription.unsubscribe();var a=[];if(e)try{for(var o=C(Object.keys(e)),l=o.next();!l.done;l=o.next()){var s=l.value;Object.prototype.hasOwnProperty.call(e,s)&&a.push(this.fb.group({key:[s,[i.Validators.required]],value:[e[s],[i.Validators.required]]}))}}catch(e){t={error:e}}finally{try{l&&!l.done&&(r=o.return)&&r.call(o)}finally{if(t)throw t.error}}this.kvListFormGroup.setControl("keyVals",this.fb.array(a)),this.valueChangeSubscription=this.kvListFormGroup.valueChanges.subscribe((function(){n.updateModel()}))},r.prototype.removeKeyVal=function(e){this.kvListFormGroup.get("keyVals").removeAt(e)},r.prototype.addKeyVal=function(){this.kvListFormGroup.get("keyVals").push(this.fb.group({key:["",[i.Validators.required]],value:["",[i.Validators.required]]}))},r.prototype.validate=function(e){return!this.kvListFormGroup.get("keyVals").value.length&&this.required?{kvMapRequired:!0}:this.kvListFormGroup.valid?null:{kvFieldsRequired:!0}},r.prototype.updateModel=function(){var e=this.kvListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{var t={};e.forEach((function(e){t[e.key]=e.value})),this.propagateChange(t)}},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:t.Injector},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",String)],r.prototype,"requiredText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"keyText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"keyRequiredText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"valText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"valRequiredText",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=a=b([t.Component({selector:"tb-kv-map-config",template:'
\n
\n {{ keyText }}\n {{ valText }}\n \n
\n
\n
\n \n \n \n \n {{ keyRequiredText | translate }}\n \n \n \n \n \n \n {{ valRequiredText | translate }}\n \n \n \n
\n
\n \n
\n \n
\n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return a})),multi:!0},{provide:i.NG_VALIDATORS,useExisting:t.forwardRef((function(){return a})),multi:!0}],styles:[":host .tb-kv-map-config{margin-bottom:16px}:host .tb-kv-map-config .header{padding-left:5px;padding-right:5px;padding-bottom:5px}:host .tb-kv-map-config .header .cell{padding-left:5px;padding-right:5px;color:rgba(0,0,0,.54);font-size:12px;font-weight:700;white-space:nowrap}:host .tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:20px;max-height:300px;overflow:auto}:host .tb-kv-map-config .body .row{padding-top:5px;max-height:40px}:host .tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell{margin:0;max-height:40px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell .mat-form-field-infix{border-top:0}:host ::ng-deep .tb-kv-map-config .body button.mat-button{margin:0}"]}),h("design:paramtypes",[o.Store,n.TranslateService,t.Injector,i.FormBuilder])],r)}(a.PageComponent),ae=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n.propagateChange=null,n}var n;return y(r,e),n=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.deviceRelationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[i.Validators.required]],maxLevel:[null,[]],relationType:[null],deviceTypes:[null,[i.Validators.required]]}),this.deviceRelationsQueryFormGroup.valueChanges.subscribe((function(t){e.deviceRelationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.deviceRelationsQueryFormGroup.disable({emitEvent:!1}):this.deviceRelationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.deviceRelationsQueryFormGroup.reset(e,{emitEvent:!1})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=b([t.Component({selector:"tb-device-relations-query-config",template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-type
\n \n \n
device.device-types
\n \n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),oe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.propagateChange=null,n}var n;return y(r,e),n=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.relationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[i.Validators.required]],maxLevel:[null,[]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((function(t){e.relationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.relationsQueryFormGroup.reset(e||{},{emitEvent:!1})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=b([t.Component({selector:"tb-relations-query-config",template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-filters
\n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),ie=function(e){function r(t,r,n,o){var i,l,s=e.call(this,t)||this;s.store=t,s.translate=r,s.truncate=n,s.fb=o,s.placeholder="tb.rulenode.message-type",s.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],s.messageTypes=[],s.messageTypesList=[],s.searchText="",s.propagateChange=function(e){},s.messageTypeConfigForm=s.fb.group({messageType:[null]});try{for(var u=C(Object.keys(a.MessageType)),d=u.next();!d.done;d=u.next()){var p=d.value;s.messageTypesList.push({name:a.messageTypeNames.get(a.MessageType[p]),value:p})}}catch(e){i={error:e}}finally{try{d&&!d.done&&(l=u.return)&&l.call(u)}finally{if(i)throw i.error}}return s}var l;return y(r,e),l=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.ngOnInit=function(){var e=this;this.filteredMessageTypes=this.messageTypeConfigForm.get("messageType").valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(t){return e.fetchMessageTypes(t)})),f.share())},r.prototype.ngAfterViewInit=function(){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.messageTypeConfigForm.disable({emitEvent:!1}):this.messageTypeConfigForm.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t=this;this.searchText="",this.messageTypes.length=0,e&&e.forEach((function(e){var r=t.messageTypesList.find((function(t){return t.value===e}));r?t.messageTypes.push({name:r.name,value:r.value}):t.messageTypes.push({name:e,value:e})}))},r.prototype.displayMessageTypeFn=function(e){return e?e.name:void 0},r.prototype.textIsNotEmpty=function(e){return!!(e&&null!=e&&e.length>0)},r.prototype.createMessageType=function(e,t){e.preventDefault(),this.transformMessageType(t)},r.prototype.add=function(e){this.transformMessageType(e.value)},r.prototype.fetchMessageTypes=function(e){if(this.searchText=e,this.searchText&&this.searchText.length){var t=this.searchText.toUpperCase();return c.of(this.messageTypesList.filter((function(e){return e.name.toUpperCase().includes(t)})))}return c.of(this.messageTypesList)},r.prototype.transformMessageType=function(e){if((e||"").trim()){var t=null,r=e.trim(),n=this.messageTypesList.find((function(e){return e.name===r}));(t=n?{name:n.name,value:n.value}:{name:r,value:r})&&this.addMessageType(t)}this.clear("")},r.prototype.remove=function(e){var t=this.messageTypes.indexOf(e);t>=0&&(this.messageTypes.splice(t,1),this.updateModel())},r.prototype.selected=function(e){this.addMessageType(e.option.value),this.clear("")},r.prototype.addMessageType=function(e){-1===this.messageTypes.findIndex((function(t){return t.value===e.value}))&&(this.messageTypes.push(e),this.updateModel())},r.prototype.onFocus=function(){this.messageTypeConfigForm.get("messageType").updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.messageTypeInput.nativeElement.value=e,this.messageTypeConfigForm.get("messageType").patchValue(null,{emitEvent:!0}),setTimeout((function(){t.messageTypeInput.nativeElement.blur(),t.messageTypeInput.nativeElement.focus()}),0)},r.prototype.updateModel=function(){var e=this.messageTypes.map((function(e){return e.value}));this.required?(this.chipList.errorState=!e.length,this.propagateChange(e.length>0?e:null)):(this.chipList.errorState=!1,this.propagateChange(e))},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:a.TruncatePipe},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),b([t.Input(),h("design:type",String)],r.prototype,"label",void 0),b([t.Input(),h("design:type",Object)],r.prototype,"placeholder",void 0),b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.ViewChild("chipList",{static:!1}),h("design:type",d.MatChipList)],r.prototype,"chipList",void 0),b([t.ViewChild("messageTypeAutocomplete",{static:!1}),h("design:type",p.MatAutocomplete)],r.prototype,"matAutocomplete",void 0),b([t.ViewChild("messageTypeInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"messageTypeInput",void 0),r=l=b([t.Component({selector:"tb-message-types-config",template:'\n {{ label }}\n \n \n {{messageType.name}}\n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-message-types-found\n
\n \n \n {{ translate.get(\'tb.rulenode.no-message-type-matching\',\n {messageType: truncate.transform(searchText, true, 6, '...')}) | async }}\n \n \n \n tb.rulenode.create-new-message-type\n \n
\n
\n
\n \n {{ \'tb.rulenode.message-types-required\' | translate }}\n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return l})),multi:!0}]}),h("design:paramtypes",[o.Store,n.TranslateService,a.TruncatePipe,i.FormBuilder])],r)}(a.PageComponent),le=function(){function e(){}return e=b([t.NgModule({declarations:[ne,ae,oe,ie],imports:[r.CommonModule,a.SharedModule,l.HomeComponentsModule],exports:[ne,ae,oe,ie]})],e)}(),se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.unassignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.unassignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-un-assign-to-customer-config",template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.snsConfigForm},r.prototype.onConfigurationSet=function(e){this.snsConfigForm=this.fb.group({topicArnPattern:[e?e.topicArnPattern:null,[i.Validators.required]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-sns-config",template:'
\n \n tb.rulenode.topic-arn-pattern\n \n \n {{ \'tb.rulenode.topic-arn-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ue=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.sqsQueueType=H,n.sqsQueueTypes=Object.keys(H),n.sqsQueueTypeTranslationsMap=$,n}return y(r,e),r.prototype.configForm=function(){return this.sqsConfigForm},r.prototype.onConfigurationSet=function(e){this.sqsConfigForm=this.fb.group({queueType:[e?e.queueType:null,[i.Validators.required]],queueUrlPattern:[e?e.queueUrlPattern:null,[i.Validators.required]],delaySeconds:[e?e.delaySeconds:null,[i.Validators.min(0),i.Validators.max(900)]],messageAttributes:[e?e.messageAttributes:null,[]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-sqs-config",template:'
\n \n tb.rulenode.queue-type\n \n \n {{ sqsQueueTypeTranslationsMap.get(type) | translate }}\n \n \n \n \n tb.rulenode.queue-url-pattern\n \n \n {{ \'tb.rulenode.queue-url-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.delay-seconds\n \n \n {{ \'tb.rulenode.min-delay-seconds-message\' | translate }}\n \n \n {{ \'tb.rulenode.max-delay-seconds-message\' | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),de=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.pubSubConfigForm},r.prototype.onConfigurationSet=function(e){this.pubSubConfigForm=this.fb.group({projectId:[e?e.projectId:null,[i.Validators.required]],topicName:[e?e.topicName:null,[i.Validators.required]],serviceAccountKey:[e?e.serviceAccountKey:null,[i.Validators.required]],serviceAccountKeyFileName:[e?e.serviceAccountKeyFileName:null,[i.Validators.required]],messageAttributes:[e?e.messageAttributes:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-pub-sub-config",template:'
\n \n tb.rulenode.gcp-project-id\n \n \n {{ \'tb.rulenode.gcp-project-id-required\' | translate }}\n \n \n \n tb.rulenode.pubsub-topic-name\n \n \n {{ \'tb.rulenode.pubsub-topic-name-required\' | translate }}\n \n \n \n \n \n
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),pe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.ackValues=["all","-1","0","1"],n.ToByteStandartCharsetTypesValues=Y,n.ToByteStandartCharsetTypeTranslationMap=Z,n}return y(r,e),r.prototype.configForm=function(){return this.kafkaConfigForm},r.prototype.onConfigurationSet=function(e){this.kafkaConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],bootstrapServers:[e?e.bootstrapServers:null,[i.Validators.required]],retries:[e?e.retries:null,[i.Validators.min(0)]],batchSize:[e?e.batchSize:null,[i.Validators.min(0)]],linger:[e?e.linger:null,[i.Validators.min(0)]],bufferMemory:[e?e.bufferMemory:null,[i.Validators.min(0)]],acks:[e?e.acks:null,[i.Validators.required]],keySerializer:[e?e.keySerializer:null,[i.Validators.required]],valueSerializer:[e?e.valueSerializer:null,[i.Validators.required]],otherProperties:[e?e.otherProperties:null,[]],addMetadataKeyValuesAsKafkaHeaders:[!!e&&e.addMetadataKeyValuesAsKafkaHeaders,[]],kafkaHeadersCharset:[e?e.kafkaHeadersCharset:null,[]]})},r.prototype.validatorTriggers=function(){return["addMetadataKeyValuesAsKafkaHeaders"]},r.prototype.updateValidators=function(e){this.kafkaConfigForm.get("addMetadataKeyValuesAsKafkaHeaders").value?this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([i.Validators.required]):this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([]),this.kafkaConfigForm.get("kafkaHeadersCharset").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-kafka-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \n \n tb.rulenode.bootstrap-servers\n \n \n {{ \'tb.rulenode.bootstrap-servers-required\' | translate }}\n \n \n \n tb.rulenode.retries\n \n \n {{ \'tb.rulenode.min-retries-message\' | translate }}\n \n \n \n tb.rulenode.batch-size-bytes\n \n \n {{ \'tb.rulenode.min-batch-size-bytes-message\' | translate }}\n \n \n \n tb.rulenode.linger-ms\n \n \n {{ \'tb.rulenode.min-linger-ms-message\' | translate }}\n \n \n \n tb.rulenode.buffer-memory-bytes\n \n \n {{ \'tb.rulenode.min-buffer-memory-bytes-message\' | translate }}\n \n \n \n tb.rulenode.acks\n \n \n {{ ackValue }}\n \n \n \n \n tb.rulenode.key-serializer\n \n \n {{ \'tb.rulenode.key-serializer-required\' | translate }}\n \n \n \n tb.rulenode.value-serializer\n \n \n {{ \'tb.rulenode.value-serializer-required\' | translate }}\n \n \n \n \n \n \n {{ \'tb.rulenode.add-metadata-key-values-as-kafka-headers\' | translate }}\n \n
tb.rulenode.add-metadata-key-values-as-kafka-headers-hint
\n \n tb.rulenode.charset-encoding\n \n \n {{ ToByteStandartCharsetTypeTranslationMap.get(charset) | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ce=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allMqttCredentialsTypes=Q,n.mqttCredentialsTypeTranslationsMap=_,n}return y(r,e),r.prototype.configForm=function(){return this.mqttConfigForm},r.prototype.onConfigurationSet=function(e){this.mqttConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(200)]],clientId:[e?e.clientId:null,[]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[i.Validators.required]],username:[e&&e.credentials?e.credentials.username:null,[]],password:[e&&e.credentials?e.credentials.password:null,[]],caCert:[e&&e.credentials?e.credentials.caCert:null,[]],caCertFileName:[e&&e.credentials?e.credentials.caCertFileName:null,[]],privateKey:[e&&e.credentials?e.credentials.privateKey:null,[]],privateKeyFileName:[e&&e.credentials?e.credentials.privateKeyFileName:null,[]],cert:[e&&e.credentials?e.credentials.cert:null,[]],certFileName:[e&&e.credentials?e.credentials.certFileName:null,[]]})})},r.prototype.prepareOutputConfig=function(e){var t=e.credentials.type;switch(t){case"anonymous":e.credentials={type:t};break;case"basic":e.credentials={type:t,username:e.credentials.username,password:e.credentials.password};break;case"cert.PEM":delete e.credentials.username}return e},r.prototype.validatorTriggers=function(){return["credentials.type"]},r.prototype.updateValidators=function(e){var t=this.mqttConfigForm.get("credentials"),r=t.get("type").value;switch(e&&t.reset({type:r},{emitEvent:!1}),t.get("username").setValidators([]),t.get("password").setValidators([]),t.get("caCert").setValidators([]),t.get("caCertFileName").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),r){case"anonymous":break;case"basic":t.get("username").setValidators([i.Validators.required]),t.get("password").setValidators([i.Validators.required]);break;case"cert.PEM":t.get("caCert").setValidators([i.Validators.required]),t.get("caCertFileName").setValidators([i.Validators.required]),t.get("privateKey").setValidators([i.Validators.required]),t.get("privateKeyFileName").setValidators([i.Validators.required]),t.get("cert").setValidators([i.Validators.required]),t.get("certFileName").setValidators([i.Validators.required])}t.get("username").updateValueAndValidity({emitEvent:e}),t.get("password").updateValueAndValidity({emitEvent:e}),t.get("caCert").updateValueAndValidity({emitEvent:e}),t.get("caCertFileName").updateValueAndValidity({emitEvent:e}),t.get("privateKey").updateValueAndValidity({emitEvent:e}),t.get("privateKeyFileName").updateValueAndValidity({emitEvent:e}),t.get("cert").updateValueAndValidity({emitEvent:e}),t.get("certFileName").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-mqtt-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n \n tb.rulenode.connect-timeout\n \n \n {{ \'tb.rulenode.connect-timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n
\n \n tb.rulenode.client-id\n \n \n \n {{ \'tb.rulenode.clean-session\' | translate }}\n \n \n {{ \'tb.rulenode.enable-ssl\' | translate }}\n \n \n \n tb.rulenode.credentials\n \n {{ mqttCredentialsTypeTranslationsMap.get(mqttConfigForm.get(\'credentials\').get(\'type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ mqttCredentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.username\n \n \n {{ \'tb.rulenode.username-required\' | translate }}\n \n \n \n tb.rulenode.password\n \n \n {{ \'tb.rulenode.password-required\' | translate }}\n \n \n \n \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n
\n
\n
\n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),fe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],n}return y(r,e),r.prototype.configForm=function(){return this.rabbitMqConfigForm},r.prototype.onConfigurationSet=function(e){this.rabbitMqConfigForm=this.fb.group({exchangeNamePattern:[e?e.exchangeNamePattern:null,[]],routingKeyPattern:[e?e.routingKeyPattern:null,[]],messageProperties:[e?e.messageProperties:null,[]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],virtualHost:[e?e.virtualHost:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]],automaticRecoveryEnabled:[!!e&&e.automaticRecoveryEnabled,[]],connectionTimeout:[e?e.connectionTimeout:null,[i.Validators.min(0)]],handshakeTimeout:[e?e.handshakeTimeout:null,[i.Validators.min(0)]],clientProperties:[e?e.clientProperties:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rabbit-mq-config",template:'
\n \n tb.rulenode.exchange-name-pattern\n \n \n \n tb.rulenode.routing-key-pattern\n \n \n \n tb.rulenode.message-properties\n \n \n {{ property }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n
\n \n tb.rulenode.virtual-host\n \n \n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n \n {{ \'tb.rulenode.automatic-recovery\' | translate }}\n \n \n tb.rulenode.connection-timeout-ms\n \n \n {{ \'tb.rulenode.min-connection-timeout-ms-message\' | translate }}\n \n \n \n tb.rulenode.handshake-timeout-ms\n \n \n {{ \'tb.rulenode.min-handshake-timeout-ms-message\' | translate }}\n \n \n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ge=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.proxySchemes=["http","https"],n.httpRequestTypes=Object.keys(z),n}return y(r,e),r.prototype.configForm=function(){return this.restApiCallConfigForm},r.prototype.onConfigurationSet=function(e){this.restApiCallConfigForm=this.fb.group({restEndpointUrlPattern:[e?e.restEndpointUrlPattern:null,[i.Validators.required]],requestMethod:[e?e.requestMethod:null,[i.Validators.required]],useSimpleClientHttpFactory:[!!e&&e.useSimpleClientHttpFactory,[]],enableProxy:[!!e&&e.enableProxy,[]],useSystemProxyProperties:[!!e&&e.enableProxy,[]],proxyScheme:[e?e.proxyHost:null,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],readTimeoutMs:[e?e.readTimeoutMs:null,[]],maxParallelRequestsCount:[e?e.maxParallelRequestsCount:null,[i.Validators.min(0)]],headers:[e?e.headers:null,[]],useRedisQueueForMsgPersistence:[!!e&&e.useRedisQueueForMsgPersistence,[]],trimQueue:[!!e&&e.trimQueue,[]],maxQueueSize:[e?e.maxQueueSize:null,[]]})},r.prototype.validatorTriggers=function(){return["useSimpleClientHttpFactory","useRedisQueueForMsgPersistence","enableProxy","useSystemProxyProperties"]},r.prototype.updateValidators=function(e){var t=this.restApiCallConfigForm.get("useSimpleClientHttpFactory").value,r=this.restApiCallConfigForm.get("useRedisQueueForMsgPersistence").value,n=this.restApiCallConfigForm.get("enableProxy").value,a=this.restApiCallConfigForm.get("useSystemProxyProperties").value;n&&!a?(this.restApiCallConfigForm.get("proxyHost").setValidators(n?[i.Validators.required]:[]),this.restApiCallConfigForm.get("proxyPort").setValidators(n?[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]:[])):(this.restApiCallConfigForm.get("proxyHost").setValidators([]),this.restApiCallConfigForm.get("proxyPort").setValidators([]),t?this.restApiCallConfigForm.get("readTimeoutMs").setValidators([]):this.restApiCallConfigForm.get("readTimeoutMs").setValidators([i.Validators.min(0)])),r?this.restApiCallConfigForm.get("maxQueueSize").setValidators([i.Validators.min(0)]):this.restApiCallConfigForm.get("maxQueueSize").setValidators([]),this.restApiCallConfigForm.get("readTimeoutMs").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("maxQueueSize").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rest-api-call-config",template:'
\n \n tb.rulenode.endpoint-url-pattern\n \n \n {{ \'tb.rulenode.endpoint-url-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.request-method\n \n \n {{ requestType }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n \n {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}\n \n
\n \n {{ \'tb.rulenode.use-system-proxy-properties\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-scheme\n \n \n {{ proxyScheme }}\n \n \n \n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n
\n \n tb.rulenode.read-timeout\n \n \n \n \n tb.rulenode.max-parallel-requests-count\n \n \n \n \n
\n \n \n \n {{ \'tb.rulenode.use-redis-queue\' | translate }}\n \n
\n \n {{ \'tb.rulenode.trim-redis-queue\' | translate }}\n \n \n tb.rulenode.redis-queue-max-size\n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ye=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.smtpProtocols=["smtp","smtps"],n.tlsVersions=["TLSv1","TLSv1.1","TLSv1.2","TLSv1.3"],n}return y(r,e),r.prototype.configForm=function(){return this.sendEmailConfigForm},r.prototype.onConfigurationSet=function(e){this.sendEmailConfigForm=this.fb.group({useSystemSmtpSettings:[!!e&&e.useSystemSmtpSettings,[]],smtpProtocol:[e?e.smtpProtocol:null,[]],smtpHost:[e?e.smtpHost:null,[]],smtpPort:[e?e.smtpPort:null,[]],timeout:[e?e.timeout:null,[]],enableTls:[!!e&&e.enableTls,[]],tlsVersion:[e?e.tlsVersion:null,[]],enableProxy:[!!e&&e.enableProxy,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]]})},r.prototype.validatorTriggers=function(){return["useSystemSmtpSettings","enableProxy"]},r.prototype.updateValidators=function(e){var t=this.sendEmailConfigForm.get("useSystemSmtpSettings").value,r=this.sendEmailConfigForm.get("enableProxy").value;t?(this.sendEmailConfigForm.get("smtpProtocol").setValidators([]),this.sendEmailConfigForm.get("smtpHost").setValidators([]),this.sendEmailConfigForm.get("smtpPort").setValidators([]),this.sendEmailConfigForm.get("timeout").setValidators([]),this.sendEmailConfigForm.get("proxyHost").setValidators([]),this.sendEmailConfigForm.get("proxyPort").setValidators([])):(this.sendEmailConfigForm.get("smtpProtocol").setValidators([i.Validators.required]),this.sendEmailConfigForm.get("smtpHost").setValidators([i.Validators.required]),this.sendEmailConfigForm.get("smtpPort").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]),this.sendEmailConfigForm.get("timeout").setValidators([i.Validators.required,i.Validators.min(0)]),this.sendEmailConfigForm.get("proxyHost").setValidators(r?[i.Validators.required]:[]),this.sendEmailConfigForm.get("proxyPort").setValidators(r?[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]:[])),this.sendEmailConfigForm.get("smtpProtocol").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpPort").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("timeout").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-send-email-config",template:'
\n \n {{ \'tb.rulenode.use-system-smtp-settings\' | translate }}\n \n
\n \n tb.rulenode.smtp-protocol\n \n \n {{ smtpProtocol.toUpperCase() }}\n \n \n \n
\n \n tb.rulenode.smtp-host\n \n \n {{ \'tb.rulenode.smtp-host-required\' | translate }}\n \n \n \n tb.rulenode.smtp-port\n \n \n {{ \'tb.rulenode.smtp-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.timeout-msec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-msec-message\' | translate }}\n \n \n \n {{ \'tb.rulenode.enable-tls\' | translate }}\n \n \n tb.rulenode.tls-version\n \n \n {{ tlsVersion }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),be=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.serviceType=a.ServiceType.TB_RULE_ENGINE,n}return y(r,e),r.prototype.configForm=function(){return this.checkPointConfigForm},r.prototype.onConfigurationSet=function(e){this.checkPointConfigForm=this.fb.group({queueName:[e?e.queueName:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-check-point-config",template:'
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),he=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allAzureIotHubCredentialsTypes=W,n.azureIotHubCredentialsTypeTranslationsMap=J,n}return y(r,e),r.prototype.configForm=function(){return this.azureIotHubConfigForm},r.prototype.onConfigurationSet=function(e){this.azureIotHubConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(200)]],clientId:[e?e.clientId:null,[i.Validators.required]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[i.Validators.required]],sasKey:[e&&e.credentials?e.credentials.sasKey:null,[]],caCert:[e&&e.credentials?e.credentials.caCert:null,[]],caCertFileName:[e&&e.credentials?e.credentials.caCertFileName:null,[]],privateKey:[e&&e.credentials?e.credentials.privateKey:null,[]],privateKeyFileName:[e&&e.credentials?e.credentials.privateKeyFileName:null,[]],cert:[e&&e.credentials?e.credentials.cert:null,[]],certFileName:[e&&e.credentials?e.credentials.certFileName:null,[]],password:[e&&e.credentials?e.credentials.password:null,[]]})})},r.prototype.prepareOutputConfig=function(e){var t=e.credentials.type;return"sas"===t&&(e.credentials={type:t,sasKey:e.credentials.sasKey,caCert:e.credentials.caCert,caCertFileName:e.credentials.caCertFileName}),e},r.prototype.validatorTriggers=function(){return["credentials.type"]},r.prototype.updateValidators=function(e){var t=this.azureIotHubConfigForm.get("credentials"),r=t.get("type").value;switch(e&&t.reset({type:r},{emitEvent:!1}),t.get("sasKey").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),r){case"sas":t.get("sasKey").setValidators([i.Validators.required]);break;case"cert.PEM":t.get("privateKey").setValidators([i.Validators.required]),t.get("privateKeyFileName").setValidators([i.Validators.required]),t.get("cert").setValidators([i.Validators.required]),t.get("certFileName").setValidators([i.Validators.required])}t.get("sasKey").updateValueAndValidity({emitEvent:e}),t.get("privateKey").updateValueAndValidity({emitEvent:e}),t.get("privateKeyFileName").updateValueAndValidity({emitEvent:e}),t.get("cert").updateValueAndValidity({emitEvent:e}),t.get("certFileName").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-azure-iot-hub-config",template:'
\n \n tb.rulenode.topic\n \n \n {{ \'tb.rulenode.topic-required\' | translate }}\n \n \n \n tb.rulenode.hostname\n \n \n {{ \'tb.rulenode.hostname-required\' | translate }}\n \n \n \n tb.rulenode.device-id\n \n \n {{ \'tb.rulenode.device-id-required\' | translate }}\n \n \n \n \n \n tb.rulenode.credentials\n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(azureIotHubConfigForm.get(\'credentials.type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.sas-key\n \n \n {{ \'tb.rulenode.sas-key-required\' | translate }}\n \n \n \n \n \n \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n
\n
\n
\n
\n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ce=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.deviceProfile},r.prototype.onConfigurationSet=function(e){this.deviceProfile=this.fb.group({persistAlarmRulesState:[!!e&&e.persistAlarmRulesState,i.Validators.required],fetchAlarmRulesStateOnStart:[!!e&&e.fetchAlarmRulesStateOnStart,i.Validators.required]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-device-profile-config",template:'
\n \n {{ \'tb.rulenode.persist-alarm-rules\' | translate }}\n \n \n {{ \'tb.rulenode.fetch-alarm-rules\' | translate }}\n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ve=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.sendSmsConfigForm},r.prototype.onConfigurationSet=function(e){this.sendSmsConfigForm=this.fb.group({numbersToTemplate:[e?e.numbersToTemplate:null,[i.Validators.required]],smsMessageTemplate:[e?e.smsMessageTemplate:null,[i.Validators.required]],useSystemSmsSettings:[!!e&&e.useSystemSmsSettings,[]],smsProviderConfiguration:[e?e.smsProviderConfiguration:null,[]]})},r.prototype.validatorTriggers=function(){return["useSystemSmsSettings"]},r.prototype.updateValidators=function(e){this.sendSmsConfigForm.get("useSystemSmsSettings").value?this.sendSmsConfigForm.get("smsProviderConfiguration").setValidators([]):this.sendSmsConfigForm.get("smsProviderConfiguration").setValidators([i.Validators.required]),this.sendSmsConfigForm.get("smsProviderConfiguration").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-send-sms-config",template:'
\n \n tb.rulenode.numbers-to-template\n \n \n {{ \'tb.rulenode.numbers-to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.sms-message-template\n \n \n {{ \'tb.rulenode.sms-message-template-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.use-system-sms-settings\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Fe=function(){function e(){}return e=b([t.NgModule({declarations:[x,T,q,S,I,k,N,V,E,A,L,X,ee,te,re,se,me,ue,de,pe,ce,fe,ge,ye,be,he,Ce,ve],imports:[r.CommonModule,a.SharedModule,l.HomeComponentsModule,le],exports:[x,T,q,S,I,k,N,V,E,A,L,X,ee,te,re,se,me,ue,de,pe,ce,fe,ge,ye,be,he,Ce,ve]})],e)}(),xe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.checkMessageConfigForm},r.prototype.onConfigurationSet=function(e){this.checkMessageConfigForm=this.fb.group({messageNames:[e?e.messageNames:null,[]],metadataNames:[e?e.metadataNames:null,[]],checkAllKeys:[!!e&&e.checkAllKeys,[]]})},r.prototype.validateConfig=function(){var e=this.checkMessageConfigForm.get("messageNames").value,t=this.checkMessageConfigForm.get("metadataNames").value;return e.length>0||t.length>0},r.prototype.removeMessageName=function(e){var t=this.checkMessageConfigForm.get("messageNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("messageNames").setValue(t,{emitEvent:!0}))},r.prototype.removeMetadataName=function(e){var t=this.checkMessageConfigForm.get("metadataNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("metadataNames").setValue(t,{emitEvent:!0}))},r.prototype.addMessageName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("messageNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("messageNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.prototype.addMetadataName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("metadataNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("metadataNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-check-message-config",template:'
\n \n \n \n \n \n {{messageName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n \n \n \n \n {{metadataName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n {{ \'tb.rulenode.check-all-keys\' | translate }}\n \n
tb.rulenode.check-all-keys-hint
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.entitySearchDirection=Object.keys(a.EntitySearchDirection),n.entitySearchDirectionTranslationsMap=a.entitySearchDirectionTranslations,n}return y(r,e),r.prototype.configForm=function(){return this.checkRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.checkRelationConfigForm=this.fb.group({checkForSingleEntity:[!!e&&e.checkForSingleEntity,[]],direction:[e?e.direction:null,[]],entityType:[e?e.entityType:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],entityId:[e?e.entityId:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],relationType:[e?e.relationType:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["checkForSingleEntity"]},r.prototype.updateValidators=function(e){var t=this.checkRelationConfigForm.get("checkForSingleEntity").value;this.checkRelationConfigForm.get("entityType").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:e}),this.checkRelationConfigForm.get("entityId").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityId").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-check-relation-config",template:'
\n \n {{ \'tb.rulenode.check-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.check-relation-hint
\n \n relation.direction\n \n \n {{ entitySearchDirectionTranslationsMap.get(direction) | translate }}\n \n \n \n
\n \n \n \n \n
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),qe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=M,n.perimeterTypes=Object.keys(M),n.perimeterTypeTranslationMap=w,n.rangeUnits=Object.keys(O),n.rangeUnitTranslationMap=B,n}return y(r,e),r.prototype.configForm=function(){return this.geoFilterConfigForm},r.prototype.onConfigurationSet=function(e){this.geoFilterConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType:null,[]],centerLatitude:[e?e.centerLatitude:null,[]],centerLongitude:[e?e.centerLatitude:null,[]],range:[e?e.range:null,[]],rangeUnit:[e?e.rangeUnit:null,[]],polygonsDefinition:[e?e.polygonsDefinition:null,[]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoFilterConfigForm.get("perimeterType").value;t?this.geoFilterConfigForm.get("perimeterType").setValidators([]):this.geoFilterConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==M.CIRCLE?(this.geoFilterConfigForm.get("centerLatitude").setValidators([]),this.geoFilterConfigForm.get("centerLongitude").setValidators([]),this.geoFilterConfigForm.get("range").setValidators([]),this.geoFilterConfigForm.get("rangeUnit").setValidators([])):(this.geoFilterConfigForm.get("centerLatitude").setValidators([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoFilterConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoFilterConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoFilterConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==M.POLYGON?this.geoFilterConfigForm.get("polygonsDefinition").setValidators([]):this.geoFilterConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoFilterConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),this.geoFilterConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-gps-geofencing-config",template:'
\n \n tb.rulenode.latitude-key-name\n \n \n {{ \'tb.rulenode.latitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.longitude-key-name\n \n \n {{ \'tb.rulenode.longitude-key-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.circle-center-latitude\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n tb.rulenode.circle-center-longitude\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.range\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n tb.rulenode.range-units\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.messageTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.messageTypeConfigForm=this.fb.group({messageTypes:[e?e.messageTypes:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-message-type-config",template:'
\n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ie=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allowedEntityTypes=[a.EntityType.DEVICE,a.EntityType.ASSET,a.EntityType.ENTITY_VIEW,a.EntityType.TENANT,a.EntityType.CUSTOMER,a.EntityType.USER,a.EntityType.DASHBOARD,a.EntityType.RULE_CHAIN,a.EntityType.RULE_NODE],n}return y(r,e),r.prototype.configForm=function(){return this.originatorTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorTypeConfigForm=this.fb.group({originatorTypes:[e?e.originatorTypes:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-originator-type-config",template:'
\n \n \n \n
\n',styles:[":host ::ng-deep tb-entity-type-list .mat-form-field-flex{padding-top:0}:host ::ng-deep tb-entity-type-list .mat-form-field-infix{border-top:0}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ke=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"filter",this.translate.instant("tb.rulenode.filter"),"Filter",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-filter-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Ne=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.switchConfigForm},r.prototype.onConfigurationSet=function(e){this.switchConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.switchConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"switch",this.translate.instant("tb.rulenode.switch"),"Switch",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.switchConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-filter-node-switch-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Ve=function(e){function r(t,r,n){var o,l,s=e.call(this,t)||this;s.store=t,s.translate=r,s.fb=n,s.alarmStatusTranslationsMap=a.alarmStatusTranslations,s.alarmStatusList=[],s.searchText="",s.displayStatusFn=s.displayStatus.bind(s);try{for(var m=C(Object.keys(a.AlarmStatus)),u=m.next();!u.done;u=m.next()){var d=u.value;s.alarmStatusList.push(a.AlarmStatus[d])}}catch(e){o={error:e}}finally{try{u&&!u.done&&(l=m.return)&&l.call(m)}finally{if(o)throw o.error}}return s.statusFormControl=new i.FormControl(""),s.filteredAlarmStatus=s.statusFormControl.valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(e){return s.fetchAlarmStatus(e)})),f.share()),s}return y(r,e),r.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},r.prototype.configForm=function(){return this.alarmStatusConfigForm},r.prototype.prepareInputConfig=function(e){return this.searchText="",this.statusFormControl.patchValue("",{emitEvent:!0}),e},r.prototype.onConfigurationSet=function(e){this.alarmStatusConfigForm=this.fb.group({alarmStatusList:[e?e.alarmStatusList:null,[i.Validators.required]]})},r.prototype.displayStatus=function(e){return e?this.translate.instant(a.alarmStatusTranslations.get(e)):void 0},r.prototype.fetchAlarmStatus=function(e){var t=this,r=this.getAlarmStatusList();if(this.searchText=e,this.searchText&&this.searchText.length){var n=this.searchText.toUpperCase();return c.of(r.filter((function(e){return t.translate.instant(a.alarmStatusTranslations.get(a.AlarmStatus[e])).toUpperCase().includes(n)})))}return c.of(r)},r.prototype.alarmStatusSelected=function(e){this.addAlarmStatus(e.option.value),this.clear("")},r.prototype.removeAlarmStatus=function(e){var t=this.alarmStatusConfigForm.get("alarmStatusList").value;if(t){var r=t.indexOf(e);r>=0&&(t.splice(r,1),this.alarmStatusConfigForm.get("alarmStatusList").setValue(t))}},r.prototype.addAlarmStatus=function(e){var t=this.alarmStatusConfigForm.get("alarmStatusList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.alarmStatusConfigForm.get("alarmStatusList").setValue(t))},r.prototype.getAlarmStatusList=function(){var e=this;return this.alarmStatusList.filter((function(t){return-1===e.alarmStatusConfigForm.get("alarmStatusList").value.indexOf(t)}))},r.prototype.onAlarmStatusInputFocus=function(){this.statusFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.alarmStatusInput.nativeElement.value=e,this.statusFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.alarmStatusInput.nativeElement.blur(),t.alarmStatusInput.nativeElement.focus()}),0)},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:i.FormBuilder}]},b([t.ViewChild("alarmStatusInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"alarmStatusInput",void 0),r=b([t.Component({selector:"tb-filter-node-check-alarm-status-config",template:'
\n \n tb.rulenode.alarm-status-filter\n \n \n \n {{alarmStatusTranslationsMap.get(alarmStatus) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-alarm-status-matching\n
\n
\n
\n
\n
\n \n
\n\n\n\n'}),h("design:paramtypes",[o.Store,n.TranslateService,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ee=function(){function e(){}return e=b([t.NgModule({declarations:[xe,Te,qe,Se,Ie,ke,Ne,Ve],imports:[r.CommonModule,a.SharedModule,le],exports:[xe,Te,qe,Se,Ie,ke,Ne,Ve]})],e)}(),Ae=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.customerAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.customerAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-customer-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Le=function(e){function r(t,r,n){var a,o,l=e.call(this,t)||this;l.store=t,l.translate=r,l.fb=n,l.entityDetailsTranslationsMap=G,l.entityDetailsList=[],l.searchText="",l.displayDetailsFn=l.displayDetails.bind(l);try{for(var s=C(Object.keys(K)),m=s.next();!m.done;m=s.next()){var u=m.value;l.entityDetailsList.push(K[u])}}catch(e){a={error:e}}finally{try{m&&!m.done&&(o=s.return)&&o.call(s)}finally{if(a)throw a.error}}return l.detailsFormControl=new i.FormControl(""),l.filteredEntityDetails=l.detailsFormControl.valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(e){return l.fetchEntityDetails(e)})),f.share()),l}return y(r,e),r.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},r.prototype.configForm=function(){return this.entityDetailsConfigForm},r.prototype.prepareInputConfig=function(e){return this.searchText="",this.detailsFormControl.patchValue("",{emitEvent:!0}),e},r.prototype.onConfigurationSet=function(e){this.entityDetailsConfigForm=this.fb.group({detailsList:[e?e.detailsList:null,[i.Validators.required]],addToMetadata:[!!e&&e.addToMetadata,[]]})},r.prototype.displayDetails=function(e){return e?this.translate.instant(G.get(e)):void 0},r.prototype.fetchEntityDetails=function(e){var t=this;if(this.searchText=e,this.searchText&&this.searchText.length){var r=this.searchText.toUpperCase();return c.of(this.entityDetailsList.filter((function(e){return t.translate.instant(G.get(K[e])).toUpperCase().includes(r)})))}return c.of(this.entityDetailsList)},r.prototype.detailsFieldSelected=function(e){this.addDetailsField(e.option.value),this.clear("")},r.prototype.removeDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;if(t){var r=t.indexOf(e);r>=0&&(t.splice(r,1),this.entityDetailsConfigForm.get("detailsList").setValue(t))}},r.prototype.addDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.entityDetailsConfigForm.get("detailsList").setValue(t))},r.prototype.onEntityDetailsInputFocus=function(){this.detailsFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.detailsInput.nativeElement.value=e,this.detailsFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.detailsInput.nativeElement.blur(),t.detailsInput.nativeElement.focus()}),0)},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:i.FormBuilder}]},b([t.ViewChild("detailsInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"detailsInput",void 0),r=b([t.Component({selector:"tb-enrichment-node-entity-details-config",template:'
\n \n tb.rulenode.entity-details\n \n \n \n {{entityDetailsTranslationsMap.get(details) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-entity-details-matching\n
\n
\n
\n
\n
\n \n \n {{ \'tb.rulenode.add-to-metadata\' | translate }}\n \n
tb.rulenode.add-to-metadata-hint
\n
\n',styles:[":host ::ng-deep mat-form-field.entity-fields-list .mat-form-field-wrapper{margin-bottom:-1.25em}"]}),h("design:paramtypes",[o.Store,n.TranslateService,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.deviceAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.deviceAttributesConfigForm=this.fb.group({deviceRelationsQuery:[e?e.deviceRelationsQuery:null,[i.Validators.required]],tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.deviceAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.deviceAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.deviceAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.deviceAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-device-attributes-config",template:'
\n \n \n \n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Pe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.originatorAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorAttributesConfigForm=this.fb.group({tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.originatorAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.originatorAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.originatorAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.originatorAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-originator-attributes-config",template:'
\n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Re=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.originatorFieldsConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorFieldsConfigForm=this.fb.group({fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-originator-fields-config",template:'
\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),we=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],n.fetchMode=U,n.fetchModes=Object.keys(U),n.samplingOrders=Object.keys(j),n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=D,n}return y(r,e),r.prototype.configForm=function(){return this.getTelemetryFromDatabaseConfigForm},r.prototype.onConfigurationSet=function(e){this.getTelemetryFromDatabaseConfigForm=this.fb.group({latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],fetchMode:[e?e.fetchMode:null,[i.Validators.required]],orderBy:[e?e.orderBy:null,[]],limit:[e?e.limit:null,[]],useMetadataIntervalPatterns:[!!e&&e.useMetadataIntervalPatterns,[]],startInterval:[e?e.startInterval:null,[]],startIntervalTimeUnit:[e?e.startIntervalTimeUnit:null,[]],endInterval:[e?e.endInterval:null,[]],endIntervalTimeUnit:[e?e.endIntervalTimeUnit:null,[]],startIntervalPattern:[e?e.startIntervalPattern:null,[]],endIntervalPattern:[e?e.endIntervalPattern:null,[]]})},r.prototype.validatorTriggers=function(){return["fetchMode","useMetadataIntervalPatterns"]},r.prototype.updateValidators=function(e){var t=this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value,r=this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").value;t&&t===U.ALL?(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([i.Validators.required,i.Validators.min(2),i.Validators.max(1e3)])):(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([])),r?(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([i.Validators.required])):(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([])),this.getTelemetryFromDatabaseConfigForm.get("orderBy").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("limit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").updateValueAndValidity({emitEvent:e})},r.prototype.removeKey=function(e,t){var r=this.getTelemetryFromDatabaseConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.getTelemetryFromDatabaseConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-get-telemetry-from-database",template:'
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n tb.rulenode.fetch-mode\n \n \n {{ mode }}\n \n \n tb.rulenode.fetch-mode-hint\n \n
\n \n tb.rulenode.order-by\n \n \n {{ order }}\n \n \n tb.rulenode.order-by-hint\n \n \n tb.rulenode.limit\n \n tb.rulenode.limit-hint\n \n
\n \n {{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-interval-patterns-hint
\n
\n
\n \n tb.rulenode.start-interval\n \n \n {{ \'tb.rulenode.start-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.start-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.end-interval\n \n \n {{ \'tb.rulenode.end-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.end-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n \n tb.rulenode.start-interval-pattern\n \n \n {{ \'tb.rulenode.start-interval-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.end-interval-pattern\n \n \n {{ \'tb.rulenode.end-interval-pattern-required\' | translate }}\n \n \n \n \n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Oe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.relatedAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.relatedAttributesConfigForm=this.fb.group({relationsQuery:[e?e.relationsQuery:null,[i.Validators.required]],telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-related-attributes-config",template:'
\n \n \n \n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),De=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.tenantAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.tenantAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-tenant-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ke=function(){function e(){}return e=b([t.NgModule({declarations:[Ae,Le,Me,Pe,Re,we,Oe,De],imports:[r.CommonModule,a.SharedModule,le],exports:[Ae,Le,Me,Pe,Re,we,Oe,De]})],e)}(),Be=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.originatorSource=v,n.originatorSources=Object.keys(v),n.originatorSourceTranslationMap=P,n}return y(r,e),r.prototype.configForm=function(){return this.changeOriginatorConfigForm},r.prototype.onConfigurationSet=function(e){this.changeOriginatorConfigForm=this.fb.group({originatorSource:[e?e.originatorSource:null,[i.Validators.required]],relationsQuery:[e?e.relationsQuery:null,[]]})},r.prototype.validatorTriggers=function(){return["originatorSource"]},r.prototype.updateValidators=function(e){var t=this.changeOriginatorConfigForm.get("originatorSource").value;t&&t===v.RELATED?this.changeOriginatorConfigForm.get("relationsQuery").setValidators([i.Validators.required]):this.changeOriginatorConfigForm.get("relationsQuery").setValidators([]),this.changeOriginatorConfigForm.get("relationsQuery").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-transformation-node-change-originator-config",template:'
\n \n tb.rulenode.originator-source\n \n \n {{ originatorSourceTranslationMap.get(source) | translate }}\n \n \n \n
\n \n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ue=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"update",this.translate.instant("tb.rulenode.transformer"),"Transform",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-transformation-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),je=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.toEmailConfigForm},r.prototype.onConfigurationSet=function(e){this.toEmailConfigForm=this.fb.group({fromTemplate:[e?e.fromTemplate:null,[i.Validators.required]],toTemplate:[e?e.toTemplate:null,[i.Validators.required]],ccTemplate:[e?e.ccTemplate:null,[]],bccTemplate:[e?e.bccTemplate:null,[]],subjectTemplate:[e?e.subjectTemplate:null,[i.Validators.required]],bodyTemplate:[e?e.bodyTemplate:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-transformation-node-to-email-config",template:'
\n \n tb.rulenode.from-template\n \n \n {{ \'tb.rulenode.from-template-required\' | translate }}\n \n \n \n \n tb.rulenode.to-template\n \n \n {{ \'tb.rulenode.to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.cc-template\n \n \n \n \n tb.rulenode.bcc-template\n \n \n \n \n tb.rulenode.subject-template\n \n \n {{ \'tb.rulenode.subject-template-required\' | translate }}\n \n \n \n \n tb.rulenode.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),He=function(){function e(){}return e=b([t.NgModule({declarations:[Be,Ue,je],imports:[r.CommonModule,a.SharedModule,le],exports:[Be,Ue,je]})],e)}(),Ge=function(){function e(e){!function(e){e.setTranslation("en_US",{tb:{rulenode:{"create-entity-if-not-exists":"Create new entity if not exists","create-entity-if-not-exists-hint":"Create a new entity set above if it does not exist.","entity-name-pattern":"Name pattern","entity-name-pattern-required":"Name pattern is required","entity-name-pattern-hint":"Name pattern, use ${metaKeyName} to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use ${metaKeyName} to substitute variables from metadata","entity-cache-expiration":"Entities cache expiration time (sec)","entity-cache-expiration-hint":"Specifies maximum time interval allowed to store found entity records. 0 value means that records will never expire.","entity-cache-expiration-required":"Entities cache expiration time is required.","entity-cache-expiration-range":"Entities cache expiration time should be greater than or equal to 0.","customer-name-pattern":"Customer name pattern","customer-name-pattern-required":"Customer name pattern is required","create-customer-if-not-exists":"Create new customer if not exists","customer-cache-expiration":"Customers cache expiration time (sec)","customer-name-pattern-hint":"Customer name pattern, use ${metaKeyName} to substitute variables from metadata","customer-cache-expiration-hint":"Specifies maximum time interval allowed to store found customer records. 0 value means that records will never expire.","customer-cache-expiration-required":"Customers cache expiration time is required.","customer-cache-expiration-range":"Customers cache expiration time should be greater than or equal to 0.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. In case you want to fetch a single entry, select fetch mode 'FIRST' or 'LAST'.","time-unit-milliseconds":"Milliseconds","time-unit-seconds":"Seconds","time-unit-minutes":"Minutes","time-unit-hours":"Hours","time-unit-days":"Days","time-value-range":"Time value should be in a range from 1 to 2147483647.","start-interval-value-required":"Start interval value is required.","end-interval-value-required":"End interval value is required.",filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","client-attributes-hint":"Client attributes, use ${metaKeyName} to substitute variables from metadata","shared-attributes":"Shared attributes","shared-attributes-hint":"Shared attributes, use ${metaKeyName} to substitute variables from metadata","server-attributes":"Server attributes","server-attributes-hint":"Server attributes, use ${metaKeyName} to substitute variables from metadata","notify-device":"Notify Device","notify-device-hint":"If the message arrives from the device, we will push it back to the device by default.","latest-timeseries":"Latest timeseries","latest-timeseries-hint":"Latest timeseries, use ${metaKeyName} to substitute variables from metadata","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use ${metaKeyName} to substitute variables from metadata","relation-type-pattern-required":"Relation type pattern is required","relation-types-list":"Relation types to propagate","relation-types-list-hint":"If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","source-field":"Source field","source-field-required":"Source field is required.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","originator-alarm-originator":"Alarm Originator","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","use-metadata-period-in-seconds-patterns":"Use metadata period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds metadata pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","period-in-seconds-pattern-hint":"Period in seconds pattern, use ${metaKeyName} to substitute variables from metadata","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required","alarm-status-filter":"Alarm status filter","alarm-status-list-empty":"Alarm status list is empty","no-alarm-status-matching":"No alarm status matching were found.",propagate:"Propagate",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From Template","from-template-required":"From Template is required","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","request-id-metadata-attribute":"Request Id Metadata attribute name","timeout-sec":"Timeout in seconds","timeout-required":"Timeout is required","min-timeout-message":"Only 0 minimum timeout value is allowed.","endpoint-url-pattern":"Endpoint URL pattern","endpoint-url-pattern-required":"Endpoint URL pattern is required","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory","read-timeout":"Read timeout in millis","read-timeout-hint":"The value of 0 means an infinite timeout","max-parallel-requests-count":"Max number of parallel requests","max-parallel-requests-count-hint":"The value of 0 specifies no limit in parallel processing",headers:"Headers","headers-hint":"Use ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata",topic:"Topic","topic-required":"Topic is required","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","gcp-project-id":"GCP project ID","gcp-project-id-required":"GCP project ID is required","gcp-service-account-key":"GCP service account key file","gcp-service-account-key-required":"GCP service account key file is required","pubsub-topic-name":"Topic name","pubsub-topic-name-required":"Topic name is required","message-attributes":"Message attributes","message-attributes-hint":"Use ${metaKeyName} in name/value fields to substitute variables from metadata","connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","device-id":"Device ID","device-id-required":"Device ID is required.","clean-session":"Clean session","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","credentials-sas":"Shared Access Signature","sas-key":"SAS Key","sas-key-required":"SAS Key is required.",hostname:"Hostname","hostname-required":"Hostname is required.","azure-ca-cert":"CA certificate file","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"Certificate file *","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password","use-system-smtp-settings":"Use system SMTP settings","use-metadata-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys are present","check-all-keys-hint":"If selected, checks that all specified keys are present in the message data and metadata.","check-relation-to-specific-entity":"Check relation to specific entity","check-relation-hint":"Checks existence of relation to specific entity or to any entity based on direction and relation type.","delete-relation-to-specific-entity":"Delete relation to specific entity","delete-relation-hint":"Deletes relation from the originator of the incoming message to the specified entity or list of entities based on direction and type.","remove-current-relations":"Remove current relations","remove-current-relations-hint":"Removes current relations from the originator of the incoming message based on direction and type.","change-originator-to-related-entity":"Change originator to related entity","change-originator-to-related-entity-hint":"Used to process submitted message as a message from another entity.","start-interval-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use ${metaKeyName} to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use ${metaKeyName} to substitute variables from metadata","smtp-protocol":"Protocol","smtp-host":"SMTP host","smtp-host-required":"SMTP host is required.","smtp-port":"SMTP port","smtp-port-required":"You must supply a smtp port.","smtp-port-range":"SMTP port should be in a range from 1 to 65535.","timeout-msec":"Timeout ms","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS","tls-version":"TLS version","enable-proxy":"Enable proxy","use-system-proxy-properties":"Use system proxy properties","proxy-host":"Proxy host","proxy-host-required":"Proxy host is required.","proxy-port":"Proxy port","proxy-port-required":"Proxy port is required.","proxy-port-range":"Proxy port should be in a range from 1 to 65535.","proxy-user":"Proxy user","proxy-password":"Proxy password","proxy-scheme":"Proxy scheme","numbers-to-template":"Phone Numbers To Template","numbers-to-template-required":"Phone Numbers To Template is required","numbers-to-template-hint":"Comma separated Phone Numbers, use ${metaKeyName} to substitute variables from metadata","sms-message-template":"SMS message Template","sms-message-template-required":"SMS message Template is required","sms-message-template-hint":"SMS message template, use ${metaKeyName} to substitute variables from metadata","use-system-sms-settings":"Use system SMS provider settings","min-period-0-seconds-message":"Only 0 second minimum period is allowed.","max-pending-messages":"Maximum pending messages","max-pending-messages-required":"Maximum pending messages is required.","max-pending-messages-range":"Maximum pending messages should be in a range from 1 to 100000.","originator-types-filter":"Originator types filter","interval-seconds":"Interval in seconds","interval-seconds-required":"Interval is required.","min-interval-seconds-message":"Only 1 second minimum interval is allowed.","output-timeseries-key-prefix":"Output timeseries key prefix","output-timeseries-key-prefix-required":"Output timeseries key prefix required.","separator-hint":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","entity-details-zip":"Zip","entity-details-address":"Address","entity-details-address2":"Address2","entity-details-additional_info":"Additional Info","entity-details-phone":"Phone","entity-details-email":"Email","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","entity-details-list-empty":"No entity details selected.","no-entity-details-matching":"No entity details matching were found.","custom-table-name":"Custom table name","custom-table-name-required":"Table Name is required","custom-table-hint":"You should enter the table name without prefix 'cs_tb_'.","message-field":"Message field","message-field-required":"Message field is required.","table-col":"Table column","table-col-required":"Table column is required.","latitude-key-name":"Latitude key name","longitude-key-name":"Longitude key name","latitude-key-name-required":"Latitude key name is required.","longitude-key-name-required":"Longitude key name is required.","fetch-perimeter-info-from-message-metadata":"Fetch perimeter information from message metadata","perimeter-circle":"Circle","perimeter-polygon":"Polygon","perimeter-type":"Perimeter type","circle-center-latitude":"Center latitude","circle-center-latitude-required":"Center latitude is required.","circle-center-longitude":"Center longitude","circle-center-longitude-required":"Center longitude is required.","range-unit-meter":"Meter","range-unit-kilometer":"Kilometer","range-unit-foot":"Foot","range-unit-mile":"Mile","range-unit-nautical-mile":"Nautical mile","range-units":"Range units",range:"Range","range-required":"Range is required.","polygon-definition":"Polygon definition","polygon-definition-required":"Polygon definition is required.","polygon-definition-hint":"Please, use the following format for manual definition of polygon: [[lat1,lon1],[lat2,lon2], ... ,[latN,lonN]].","min-inside-duration":"Minimal inside duration","min-inside-duration-value-required":"Minimal inside duration is required","min-inside-duration-time-unit":"Minimal inside duration time unit","min-outside-duration":"Minimal outside duration","min-outside-duration-value-required":"Minimal outside duration is required","min-outside-duration-time-unit":"Minimal outside duration time unit","tell-failure-if-absent":"Tell Failure","tell-failure-if-absent-hint":'If at least one selected key doesn\'t exist the outbound message will report "Failure".',"get-latest-value-with-ts":"Fetch Latest telemetry with Timestamp","get-latest-value-with-ts-hint":'If selected, latest telemetry values will be added to the outbound message metadata with timestamp, e.g: "temp": "{\\"ts\\":1574329385897,\\"value\\":42}"',"use-redis-queue":"Use redis queue for message persistence","trim-redis-queue":"Trim redis queue","redis-queue-max-size":"Redis queue max size","add-metadata-key-values-as-kafka-headers":"Add Message metadata key-value pairs to Kafka record headers","add-metadata-key-values-as-kafka-headers-hint":"If selected, key-value pairs from message metadata will be added to the outgoing records headers as byte arrays with predefined charset encoding.","charset-encoding":"Charset encoding","charset-encoding-required":"Charset encoding is required.","charset-us-ascii":"US-ASCII","charset-iso-8859-1":"ISO-8859-1","charset-utf-8":"UTF-8","charset-utf-16be":"UTF-16BE","charset-utf-16le":"UTF-16LE","charset-utf-16":"UTF-16","select-queue-hint":"The queue name can be selected from a drop-down list or add a custom name.","persist-alarm-rules":"Persist state of alarm rules","fetch-alarm-rules":"Fetch state of alarm rules"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}},!0)}(e)}return e.ctorParameters=function(){return[{type:n.TranslateService}]},e=b([t.NgModule({declarations:[F],imports:[r.CommonModule,a.SharedModule],exports:[Fe,Ee,Ke,He,F]}),h("design:paramtypes",[n.TranslateService])],e)}();e.RuleNodeCoreConfigModule=Ge,e.ɵa=F,e.ɵb=Fe,e.ɵba=be,e.ɵbb=he,e.ɵbc=Ce,e.ɵbd=ve,e.ɵbe=le,e.ɵbf=ne,e.ɵbg=ae,e.ɵbh=oe,e.ɵbi=ie,e.ɵbj=Ee,e.ɵbk=xe,e.ɵbl=Te,e.ɵbm=qe,e.ɵbn=Se,e.ɵbo=Ie,e.ɵbp=ke,e.ɵbq=Ne,e.ɵbr=Ve,e.ɵbs=Ke,e.ɵbt=Ae,e.ɵbu=Le,e.ɵbv=Me,e.ɵbw=Pe,e.ɵbx=Re,e.ɵby=we,e.ɵbz=Oe,e.ɵc=x,e.ɵca=De,e.ɵcb=He,e.ɵcc=Be,e.ɵcd=Ue,e.ɵce=je,e.ɵd=T,e.ɵe=q,e.ɵf=S,e.ɵg=I,e.ɵh=k,e.ɵi=N,e.ɵj=V,e.ɵk=E,e.ɵl=A,e.ɵm=L,e.ɵn=X,e.ɵo=ee,e.ɵp=te,e.ɵq=re,e.ɵr=se,e.ɵs=me,e.ɵt=ue,e.ɵu=de,e.ɵv=pe,e.ɵw=ce,e.ɵx=fe,e.ɵy=ge,e.ɵz=ye,Object.defineProperty(e,"__esModule",{value:!0})})); + ***************************************************************************** */var g=function(e,t){return(g=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(e[r]=t[r])})(e,t)};function y(e,t){function r(){this.constructor=e}g(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)}function b(e,t,r,n){var a,o=arguments.length,i=o<3?t:null===n?n=Object.getOwnPropertyDescriptor(t,r):n;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,r,n);else for(var l=e.length-1;l>=0;l--)(a=e[l])&&(i=(o<3?a(i):o>3?a(t,r,i):a(t,r))||i);return o>3&&i&&Object.defineProperty(t,r,i),i}function h(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)}Object.create;function C(e){var t="function"==typeof Symbol&&Symbol.iterator,r=t&&e[t],n=0;if(r)return r.call(e);if(e&&"number"==typeof e.length)return{next:function(){return e&&n>=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}Object.create;var v,F=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.emptyConfigForm},r.prototype.onConfigurationSet=function(e){this.emptyConfigForm=this.fb.group({})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-node-empty-config",template:"
"}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),x=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.attributeScopes=Object.keys(a.AttributeScope),n.telemetryTypeTranslationsMap=a.telemetryTypeTranslations,n}return y(r,e),r.prototype.configForm=function(){return this.attributesConfigForm},r.prototype.onConfigurationSet=function(e){this.attributesConfigForm=this.fb.group({scope:[e?e.scope:null,[i.Validators.required]],notifyDevice:[!e||e.scope,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-attributes-config",template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.notify-device\' | translate }}\n \n
tb.rulenode.notify-device-hint
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),T=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.timeseriesConfigForm},r.prototype.onConfigurationSet=function(e){this.timeseriesConfigForm=this.fb.group({defaultTTL:[e?e.defaultTTL:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-timeseries-config",template:'
\n \n tb.rulenode.default-ttl\n \n \n {{ \'tb.rulenode.default-ttl-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-default-ttl-message\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),q=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.rpcRequestConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcRequestConfigForm=this.fb.group({timeoutInSeconds:[e?e.timeoutInSeconds:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rpc-request-config",template:'
\n \n tb.rulenode.timeout-sec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-message\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),S=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.logConfigForm},r.prototype.onConfigurationSet=function(e){this.logConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.logConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"string",this.translate.instant("tb.rulenode.to-string"),"ToString",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.logConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-log-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),I=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.assignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.assignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],createCustomerIfNotExists:[!!e&&e.createCustomerIfNotExists,[]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-assign-to-customer-config",template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.create-customer-if-not-exists\' | translate }}\n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),k=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.clearAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.clearAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],alarmType:[e?e.alarmType:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.clearAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.clearAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-clear-alarm-config",template:'
\n \n \n \n
\n \n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),N=function(e){function r(t,r,n,o){var i=e.call(this,t)||this;return i.store=t,i.fb=r,i.nodeScriptTestService=n,i.translate=o,i.alarmSeverities=Object.keys(a.AlarmSeverity),i.alarmSeverityTranslationMap=a.alarmSeverityTranslations,i.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],i}return y(r,e),r.prototype.configForm=function(){return this.createAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.createAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],useMessageAlarmData:[!!e&&e.useMessageAlarmData,[]],alarmType:[e?e.alarmType:null,[]],severity:[e?e.severity:null,[]],propagate:[!!e&&e.propagate,[]],relationTypes:[e?e.relationTypes:null,[]]})},r.prototype.validatorTriggers=function(){return["useMessageAlarmData"]},r.prototype.updateValidators=function(e){this.createAlarmConfigForm.get("useMessageAlarmData").value?(this.createAlarmConfigForm.get("alarmType").setValidators([]),this.createAlarmConfigForm.get("severity").setValidators([])):(this.createAlarmConfigForm.get("alarmType").setValidators([i.Validators.required]),this.createAlarmConfigForm.get("severity").setValidators([i.Validators.required])),this.createAlarmConfigForm.get("alarmType").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("severity").updateValueAndValidity({emitEvent:e})},r.prototype.testScript=function(){var e=this,t=this.createAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.createAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.removeKey=function(e,t){var r=this.createAlarmConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.createAlarmConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.createAlarmConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.createAlarmConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-create-alarm-config",template:'
\n \n \n \n
\n \n
\n \n {{ \'tb.rulenode.use-message-alarm-data\' | translate }}\n \n
\n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \n \n \n tb.rulenode.alarm-severity\n \n \n {{ alarmSeverityTranslationMap.get(severity) | translate }}\n \n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \n \n
\n \n {{ \'tb.rulenode.propagate\' | translate }}\n \n
\n \n tb.rulenode.relation-types-list\n \n \n {{key}}\n close\n \n \n \n \n \n
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),V=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return y(r,e),r.prototype.configForm=function(){return this.createRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.createRelationConfigForm=this.fb.group({direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[i.Validators.required]],entityNamePattern:[e?e.entityNamePattern:null,[]],entityTypePattern:[e?e.entityTypePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],createEntityIfNotExists:[!!e&&e.createEntityIfNotExists,[]],removeCurrentRelations:[!!e&&e.removeCurrentRelations,[]],changeOriginatorToRelatedEntity:[!!e&&e.changeOriginatorToRelatedEntity,[]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["entityType"]},r.prototype.updateValidators=function(e){var t=this.createRelationConfigForm.get("entityType").value;t?this.createRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.createRelationConfigForm.get("entityNamePattern").setValidators([]),!t||t!==a.EntityType.DEVICE&&t!==a.EntityType.ASSET?this.createRelationConfigForm.get("entityTypePattern").setValidators([]):this.createRelationConfigForm.get("entityTypePattern").setValidators([i.Validators.required]),this.createRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e}),this.createRelationConfigForm.get("entityTypePattern").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-create-relation-config",template:'
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.entity-type-pattern\n \n \n {{ \'tb.rulenode.entity-type-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.create-entity-if-not-exists\' | translate }}\n \n
tb.rulenode.create-entity-if-not-exists-hint
\n
\n \n {{ \'tb.rulenode.remove-current-relations\' | translate }}\n \n
tb.rulenode.remove-current-relations-hint
\n \n {{ \'tb.rulenode.change-originator-to-related-entity\' | translate }}\n \n
tb.rulenode.change-originator-to-related-entity-hint
\n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),E=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.msgDelayConfigForm},r.prototype.onConfigurationSet=function(e){this.msgDelayConfigForm=this.fb.group({useMetadataPeriodInSecondsPatterns:[!!e&&e.useMetadataPeriodInSecondsPatterns,[]],periodInSeconds:[e?e.periodInSeconds:null,[]],periodInSecondsPattern:[e?e.periodInSecondsPattern:null,[]],maxPendingMsgs:[e?e.maxPendingMsgs:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(1e5)]]})},r.prototype.validatorTriggers=function(){return["useMetadataPeriodInSecondsPatterns"]},r.prototype.updateValidators=function(e){this.msgDelayConfigForm.get("useMetadataPeriodInSecondsPatterns").value?(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([i.Validators.required]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([])):(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([i.Validators.required,i.Validators.min(0)])),this.msgDelayConfigForm.get("periodInSecondsPattern").updateValueAndValidity({emitEvent:e}),this.msgDelayConfigForm.get("periodInSeconds").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-msg-delay-config",template:'
\n \n {{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-period-in-seconds-patterns-hint
\n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-0-seconds-message\' | translate }}\n \n \n \n \n tb.rulenode.period-in-seconds-pattern\n \n \n {{ \'tb.rulenode.period-in-seconds-pattern-required\' | translate }}\n \n \n \n \n \n tb.rulenode.max-pending-messages\n \n \n {{ \'tb.rulenode.max-pending-messages-required\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),A=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return y(r,e),r.prototype.configForm=function(){return this.deleteRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.deleteRelationConfigForm=this.fb.group({deleteForSingleEntity:[!!e&&e.deleteForSingleEntity,[]],direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["deleteForSingleEntity","entityType"]},r.prototype.updateValidators=function(e){var t=this.deleteRelationConfigForm.get("deleteForSingleEntity").value,r=this.deleteRelationConfigForm.get("entityType").value;t?this.deleteRelationConfigForm.get("entityType").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityType").setValidators([]),t&&r?this.deleteRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityNamePattern").setValidators([]),this.deleteRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:!1}),this.deleteRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-delete-relation-config",template:'
\n \n {{ \'tb.rulenode.delete-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.delete-relation-hint
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),L=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.generatorConfigForm},r.prototype.onConfigurationSet=function(e){this.generatorConfigForm=this.fb.group({msgCount:[e?e.msgCount:null,[i.Validators.required,i.Validators.min(0)]],periodInSeconds:[e?e.periodInSeconds:null,[i.Validators.required,i.Validators.min(1)]],originator:[e?e.originator:null,[]],jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.prepareInputConfig=function(e){return e&&(e.originatorId&&e.originatorType?e.originator={id:e.originatorId,entityType:e.originatorType}:e.originator=null,delete e.originatorId,delete e.originatorType),e},r.prototype.prepareOutputConfig=function(e){return e.originator?(e.originatorId=e.originator.id,e.originatorType=e.originator.entityType):(e.originatorId=null,e.originatorType=null),delete e.originator,e},r.prototype.testScript=function(){var e=this,t=this.generatorConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"generate",this.translate.instant("tb.rulenode.generator"),"Generate",["prevMsg","prevMetadata","prevMsgType"],this.ruleNodeId).subscribe((function(t){t&&e.generatorConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-generator-config",template:'
\n \n tb.rulenode.message-count\n \n \n {{ \'tb.rulenode.message-count-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-message-count-message\' | translate }}\n \n \n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-seconds-message\' | translate }}\n \n \n
\n \n \n \n
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent);!function(e){e.CUSTOMER="CUSTOMER",e.TENANT="TENANT",e.RELATED="RELATED",e.ALARM_ORIGINATOR="ALARM_ORIGINATOR"}(v||(v={}));var M,P=new Map([[v.CUSTOMER,"tb.rulenode.originator-customer"],[v.TENANT,"tb.rulenode.originator-tenant"],[v.RELATED,"tb.rulenode.originator-related"],[v.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator"]]);!function(e){e.CIRCLE="CIRCLE",e.POLYGON="POLYGON"}(M||(M={}));var R,w=new Map([[M.CIRCLE,"tb.rulenode.perimeter-circle"],[M.POLYGON,"tb.rulenode.perimeter-polygon"]]);!function(e){e.MILLISECONDS="MILLISECONDS",e.SECONDS="SECONDS",e.MINUTES="MINUTES",e.HOURS="HOURS",e.DAYS="DAYS"}(R||(R={}));var O,D=new Map([[R.MILLISECONDS,"tb.rulenode.time-unit-milliseconds"],[R.SECONDS,"tb.rulenode.time-unit-seconds"],[R.MINUTES,"tb.rulenode.time-unit-minutes"],[R.HOURS,"tb.rulenode.time-unit-hours"],[R.DAYS,"tb.rulenode.time-unit-days"]]);!function(e){e.METER="METER",e.KILOMETER="KILOMETER",e.FOOT="FOOT",e.MILE="MILE",e.NAUTICAL_MILE="NAUTICAL_MILE"}(O||(O={}));var K,B=new Map([[O.METER,"tb.rulenode.range-unit-meter"],[O.KILOMETER,"tb.rulenode.range-unit-kilometer"],[O.FOOT,"tb.rulenode.range-unit-foot"],[O.MILE,"tb.rulenode.range-unit-mile"],[O.NAUTICAL_MILE,"tb.rulenode.range-unit-nautical-mile"]]);!function(e){e.TITLE="TITLE",e.COUNTRY="COUNTRY",e.STATE="STATE",e.ZIP="ZIP",e.ADDRESS="ADDRESS",e.ADDRESS2="ADDRESS2",e.PHONE="PHONE",e.EMAIL="EMAIL",e.ADDITIONAL_INFO="ADDITIONAL_INFO"}(K||(K={}));var G,U,j,H=new Map([[K.TITLE,"tb.rulenode.entity-details-title"],[K.COUNTRY,"tb.rulenode.entity-details-country"],[K.STATE,"tb.rulenode.entity-details-state"],[K.ZIP,"tb.rulenode.entity-details-zip"],[K.ADDRESS,"tb.rulenode.entity-details-address"],[K.ADDRESS2,"tb.rulenode.entity-details-address2"],[K.PHONE,"tb.rulenode.entity-details-phone"],[K.EMAIL,"tb.rulenode.entity-details-email"],[K.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]);!function(e){e.FIRST="FIRST",e.LAST="LAST",e.ALL="ALL"}(G||(G={})),function(e){e.ASC="ASC",e.DESC="DESC"}(U||(U={})),function(e){e.STANDARD="STANDARD",e.FIFO="FIFO"}(j||(j={}));var z,$=new Map([[j.STANDARD,"tb.rulenode.sqs-queue-standard"],[j.FIFO,"tb.rulenode.sqs-queue-fifo"]]),Q=["anonymous","basic","cert.PEM"],_=new Map([["anonymous","tb.rulenode.credentials-anonymous"],["basic","tb.rulenode.credentials-basic"],["cert.PEM","tb.rulenode.credentials-pem"]]),W=["sas","cert.PEM"],J=new Map([["sas","tb.rulenode.credentials-sas"],["cert.PEM","tb.rulenode.credentials-pem"]]);!function(e){e.GET="GET",e.POST="POST",e.PUT="PUT",e.DELETE="DELETE"}(z||(z={}));var Y=["US-ASCII","ISO-8859-1","UTF-8","UTF-16BE","UTF-16LE","UTF-16"],Z=new Map([["US-ASCII","tb.rulenode.charset-us-ascii"],["ISO-8859-1","tb.rulenode.charset-iso-8859-1"],["UTF-8","tb.rulenode.charset-utf-8"],["UTF-16BE","tb.rulenode.charset-utf-16be"],["UTF-16LE","tb.rulenode.charset-utf-16le"],["UTF-16","tb.rulenode.charset-utf-16"]]),X=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=M,n.perimeterTypes=Object.keys(M),n.perimeterTypeTranslationMap=w,n.rangeUnits=Object.keys(O),n.rangeUnitTranslationMap=B,n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=D,n}return y(r,e),r.prototype.configForm=function(){return this.geoActionConfigForm},r.prototype.onConfigurationSet=function(e){this.geoActionConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType:null,[]],centerLatitude:[e?e.centerLatitude:null,[]],centerLongitude:[e?e.centerLatitude:null,[]],range:[e?e.range:null,[]],rangeUnit:[e?e.rangeUnit:null,[]],polygonsDefinition:[e?e.polygonsDefinition:null,[]],minInsideDuration:[e?e.minInsideDuration:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minInsideDurationTimeUnit:[e?e.minInsideDurationTimeUnit:null,[i.Validators.required]],minOutsideDuration:[e?e.minOutsideDuration:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minOutsideDurationTimeUnit:[e?e.minOutsideDurationTimeUnit:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoActionConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoActionConfigForm.get("perimeterType").value;t?this.geoActionConfigForm.get("perimeterType").setValidators([]):this.geoActionConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==M.CIRCLE?(this.geoActionConfigForm.get("centerLatitude").setValidators([]),this.geoActionConfigForm.get("centerLongitude").setValidators([]),this.geoActionConfigForm.get("range").setValidators([]),this.geoActionConfigForm.get("rangeUnit").setValidators([])):(this.geoActionConfigForm.get("centerLatitude").setValidators([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoActionConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoActionConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoActionConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==M.POLYGON?this.geoActionConfigForm.get("polygonsDefinition").setValidators([]):this.geoActionConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoActionConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),this.geoActionConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-gps-geofencing-config",template:'
\n \n tb.rulenode.latitude-key-name\n \n \n {{ \'tb.rulenode.latitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.longitude-key-name\n \n \n {{ \'tb.rulenode.longitude-key-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.circle-center-latitude\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n tb.rulenode.circle-center-longitude\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.range\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n tb.rulenode.range-units\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.min-inside-duration\n \n \n {{ \'tb.rulenode.min-inside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-inside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.min-outside-duration\n \n \n {{ \'tb.rulenode.min-outside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-outside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ee=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.msgCountConfigForm},r.prototype.onConfigurationSet=function(e){this.msgCountConfigForm=this.fb.group({interval:[e?e.interval:null,[i.Validators.required,i.Validators.min(1)]],telemetryPrefix:[e?e.telemetryPrefix:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-msg-count-config",template:'
\n \n tb.rulenode.interval-seconds\n \n \n {{ \'tb.rulenode.interval-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-interval-seconds-message\' | translate }}\n \n \n \n tb.rulenode.output-timeseries-key-prefix\n \n \n {{ \'tb.rulenode.output-timeseries-key-prefix-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.rpcReplyConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcReplyConfigForm=this.fb.group({requestIdMetaDataAttribute:[e?e.requestIdMetaDataAttribute:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rpc-reply-config",template:'
\n \n tb.rulenode.request-id-metadata-attribute\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),re=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.saveToCustomTableConfigForm},r.prototype.onConfigurationSet=function(e){this.saveToCustomTableConfigForm=this.fb.group({tableName:[e?e.tableName:null,[i.Validators.required]],fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-custom-table-config",template:'
\n \n tb.rulenode.custom-table-name\n \n \n {{ \'tb.rulenode.custom-table-name-required\' | translate }}\n \n \n \n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ne=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.translate=r,o.injector=n,o.fb=a,o.propagateChange=null,o.valueChangeSubscription=null,o}var a;return y(r,e),a=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){this.ngControl=this.injector.get(i.NgControl),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({}),this.kvListFormGroup.addControl("keyVals",this.fb.array([]))},r.prototype.keyValsFormArray=function(){return this.kvListFormGroup.get("keyVals")},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t,r,n=this;this.valueChangeSubscription&&this.valueChangeSubscription.unsubscribe();var a=[];if(e)try{for(var o=C(Object.keys(e)),l=o.next();!l.done;l=o.next()){var s=l.value;Object.prototype.hasOwnProperty.call(e,s)&&a.push(this.fb.group({key:[s,[i.Validators.required]],value:[e[s],[i.Validators.required]]}))}}catch(e){t={error:e}}finally{try{l&&!l.done&&(r=o.return)&&r.call(o)}finally{if(t)throw t.error}}this.kvListFormGroup.setControl("keyVals",this.fb.array(a)),this.valueChangeSubscription=this.kvListFormGroup.valueChanges.subscribe((function(){n.updateModel()}))},r.prototype.removeKeyVal=function(e){this.kvListFormGroup.get("keyVals").removeAt(e)},r.prototype.addKeyVal=function(){this.kvListFormGroup.get("keyVals").push(this.fb.group({key:["",[i.Validators.required]],value:["",[i.Validators.required]]}))},r.prototype.validate=function(e){return!this.kvListFormGroup.get("keyVals").value.length&&this.required?{kvMapRequired:!0}:this.kvListFormGroup.valid?null:{kvFieldsRequired:!0}},r.prototype.updateModel=function(){var e=this.kvListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{var t={};e.forEach((function(e){t[e.key]=e.value})),this.propagateChange(t)}},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:t.Injector},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",String)],r.prototype,"requiredText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"keyText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"keyRequiredText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"valText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"valRequiredText",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=a=b([t.Component({selector:"tb-kv-map-config",template:'
\n
\n {{ keyText }}\n {{ valText }}\n \n
\n
\n
\n \n \n \n \n {{ keyRequiredText | translate }}\n \n \n \n \n \n \n {{ valRequiredText | translate }}\n \n \n \n
\n
\n \n
\n \n
\n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return a})),multi:!0},{provide:i.NG_VALIDATORS,useExisting:t.forwardRef((function(){return a})),multi:!0}],styles:[":host .tb-kv-map-config{margin-bottom:16px}:host .tb-kv-map-config .header{padding-left:5px;padding-right:5px;padding-bottom:5px}:host .tb-kv-map-config .header .cell{padding-left:5px;padding-right:5px;color:rgba(0,0,0,.54);font-size:12px;font-weight:700;white-space:nowrap}:host .tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:20px;max-height:300px;overflow:auto}:host .tb-kv-map-config .body .row{padding-top:5px;max-height:40px}:host .tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell{margin:0;max-height:40px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell .mat-form-field-infix{border-top:0}:host ::ng-deep .tb-kv-map-config .body button.mat-button{margin:0}"]}),h("design:paramtypes",[o.Store,n.TranslateService,t.Injector,i.FormBuilder])],r)}(a.PageComponent),ae=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n.propagateChange=null,n}var n;return y(r,e),n=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.deviceRelationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[i.Validators.required]],maxLevel:[null,[]],relationType:[null],deviceTypes:[null,[i.Validators.required]]}),this.deviceRelationsQueryFormGroup.valueChanges.subscribe((function(t){e.deviceRelationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.deviceRelationsQueryFormGroup.disable({emitEvent:!1}):this.deviceRelationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.deviceRelationsQueryFormGroup.reset(e,{emitEvent:!1})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=b([t.Component({selector:"tb-device-relations-query-config",template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-type
\n \n \n
device.device-types
\n \n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),oe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.propagateChange=null,n}var n;return y(r,e),n=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.relationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[i.Validators.required]],maxLevel:[null,[]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((function(t){e.relationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.relationsQueryFormGroup.reset(e||{},{emitEvent:!1})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=b([t.Component({selector:"tb-relations-query-config",template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-filters
\n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),ie=function(e){function r(t,r,n,o){var i,l,s=e.call(this,t)||this;s.store=t,s.translate=r,s.truncate=n,s.fb=o,s.placeholder="tb.rulenode.message-type",s.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],s.messageTypes=[],s.messageTypesList=[],s.searchText="",s.propagateChange=function(e){},s.messageTypeConfigForm=s.fb.group({messageType:[null]});try{for(var u=C(Object.keys(a.MessageType)),d=u.next();!d.done;d=u.next()){var p=d.value;s.messageTypesList.push({name:a.messageTypeNames.get(a.MessageType[p]),value:p})}}catch(e){i={error:e}}finally{try{d&&!d.done&&(l=u.return)&&l.call(u)}finally{if(i)throw i.error}}return s}var l;return y(r,e),l=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.ngOnInit=function(){var e=this;this.filteredMessageTypes=this.messageTypeConfigForm.get("messageType").valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(t){return e.fetchMessageTypes(t)})),f.share())},r.prototype.ngAfterViewInit=function(){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.messageTypeConfigForm.disable({emitEvent:!1}):this.messageTypeConfigForm.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t=this;this.searchText="",this.messageTypes.length=0,e&&e.forEach((function(e){var r=t.messageTypesList.find((function(t){return t.value===e}));r?t.messageTypes.push({name:r.name,value:r.value}):t.messageTypes.push({name:e,value:e})}))},r.prototype.displayMessageTypeFn=function(e){return e?e.name:void 0},r.prototype.textIsNotEmpty=function(e){return!!(e&&null!=e&&e.length>0)},r.prototype.createMessageType=function(e,t){e.preventDefault(),this.transformMessageType(t)},r.prototype.add=function(e){this.transformMessageType(e.value)},r.prototype.fetchMessageTypes=function(e){if(this.searchText=e,this.searchText&&this.searchText.length){var t=this.searchText.toUpperCase();return c.of(this.messageTypesList.filter((function(e){return e.name.toUpperCase().includes(t)})))}return c.of(this.messageTypesList)},r.prototype.transformMessageType=function(e){if((e||"").trim()){var t=null,r=e.trim(),n=this.messageTypesList.find((function(e){return e.name===r}));(t=n?{name:n.name,value:n.value}:{name:r,value:r})&&this.addMessageType(t)}this.clear("")},r.prototype.remove=function(e){var t=this.messageTypes.indexOf(e);t>=0&&(this.messageTypes.splice(t,1),this.updateModel())},r.prototype.selected=function(e){this.addMessageType(e.option.value),this.clear("")},r.prototype.addMessageType=function(e){-1===this.messageTypes.findIndex((function(t){return t.value===e.value}))&&(this.messageTypes.push(e),this.updateModel())},r.prototype.onFocus=function(){this.messageTypeConfigForm.get("messageType").updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.messageTypeInput.nativeElement.value=e,this.messageTypeConfigForm.get("messageType").patchValue(null,{emitEvent:!0}),setTimeout((function(){t.messageTypeInput.nativeElement.blur(),t.messageTypeInput.nativeElement.focus()}),0)},r.prototype.updateModel=function(){var e=this.messageTypes.map((function(e){return e.value}));this.required?(this.chipList.errorState=!e.length,this.propagateChange(e.length>0?e:null)):(this.chipList.errorState=!1,this.propagateChange(e))},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:a.TruncatePipe},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),b([t.Input(),h("design:type",String)],r.prototype,"label",void 0),b([t.Input(),h("design:type",Object)],r.prototype,"placeholder",void 0),b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.ViewChild("chipList",{static:!1}),h("design:type",d.MatChipList)],r.prototype,"chipList",void 0),b([t.ViewChild("messageTypeAutocomplete",{static:!1}),h("design:type",p.MatAutocomplete)],r.prototype,"matAutocomplete",void 0),b([t.ViewChild("messageTypeInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"messageTypeInput",void 0),r=l=b([t.Component({selector:"tb-message-types-config",template:'\n {{ label }}\n \n \n {{messageType.name}}\n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-message-types-found\n
\n \n \n {{ translate.get(\'tb.rulenode.no-message-type-matching\',\n {messageType: truncate.transform(searchText, true, 6, '...')}) | async }}\n \n \n \n tb.rulenode.create-new-message-type\n \n
\n
\n
\n \n {{ \'tb.rulenode.message-types-required\' | translate }}\n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return l})),multi:!0}]}),h("design:paramtypes",[o.Store,n.TranslateService,a.TruncatePipe,i.FormBuilder])],r)}(a.PageComponent),le=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.subscriptions=[],n.disableCertPemCredentials=!1,n.allCredentialsTypes=Q,n.credentialsTypeTranslationsMap=_,n.propagateChange=null,n}var n;return y(r,e),n=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.credentialsConfigFormGroup=this.fb.group({type:[null,[i.Validators.required]],username:[null,[]],password:[null,[]],caCert:[null,[]],caCertFileName:[null,[]],privateKey:[null,[]],privateKeyFileName:[null,[]],cert:[null,[]],certFileName:[null,[]]}),this.subscriptions.push(this.credentialsConfigFormGroup.valueChanges.pipe(f.distinctUntilChanged()).subscribe((function(){e.updateView()}))),this.subscriptions.push(this.credentialsConfigFormGroup.get("type").valueChanges.subscribe((function(){e.credentialsTypeChanged()})))},r.prototype.ngOnChanges=function(e){var t,r,n=this;try{for(var a=C(Object.keys(e)),o=a.next();!o.done;o=a.next()){var i=o.value,l=e[i];if(!l.firstChange&&l.currentValue!==l.previousValue)if(l.currentValue&&"disableCertPemCredentials"===i)"cert.PEM"===this.credentialsConfigFormGroup.get("type").value&&setTimeout((function(){n.credentialsConfigFormGroup.get("type").patchValue("anonymous",{emitEvent:!0})}))}}catch(e){t={error:e}}finally{try{o&&!o.done&&(r=a.return)&&r.call(a)}finally{if(t)throw t.error}}},r.prototype.ngOnDestroy=function(){this.subscriptions.forEach((function(e){return e.unsubscribe()}))},r.prototype.writeValue=function(e){s.isDefinedAndNotNull(e)&&(this.credentialsConfigFormGroup.reset(e,{emitEvent:!1}),this.updateValidators(!1))},r.prototype.setDisabledState=function(e){e?this.credentialsConfigFormGroup.disable():(this.credentialsConfigFormGroup.enable(),this.updateValidators())},r.prototype.updateView=function(){var e=this.credentialsConfigFormGroup.value,t=e.type;switch(t){case"anonymous":e={type:t};break;case"basic":e={type:t,username:e.username,password:e.password};break;case"cert.PEM":delete e.username}this.propagateChange(e)},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.validate=function(e){return this.credentialsConfigFormGroup.valid?null:{credentialsConfig:{valid:!1}}},r.prototype.credentialsTypeChanged=function(){this.credentialsConfigFormGroup.patchValue({username:null,password:null,caCert:null,caCertFileName:null,privateKey:null,privateKeyFileName:null,cert:null,certFileName:null}),this.updateValidators()},r.prototype.updateValidators=function(e){void 0===e&&(e=!1);var t=this.credentialsConfigFormGroup.get("type").value;switch(e&&this.credentialsConfigFormGroup.reset({type:t},{emitEvent:!1}),this.credentialsConfigFormGroup.setValidators([]),this.credentialsConfigFormGroup.get("username").setValidators([]),this.credentialsConfigFormGroup.get("password").setValidators([]),t){case"anonymous":break;case"basic":this.credentialsConfigFormGroup.get("username").setValidators([i.Validators.required]),this.credentialsConfigFormGroup.get("password").setValidators([i.Validators.required]);break;case"cert.PEM":this.credentialsConfigFormGroup.setValidators([this.requiredFilesSelected(i.Validators.required,[["caCert","caCertFileName"],["privateKey","privateKeyFileName","cert","certFileName"]])])}this.credentialsConfigFormGroup.get("username").updateValueAndValidity({emitEvent:e}),this.credentialsConfigFormGroup.get("password").updateValueAndValidity({emitEvent:e}),this.credentialsConfigFormGroup.updateValueAndValidity({emitEvent:e})},r.prototype.requiredFilesSelected=function(e,t){return void 0===t&&(t=null),function(r){return t||(t=[Object.keys(r.controls)]),(null==r?void 0:r.controls)&&t.some((function(t){return t.every((function(t){return!e(r.controls[t])}))}))?null:{notAllRequiredFilesSelected:!0}}},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),b([t.Input(),h("design:type",Object)],r.prototype,"disableCertPemCredentials",void 0),r=n=b([t.Component({selector:"tb-credentials-config",template:'
\n \n \n tb.rulenode.credentials\n \n {{ credentialsTypeTranslationsMap.get(credentialsConfigFormGroup.get(\'type\').value) | translate }}\n \n \n \n \n tb.rulenode.credentials-type\n \n \n {{ credentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.username\n \n \n {{ \'tb.rulenode.username-required\' | translate }}\n \n \n \n tb.rulenode.password\n \n \n {{ \'tb.rulenode.password-required\' | translate }}\n \n \n \n \n
{{ \'tb.rulenode.credentials-pem-hint\' | translate }}
\n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n
\n
\n
\n
\n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0},{provide:i.NG_VALIDATORS,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),se=function(){function e(){}return e=b([t.NgModule({declarations:[ne,ae,oe,ie,le],imports:[r.CommonModule,a.SharedModule,l.HomeComponentsModule],exports:[ne,ae,oe,ie,le]})],e)}(),me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.unassignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.unassignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-un-assign-to-customer-config",template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ue=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.snsConfigForm},r.prototype.onConfigurationSet=function(e){this.snsConfigForm=this.fb.group({topicArnPattern:[e?e.topicArnPattern:null,[i.Validators.required]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-sns-config",template:'
\n \n tb.rulenode.topic-arn-pattern\n \n \n {{ \'tb.rulenode.topic-arn-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),de=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.sqsQueueType=j,n.sqsQueueTypes=Object.keys(j),n.sqsQueueTypeTranslationsMap=$,n}return y(r,e),r.prototype.configForm=function(){return this.sqsConfigForm},r.prototype.onConfigurationSet=function(e){this.sqsConfigForm=this.fb.group({queueType:[e?e.queueType:null,[i.Validators.required]],queueUrlPattern:[e?e.queueUrlPattern:null,[i.Validators.required]],delaySeconds:[e?e.delaySeconds:null,[i.Validators.min(0),i.Validators.max(900)]],messageAttributes:[e?e.messageAttributes:null,[]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-sqs-config",template:'
\n \n tb.rulenode.queue-type\n \n \n {{ sqsQueueTypeTranslationsMap.get(type) | translate }}\n \n \n \n \n tb.rulenode.queue-url-pattern\n \n \n {{ \'tb.rulenode.queue-url-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.delay-seconds\n \n \n {{ \'tb.rulenode.min-delay-seconds-message\' | translate }}\n \n \n {{ \'tb.rulenode.max-delay-seconds-message\' | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),pe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.pubSubConfigForm},r.prototype.onConfigurationSet=function(e){this.pubSubConfigForm=this.fb.group({projectId:[e?e.projectId:null,[i.Validators.required]],topicName:[e?e.topicName:null,[i.Validators.required]],serviceAccountKey:[e?e.serviceAccountKey:null,[i.Validators.required]],serviceAccountKeyFileName:[e?e.serviceAccountKeyFileName:null,[i.Validators.required]],messageAttributes:[e?e.messageAttributes:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-pub-sub-config",template:'
\n \n tb.rulenode.gcp-project-id\n \n \n {{ \'tb.rulenode.gcp-project-id-required\' | translate }}\n \n \n \n tb.rulenode.pubsub-topic-name\n \n \n {{ \'tb.rulenode.pubsub-topic-name-required\' | translate }}\n \n \n \n \n \n
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ce=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.ackValues=["all","-1","0","1"],n.ToByteStandartCharsetTypesValues=Y,n.ToByteStandartCharsetTypeTranslationMap=Z,n}return y(r,e),r.prototype.configForm=function(){return this.kafkaConfigForm},r.prototype.onConfigurationSet=function(e){this.kafkaConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],bootstrapServers:[e?e.bootstrapServers:null,[i.Validators.required]],retries:[e?e.retries:null,[i.Validators.min(0)]],batchSize:[e?e.batchSize:null,[i.Validators.min(0)]],linger:[e?e.linger:null,[i.Validators.min(0)]],bufferMemory:[e?e.bufferMemory:null,[i.Validators.min(0)]],acks:[e?e.acks:null,[i.Validators.required]],keySerializer:[e?e.keySerializer:null,[i.Validators.required]],valueSerializer:[e?e.valueSerializer:null,[i.Validators.required]],otherProperties:[e?e.otherProperties:null,[]],addMetadataKeyValuesAsKafkaHeaders:[!!e&&e.addMetadataKeyValuesAsKafkaHeaders,[]],kafkaHeadersCharset:[e?e.kafkaHeadersCharset:null,[]]})},r.prototype.validatorTriggers=function(){return["addMetadataKeyValuesAsKafkaHeaders"]},r.prototype.updateValidators=function(e){this.kafkaConfigForm.get("addMetadataKeyValuesAsKafkaHeaders").value?this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([i.Validators.required]):this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([]),this.kafkaConfigForm.get("kafkaHeadersCharset").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-kafka-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \n \n tb.rulenode.bootstrap-servers\n \n \n {{ \'tb.rulenode.bootstrap-servers-required\' | translate }}\n \n \n \n tb.rulenode.retries\n \n \n {{ \'tb.rulenode.min-retries-message\' | translate }}\n \n \n \n tb.rulenode.batch-size-bytes\n \n \n {{ \'tb.rulenode.min-batch-size-bytes-message\' | translate }}\n \n \n \n tb.rulenode.linger-ms\n \n \n {{ \'tb.rulenode.min-linger-ms-message\' | translate }}\n \n \n \n tb.rulenode.buffer-memory-bytes\n \n \n {{ \'tb.rulenode.min-buffer-memory-bytes-message\' | translate }}\n \n \n \n tb.rulenode.acks\n \n \n {{ ackValue }}\n \n \n \n \n tb.rulenode.key-serializer\n \n \n {{ \'tb.rulenode.key-serializer-required\' | translate }}\n \n \n \n tb.rulenode.value-serializer\n \n \n {{ \'tb.rulenode.value-serializer-required\' | translate }}\n \n \n \n \n \n \n {{ \'tb.rulenode.add-metadata-key-values-as-kafka-headers\' | translate }}\n \n
tb.rulenode.add-metadata-key-values-as-kafka-headers-hint
\n \n tb.rulenode.charset-encoding\n \n \n {{ ToByteStandartCharsetTypeTranslationMap.get(charset) | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),fe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.mqttConfigForm},r.prototype.onConfigurationSet=function(e){this.mqttConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(200)]],clientId:[e?e.clientId:null,[]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:[e?e.credentials:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-mqtt-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n \n tb.rulenode.connect-timeout\n \n \n {{ \'tb.rulenode.connect-timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n
\n \n tb.rulenode.client-id\n \n \n \n {{ \'tb.rulenode.clean-session\' | translate }}\n \n \n {{ \'tb.rulenode.enable-ssl\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ge=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],n}return y(r,e),r.prototype.configForm=function(){return this.rabbitMqConfigForm},r.prototype.onConfigurationSet=function(e){this.rabbitMqConfigForm=this.fb.group({exchangeNamePattern:[e?e.exchangeNamePattern:null,[]],routingKeyPattern:[e?e.routingKeyPattern:null,[]],messageProperties:[e?e.messageProperties:null,[]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],virtualHost:[e?e.virtualHost:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]],automaticRecoveryEnabled:[!!e&&e.automaticRecoveryEnabled,[]],connectionTimeout:[e?e.connectionTimeout:null,[i.Validators.min(0)]],handshakeTimeout:[e?e.handshakeTimeout:null,[i.Validators.min(0)]],clientProperties:[e?e.clientProperties:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rabbit-mq-config",template:'
\n \n tb.rulenode.exchange-name-pattern\n \n \n \n tb.rulenode.routing-key-pattern\n \n \n \n tb.rulenode.message-properties\n \n \n {{ property }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n
\n \n tb.rulenode.virtual-host\n \n \n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n \n {{ \'tb.rulenode.automatic-recovery\' | translate }}\n \n \n tb.rulenode.connection-timeout-ms\n \n \n {{ \'tb.rulenode.min-connection-timeout-ms-message\' | translate }}\n \n \n \n tb.rulenode.handshake-timeout-ms\n \n \n {{ \'tb.rulenode.min-handshake-timeout-ms-message\' | translate }}\n \n \n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ye=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.proxySchemes=["http","https"],n.httpRequestTypes=Object.keys(z),n}return y(r,e),r.prototype.configForm=function(){return this.restApiCallConfigForm},r.prototype.onConfigurationSet=function(e){this.restApiCallConfigForm=this.fb.group({restEndpointUrlPattern:[e?e.restEndpointUrlPattern:null,[i.Validators.required]],requestMethod:[e?e.requestMethod:null,[i.Validators.required]],useSimpleClientHttpFactory:[!!e&&e.useSimpleClientHttpFactory,[]],enableProxy:[!!e&&e.enableProxy,[]],useSystemProxyProperties:[!!e&&e.enableProxy,[]],proxyScheme:[e?e.proxyHost:null,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],readTimeoutMs:[e?e.readTimeoutMs:null,[]],maxParallelRequestsCount:[e?e.maxParallelRequestsCount:null,[i.Validators.min(0)]],headers:[e?e.headers:null,[]],useRedisQueueForMsgPersistence:[!!e&&e.useRedisQueueForMsgPersistence,[]],trimQueue:[!!e&&e.trimQueue,[]],maxQueueSize:[e?e.maxQueueSize:null,[]],credentials:[e?e.credentials:null,[]]})},r.prototype.validatorTriggers=function(){return["useSimpleClientHttpFactory","useRedisQueueForMsgPersistence","enableProxy","useSystemProxyProperties"]},r.prototype.updateValidators=function(e){var t=this.restApiCallConfigForm.get("useSimpleClientHttpFactory").value,r=this.restApiCallConfigForm.get("useRedisQueueForMsgPersistence").value,n=this.restApiCallConfigForm.get("enableProxy").value,a=this.restApiCallConfigForm.get("useSystemProxyProperties").value;n&&!a?(this.restApiCallConfigForm.get("proxyHost").setValidators(n?[i.Validators.required]:[]),this.restApiCallConfigForm.get("proxyPort").setValidators(n?[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]:[])):(this.restApiCallConfigForm.get("proxyHost").setValidators([]),this.restApiCallConfigForm.get("proxyPort").setValidators([]),t?this.restApiCallConfigForm.get("readTimeoutMs").setValidators([]):this.restApiCallConfigForm.get("readTimeoutMs").setValidators([i.Validators.min(0)])),r?this.restApiCallConfigForm.get("maxQueueSize").setValidators([i.Validators.min(0)]):this.restApiCallConfigForm.get("maxQueueSize").setValidators([]),this.restApiCallConfigForm.get("readTimeoutMs").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("maxQueueSize").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("credentials").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rest-api-call-config",template:'
\n \n tb.rulenode.endpoint-url-pattern\n \n \n {{ \'tb.rulenode.endpoint-url-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.request-method\n \n \n {{ requestType }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n \n {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}\n \n
\n \n {{ \'tb.rulenode.use-system-proxy-properties\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-scheme\n \n \n {{ proxyScheme }}\n \n \n \n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n
\n \n tb.rulenode.read-timeout\n \n \n \n \n tb.rulenode.max-parallel-requests-count\n \n \n \n \n
\n \n \n \n {{ \'tb.rulenode.use-redis-queue\' | translate }}\n \n
\n \n {{ \'tb.rulenode.trim-redis-queue\' | translate }}\n \n \n tb.rulenode.redis-queue-max-size\n \n \n
\n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),be=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.smtpProtocols=["smtp","smtps"],n.tlsVersions=["TLSv1","TLSv1.1","TLSv1.2","TLSv1.3"],n}return y(r,e),r.prototype.configForm=function(){return this.sendEmailConfigForm},r.prototype.onConfigurationSet=function(e){this.sendEmailConfigForm=this.fb.group({useSystemSmtpSettings:[!!e&&e.useSystemSmtpSettings,[]],smtpProtocol:[e?e.smtpProtocol:null,[]],smtpHost:[e?e.smtpHost:null,[]],smtpPort:[e?e.smtpPort:null,[]],timeout:[e?e.timeout:null,[]],enableTls:[!!e&&e.enableTls,[]],tlsVersion:[e?e.tlsVersion:null,[]],enableProxy:[!!e&&e.enableProxy,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]]})},r.prototype.validatorTriggers=function(){return["useSystemSmtpSettings","enableProxy"]},r.prototype.updateValidators=function(e){var t=this.sendEmailConfigForm.get("useSystemSmtpSettings").value,r=this.sendEmailConfigForm.get("enableProxy").value;t?(this.sendEmailConfigForm.get("smtpProtocol").setValidators([]),this.sendEmailConfigForm.get("smtpHost").setValidators([]),this.sendEmailConfigForm.get("smtpPort").setValidators([]),this.sendEmailConfigForm.get("timeout").setValidators([]),this.sendEmailConfigForm.get("proxyHost").setValidators([]),this.sendEmailConfigForm.get("proxyPort").setValidators([])):(this.sendEmailConfigForm.get("smtpProtocol").setValidators([i.Validators.required]),this.sendEmailConfigForm.get("smtpHost").setValidators([i.Validators.required]),this.sendEmailConfigForm.get("smtpPort").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]),this.sendEmailConfigForm.get("timeout").setValidators([i.Validators.required,i.Validators.min(0)]),this.sendEmailConfigForm.get("proxyHost").setValidators(r?[i.Validators.required]:[]),this.sendEmailConfigForm.get("proxyPort").setValidators(r?[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]:[])),this.sendEmailConfigForm.get("smtpProtocol").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpPort").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("timeout").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-send-email-config",template:'
\n \n {{ \'tb.rulenode.use-system-smtp-settings\' | translate }}\n \n
\n \n tb.rulenode.smtp-protocol\n \n \n {{ smtpProtocol.toUpperCase() }}\n \n \n \n
\n \n tb.rulenode.smtp-host\n \n \n {{ \'tb.rulenode.smtp-host-required\' | translate }}\n \n \n \n tb.rulenode.smtp-port\n \n \n {{ \'tb.rulenode.smtp-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.timeout-msec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-msec-message\' | translate }}\n \n \n \n {{ \'tb.rulenode.enable-tls\' | translate }}\n \n \n tb.rulenode.tls-version\n \n \n {{ tlsVersion }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),he=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.serviceType=a.ServiceType.TB_RULE_ENGINE,n}return y(r,e),r.prototype.configForm=function(){return this.checkPointConfigForm},r.prototype.onConfigurationSet=function(e){this.checkPointConfigForm=this.fb.group({queueName:[e?e.queueName:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-check-point-config",template:'
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ce=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allAzureIotHubCredentialsTypes=W,n.azureIotHubCredentialsTypeTranslationsMap=J,n}return y(r,e),r.prototype.configForm=function(){return this.azureIotHubConfigForm},r.prototype.onConfigurationSet=function(e){this.azureIotHubConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(200)]],clientId:[e?e.clientId:null,[i.Validators.required]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[i.Validators.required]],sasKey:[e&&e.credentials?e.credentials.sasKey:null,[]],caCert:[e&&e.credentials?e.credentials.caCert:null,[]],caCertFileName:[e&&e.credentials?e.credentials.caCertFileName:null,[]],privateKey:[e&&e.credentials?e.credentials.privateKey:null,[]],privateKeyFileName:[e&&e.credentials?e.credentials.privateKeyFileName:null,[]],cert:[e&&e.credentials?e.credentials.cert:null,[]],certFileName:[e&&e.credentials?e.credentials.certFileName:null,[]],password:[e&&e.credentials?e.credentials.password:null,[]]})})},r.prototype.prepareOutputConfig=function(e){var t=e.credentials.type;return"sas"===t&&(e.credentials={type:t,sasKey:e.credentials.sasKey,caCert:e.credentials.caCert,caCertFileName:e.credentials.caCertFileName}),e},r.prototype.validatorTriggers=function(){return["credentials.type"]},r.prototype.updateValidators=function(e){var t=this.azureIotHubConfigForm.get("credentials"),r=t.get("type").value;switch(e&&t.reset({type:r},{emitEvent:!1}),t.get("sasKey").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),r){case"sas":t.get("sasKey").setValidators([i.Validators.required]);break;case"cert.PEM":t.get("privateKey").setValidators([i.Validators.required]),t.get("privateKeyFileName").setValidators([i.Validators.required]),t.get("cert").setValidators([i.Validators.required]),t.get("certFileName").setValidators([i.Validators.required])}t.get("sasKey").updateValueAndValidity({emitEvent:e}),t.get("privateKey").updateValueAndValidity({emitEvent:e}),t.get("privateKeyFileName").updateValueAndValidity({emitEvent:e}),t.get("cert").updateValueAndValidity({emitEvent:e}),t.get("certFileName").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-azure-iot-hub-config",template:'
\n \n tb.rulenode.topic\n \n \n {{ \'tb.rulenode.topic-required\' | translate }}\n \n \n \n tb.rulenode.hostname\n \n \n {{ \'tb.rulenode.hostname-required\' | translate }}\n \n \n \n tb.rulenode.device-id\n \n \n {{ \'tb.rulenode.device-id-required\' | translate }}\n \n \n \n \n \n tb.rulenode.credentials\n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(azureIotHubConfigForm.get(\'credentials.type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.sas-key\n \n \n {{ \'tb.rulenode.sas-key-required\' | translate }}\n \n \n \n \n \n \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n
\n
\n
\n
\n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ve=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.deviceProfile},r.prototype.onConfigurationSet=function(e){this.deviceProfile=this.fb.group({persistAlarmRulesState:[!!e&&e.persistAlarmRulesState,i.Validators.required],fetchAlarmRulesStateOnStart:[!!e&&e.fetchAlarmRulesStateOnStart,i.Validators.required]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-device-profile-config",template:'
\n \n {{ \'tb.rulenode.persist-alarm-rules\' | translate }}\n \n \n {{ \'tb.rulenode.fetch-alarm-rules\' | translate }}\n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Fe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.sendSmsConfigForm},r.prototype.onConfigurationSet=function(e){this.sendSmsConfigForm=this.fb.group({numbersToTemplate:[e?e.numbersToTemplate:null,[i.Validators.required]],smsMessageTemplate:[e?e.smsMessageTemplate:null,[i.Validators.required]],useSystemSmsSettings:[!!e&&e.useSystemSmsSettings,[]],smsProviderConfiguration:[e?e.smsProviderConfiguration:null,[]]})},r.prototype.validatorTriggers=function(){return["useSystemSmsSettings"]},r.prototype.updateValidators=function(e){this.sendSmsConfigForm.get("useSystemSmsSettings").value?this.sendSmsConfigForm.get("smsProviderConfiguration").setValidators([]):this.sendSmsConfigForm.get("smsProviderConfiguration").setValidators([i.Validators.required]),this.sendSmsConfigForm.get("smsProviderConfiguration").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-send-sms-config",template:'
\n \n tb.rulenode.numbers-to-template\n \n \n {{ \'tb.rulenode.numbers-to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.sms-message-template\n \n \n {{ \'tb.rulenode.sms-message-template-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.use-system-sms-settings\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),xe=function(){function e(){}return e=b([t.NgModule({declarations:[x,T,q,S,I,k,N,V,E,A,L,X,ee,te,re,me,ue,de,pe,ce,fe,ge,ye,be,he,Ce,ve,Fe],imports:[r.CommonModule,a.SharedModule,l.HomeComponentsModule,se],exports:[x,T,q,S,I,k,N,V,E,A,L,X,ee,te,re,me,ue,de,pe,ce,fe,ge,ye,be,he,Ce,ve,Fe]})],e)}(),Te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.checkMessageConfigForm},r.prototype.onConfigurationSet=function(e){this.checkMessageConfigForm=this.fb.group({messageNames:[e?e.messageNames:null,[]],metadataNames:[e?e.metadataNames:null,[]],checkAllKeys:[!!e&&e.checkAllKeys,[]]})},r.prototype.validateConfig=function(){var e=this.checkMessageConfigForm.get("messageNames").value,t=this.checkMessageConfigForm.get("metadataNames").value;return e.length>0||t.length>0},r.prototype.removeMessageName=function(e){var t=this.checkMessageConfigForm.get("messageNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("messageNames").setValue(t,{emitEvent:!0}))},r.prototype.removeMetadataName=function(e){var t=this.checkMessageConfigForm.get("metadataNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("metadataNames").setValue(t,{emitEvent:!0}))},r.prototype.addMessageName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("messageNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("messageNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.prototype.addMetadataName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("metadataNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("metadataNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-check-message-config",template:'
\n \n \n \n \n \n {{messageName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n \n \n \n \n {{metadataName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n {{ \'tb.rulenode.check-all-keys\' | translate }}\n \n
tb.rulenode.check-all-keys-hint
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),qe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.entitySearchDirection=Object.keys(a.EntitySearchDirection),n.entitySearchDirectionTranslationsMap=a.entitySearchDirectionTranslations,n}return y(r,e),r.prototype.configForm=function(){return this.checkRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.checkRelationConfigForm=this.fb.group({checkForSingleEntity:[!!e&&e.checkForSingleEntity,[]],direction:[e?e.direction:null,[]],entityType:[e?e.entityType:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],entityId:[e?e.entityId:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],relationType:[e?e.relationType:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["checkForSingleEntity"]},r.prototype.updateValidators=function(e){var t=this.checkRelationConfigForm.get("checkForSingleEntity").value;this.checkRelationConfigForm.get("entityType").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:e}),this.checkRelationConfigForm.get("entityId").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityId").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-check-relation-config",template:'
\n \n {{ \'tb.rulenode.check-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.check-relation-hint
\n \n relation.direction\n \n \n {{ entitySearchDirectionTranslationsMap.get(direction) | translate }}\n \n \n \n
\n \n \n \n \n
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=M,n.perimeterTypes=Object.keys(M),n.perimeterTypeTranslationMap=w,n.rangeUnits=Object.keys(O),n.rangeUnitTranslationMap=B,n}return y(r,e),r.prototype.configForm=function(){return this.geoFilterConfigForm},r.prototype.onConfigurationSet=function(e){this.geoFilterConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType:null,[]],centerLatitude:[e?e.centerLatitude:null,[]],centerLongitude:[e?e.centerLatitude:null,[]],range:[e?e.range:null,[]],rangeUnit:[e?e.rangeUnit:null,[]],polygonsDefinition:[e?e.polygonsDefinition:null,[]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoFilterConfigForm.get("perimeterType").value;t?this.geoFilterConfigForm.get("perimeterType").setValidators([]):this.geoFilterConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==M.CIRCLE?(this.geoFilterConfigForm.get("centerLatitude").setValidators([]),this.geoFilterConfigForm.get("centerLongitude").setValidators([]),this.geoFilterConfigForm.get("range").setValidators([]),this.geoFilterConfigForm.get("rangeUnit").setValidators([])):(this.geoFilterConfigForm.get("centerLatitude").setValidators([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoFilterConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoFilterConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoFilterConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==M.POLYGON?this.geoFilterConfigForm.get("polygonsDefinition").setValidators([]):this.geoFilterConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoFilterConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),this.geoFilterConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-gps-geofencing-config",template:'
\n \n tb.rulenode.latitude-key-name\n \n \n {{ \'tb.rulenode.latitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.longitude-key-name\n \n \n {{ \'tb.rulenode.longitude-key-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.circle-center-latitude\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n tb.rulenode.circle-center-longitude\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.range\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n tb.rulenode.range-units\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ie=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.messageTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.messageTypeConfigForm=this.fb.group({messageTypes:[e?e.messageTypes:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-message-type-config",template:'
\n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ke=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allowedEntityTypes=[a.EntityType.DEVICE,a.EntityType.ASSET,a.EntityType.ENTITY_VIEW,a.EntityType.TENANT,a.EntityType.CUSTOMER,a.EntityType.USER,a.EntityType.DASHBOARD,a.EntityType.RULE_CHAIN,a.EntityType.RULE_NODE],n}return y(r,e),r.prototype.configForm=function(){return this.originatorTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorTypeConfigForm=this.fb.group({originatorTypes:[e?e.originatorTypes:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-originator-type-config",template:'
\n \n \n \n
\n',styles:[":host ::ng-deep tb-entity-type-list .mat-form-field-flex{padding-top:0}:host ::ng-deep tb-entity-type-list .mat-form-field-infix{border-top:0}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ne=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"filter",this.translate.instant("tb.rulenode.filter"),"Filter",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-filter-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Ve=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.switchConfigForm},r.prototype.onConfigurationSet=function(e){this.switchConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.switchConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"switch",this.translate.instant("tb.rulenode.switch"),"Switch",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.switchConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-filter-node-switch-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Ee=function(e){function r(t,r,n){var o,l,s=e.call(this,t)||this;s.store=t,s.translate=r,s.fb=n,s.alarmStatusTranslationsMap=a.alarmStatusTranslations,s.alarmStatusList=[],s.searchText="",s.displayStatusFn=s.displayStatus.bind(s);try{for(var m=C(Object.keys(a.AlarmStatus)),u=m.next();!u.done;u=m.next()){var d=u.value;s.alarmStatusList.push(a.AlarmStatus[d])}}catch(e){o={error:e}}finally{try{u&&!u.done&&(l=m.return)&&l.call(m)}finally{if(o)throw o.error}}return s.statusFormControl=new i.FormControl(""),s.filteredAlarmStatus=s.statusFormControl.valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(e){return s.fetchAlarmStatus(e)})),f.share()),s}return y(r,e),r.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},r.prototype.configForm=function(){return this.alarmStatusConfigForm},r.prototype.prepareInputConfig=function(e){return this.searchText="",this.statusFormControl.patchValue("",{emitEvent:!0}),e},r.prototype.onConfigurationSet=function(e){this.alarmStatusConfigForm=this.fb.group({alarmStatusList:[e?e.alarmStatusList:null,[i.Validators.required]]})},r.prototype.displayStatus=function(e){return e?this.translate.instant(a.alarmStatusTranslations.get(e)):void 0},r.prototype.fetchAlarmStatus=function(e){var t=this,r=this.getAlarmStatusList();if(this.searchText=e,this.searchText&&this.searchText.length){var n=this.searchText.toUpperCase();return c.of(r.filter((function(e){return t.translate.instant(a.alarmStatusTranslations.get(a.AlarmStatus[e])).toUpperCase().includes(n)})))}return c.of(r)},r.prototype.alarmStatusSelected=function(e){this.addAlarmStatus(e.option.value),this.clear("")},r.prototype.removeAlarmStatus=function(e){var t=this.alarmStatusConfigForm.get("alarmStatusList").value;if(t){var r=t.indexOf(e);r>=0&&(t.splice(r,1),this.alarmStatusConfigForm.get("alarmStatusList").setValue(t))}},r.prototype.addAlarmStatus=function(e){var t=this.alarmStatusConfigForm.get("alarmStatusList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.alarmStatusConfigForm.get("alarmStatusList").setValue(t))},r.prototype.getAlarmStatusList=function(){var e=this;return this.alarmStatusList.filter((function(t){return-1===e.alarmStatusConfigForm.get("alarmStatusList").value.indexOf(t)}))},r.prototype.onAlarmStatusInputFocus=function(){this.statusFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.alarmStatusInput.nativeElement.value=e,this.statusFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.alarmStatusInput.nativeElement.blur(),t.alarmStatusInput.nativeElement.focus()}),0)},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:i.FormBuilder}]},b([t.ViewChild("alarmStatusInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"alarmStatusInput",void 0),r=b([t.Component({selector:"tb-filter-node-check-alarm-status-config",template:'
\n \n tb.rulenode.alarm-status-filter\n \n \n \n {{alarmStatusTranslationsMap.get(alarmStatus) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-alarm-status-matching\n
\n
\n
\n
\n
\n \n
\n\n\n\n'}),h("design:paramtypes",[o.Store,n.TranslateService,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ae=function(){function e(){}return e=b([t.NgModule({declarations:[Te,qe,Se,Ie,ke,Ne,Ve,Ee],imports:[r.CommonModule,a.SharedModule,se],exports:[Te,qe,Se,Ie,ke,Ne,Ve,Ee]})],e)}(),Le=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.customerAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.customerAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-customer-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Me=function(e){function r(t,r,n){var a,o,l=e.call(this,t)||this;l.store=t,l.translate=r,l.fb=n,l.entityDetailsTranslationsMap=H,l.entityDetailsList=[],l.searchText="",l.displayDetailsFn=l.displayDetails.bind(l);try{for(var s=C(Object.keys(K)),m=s.next();!m.done;m=s.next()){var u=m.value;l.entityDetailsList.push(K[u])}}catch(e){a={error:e}}finally{try{m&&!m.done&&(o=s.return)&&o.call(s)}finally{if(a)throw a.error}}return l.detailsFormControl=new i.FormControl(""),l.filteredEntityDetails=l.detailsFormControl.valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(e){return l.fetchEntityDetails(e)})),f.share()),l}return y(r,e),r.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},r.prototype.configForm=function(){return this.entityDetailsConfigForm},r.prototype.prepareInputConfig=function(e){return this.searchText="",this.detailsFormControl.patchValue("",{emitEvent:!0}),e},r.prototype.onConfigurationSet=function(e){this.entityDetailsConfigForm=this.fb.group({detailsList:[e?e.detailsList:null,[i.Validators.required]],addToMetadata:[!!e&&e.addToMetadata,[]]})},r.prototype.displayDetails=function(e){return e?this.translate.instant(H.get(e)):void 0},r.prototype.fetchEntityDetails=function(e){var t=this;if(this.searchText=e,this.searchText&&this.searchText.length){var r=this.searchText.toUpperCase();return c.of(this.entityDetailsList.filter((function(e){return t.translate.instant(H.get(K[e])).toUpperCase().includes(r)})))}return c.of(this.entityDetailsList)},r.prototype.detailsFieldSelected=function(e){this.addDetailsField(e.option.value),this.clear("")},r.prototype.removeDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;if(t){var r=t.indexOf(e);r>=0&&(t.splice(r,1),this.entityDetailsConfigForm.get("detailsList").setValue(t))}},r.prototype.addDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.entityDetailsConfigForm.get("detailsList").setValue(t))},r.prototype.onEntityDetailsInputFocus=function(){this.detailsFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.detailsInput.nativeElement.value=e,this.detailsFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.detailsInput.nativeElement.blur(),t.detailsInput.nativeElement.focus()}),0)},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:i.FormBuilder}]},b([t.ViewChild("detailsInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"detailsInput",void 0),r=b([t.Component({selector:"tb-enrichment-node-entity-details-config",template:'
\n \n tb.rulenode.entity-details\n \n \n \n {{entityDetailsTranslationsMap.get(details) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-entity-details-matching\n
\n
\n
\n
\n
\n \n \n {{ \'tb.rulenode.add-to-metadata\' | translate }}\n \n
tb.rulenode.add-to-metadata-hint
\n
\n',styles:[":host ::ng-deep mat-form-field.entity-fields-list .mat-form-field-wrapper{margin-bottom:-1.25em}"]}),h("design:paramtypes",[o.Store,n.TranslateService,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Pe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.deviceAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.deviceAttributesConfigForm=this.fb.group({deviceRelationsQuery:[e?e.deviceRelationsQuery:null,[i.Validators.required]],tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.deviceAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.deviceAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.deviceAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.deviceAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-device-attributes-config",template:'
\n \n \n \n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Re=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.originatorAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorAttributesConfigForm=this.fb.group({tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.originatorAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.originatorAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.originatorAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.originatorAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-originator-attributes-config",template:'
\n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),we=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.originatorFieldsConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorFieldsConfigForm=this.fb.group({fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-originator-fields-config",template:'
\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Oe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],n.fetchMode=G,n.fetchModes=Object.keys(G),n.samplingOrders=Object.keys(U),n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=D,n}return y(r,e),r.prototype.configForm=function(){return this.getTelemetryFromDatabaseConfigForm},r.prototype.onConfigurationSet=function(e){this.getTelemetryFromDatabaseConfigForm=this.fb.group({latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],fetchMode:[e?e.fetchMode:null,[i.Validators.required]],orderBy:[e?e.orderBy:null,[]],limit:[e?e.limit:null,[]],useMetadataIntervalPatterns:[!!e&&e.useMetadataIntervalPatterns,[]],startInterval:[e?e.startInterval:null,[]],startIntervalTimeUnit:[e?e.startIntervalTimeUnit:null,[]],endInterval:[e?e.endInterval:null,[]],endIntervalTimeUnit:[e?e.endIntervalTimeUnit:null,[]],startIntervalPattern:[e?e.startIntervalPattern:null,[]],endIntervalPattern:[e?e.endIntervalPattern:null,[]]})},r.prototype.validatorTriggers=function(){return["fetchMode","useMetadataIntervalPatterns"]},r.prototype.updateValidators=function(e){var t=this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value,r=this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").value;t&&t===G.ALL?(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([i.Validators.required,i.Validators.min(2),i.Validators.max(1e3)])):(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([])),r?(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([i.Validators.required])):(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([])),this.getTelemetryFromDatabaseConfigForm.get("orderBy").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("limit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").updateValueAndValidity({emitEvent:e})},r.prototype.removeKey=function(e,t){var r=this.getTelemetryFromDatabaseConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.getTelemetryFromDatabaseConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-get-telemetry-from-database",template:'
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n tb.rulenode.fetch-mode\n \n \n {{ mode }}\n \n \n tb.rulenode.fetch-mode-hint\n \n
\n \n tb.rulenode.order-by\n \n \n {{ order }}\n \n \n tb.rulenode.order-by-hint\n \n \n tb.rulenode.limit\n \n tb.rulenode.limit-hint\n \n
\n \n {{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-interval-patterns-hint
\n
\n
\n \n tb.rulenode.start-interval\n \n \n {{ \'tb.rulenode.start-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.start-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.end-interval\n \n \n {{ \'tb.rulenode.end-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.end-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n \n tb.rulenode.start-interval-pattern\n \n \n {{ \'tb.rulenode.start-interval-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.end-interval-pattern\n \n \n {{ \'tb.rulenode.end-interval-pattern-required\' | translate }}\n \n \n \n \n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),De=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.relatedAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.relatedAttributesConfigForm=this.fb.group({relationsQuery:[e?e.relationsQuery:null,[i.Validators.required]],telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-related-attributes-config",template:'
\n \n \n \n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ke=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.tenantAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.tenantAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-tenant-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Be=function(){function e(){}return e=b([t.NgModule({declarations:[Le,Me,Pe,Re,we,Oe,De,Ke],imports:[r.CommonModule,a.SharedModule,se],exports:[Le,Me,Pe,Re,we,Oe,De,Ke]})],e)}(),Ge=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.originatorSource=v,n.originatorSources=Object.keys(v),n.originatorSourceTranslationMap=P,n}return y(r,e),r.prototype.configForm=function(){return this.changeOriginatorConfigForm},r.prototype.onConfigurationSet=function(e){this.changeOriginatorConfigForm=this.fb.group({originatorSource:[e?e.originatorSource:null,[i.Validators.required]],relationsQuery:[e?e.relationsQuery:null,[]]})},r.prototype.validatorTriggers=function(){return["originatorSource"]},r.prototype.updateValidators=function(e){var t=this.changeOriginatorConfigForm.get("originatorSource").value;t&&t===v.RELATED?this.changeOriginatorConfigForm.get("relationsQuery").setValidators([i.Validators.required]):this.changeOriginatorConfigForm.get("relationsQuery").setValidators([]),this.changeOriginatorConfigForm.get("relationsQuery").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-transformation-node-change-originator-config",template:'
\n \n tb.rulenode.originator-source\n \n \n {{ originatorSourceTranslationMap.get(source) | translate }}\n \n \n \n
\n \n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ue=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"update",this.translate.instant("tb.rulenode.transformer"),"Transform",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-transformation-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),je=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.toEmailConfigForm},r.prototype.onConfigurationSet=function(e){this.toEmailConfigForm=this.fb.group({fromTemplate:[e?e.fromTemplate:null,[i.Validators.required]],toTemplate:[e?e.toTemplate:null,[i.Validators.required]],ccTemplate:[e?e.ccTemplate:null,[]],bccTemplate:[e?e.bccTemplate:null,[]],subjectTemplate:[e?e.subjectTemplate:null,[i.Validators.required]],bodyTemplate:[e?e.bodyTemplate:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-transformation-node-to-email-config",template:'
\n \n tb.rulenode.from-template\n \n \n {{ \'tb.rulenode.from-template-required\' | translate }}\n \n \n \n \n tb.rulenode.to-template\n \n \n {{ \'tb.rulenode.to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.cc-template\n \n \n \n \n tb.rulenode.bcc-template\n \n \n \n \n tb.rulenode.subject-template\n \n \n {{ \'tb.rulenode.subject-template-required\' | translate }}\n \n \n \n \n tb.rulenode.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),He=function(){function e(){}return e=b([t.NgModule({declarations:[Ge,Ue,je],imports:[r.CommonModule,a.SharedModule,se],exports:[Ge,Ue,je]})],e)}(),ze=function(){function e(e){!function(e){e.setTranslation("en_US",{tb:{rulenode:{"create-entity-if-not-exists":"Create new entity if not exists","create-entity-if-not-exists-hint":"Create a new entity set above if it does not exist.","entity-name-pattern":"Name pattern","entity-name-pattern-required":"Name pattern is required","entity-name-pattern-hint":"Name pattern, use ${metaKeyName} to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use ${metaKeyName} to substitute variables from metadata","entity-cache-expiration":"Entities cache expiration time (sec)","entity-cache-expiration-hint":"Specifies maximum time interval allowed to store found entity records. 0 value means that records will never expire.","entity-cache-expiration-required":"Entities cache expiration time is required.","entity-cache-expiration-range":"Entities cache expiration time should be greater than or equal to 0.","customer-name-pattern":"Customer name pattern","customer-name-pattern-required":"Customer name pattern is required","create-customer-if-not-exists":"Create new customer if not exists","customer-cache-expiration":"Customers cache expiration time (sec)","customer-name-pattern-hint":"Customer name pattern, use ${metaKeyName} to substitute variables from metadata","customer-cache-expiration-hint":"Specifies maximum time interval allowed to store found customer records. 0 value means that records will never expire.","customer-cache-expiration-required":"Customers cache expiration time is required.","customer-cache-expiration-range":"Customers cache expiration time should be greater than or equal to 0.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. In case you want to fetch a single entry, select fetch mode 'FIRST' or 'LAST'.","time-unit-milliseconds":"Milliseconds","time-unit-seconds":"Seconds","time-unit-minutes":"Minutes","time-unit-hours":"Hours","time-unit-days":"Days","time-value-range":"Time value should be in a range from 1 to 2147483647.","start-interval-value-required":"Start interval value is required.","end-interval-value-required":"End interval value is required.",filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","client-attributes-hint":"Client attributes, use ${metaKeyName} to substitute variables from metadata","shared-attributes":"Shared attributes","shared-attributes-hint":"Shared attributes, use ${metaKeyName} to substitute variables from metadata","server-attributes":"Server attributes","server-attributes-hint":"Server attributes, use ${metaKeyName} to substitute variables from metadata","notify-device":"Notify Device","notify-device-hint":"If the message arrives from the device, we will push it back to the device by default.","latest-timeseries":"Latest timeseries","latest-timeseries-hint":"Latest timeseries, use ${metaKeyName} to substitute variables from metadata","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use ${metaKeyName} to substitute variables from metadata","relation-type-pattern-required":"Relation type pattern is required","relation-types-list":"Relation types to propagate","relation-types-list-hint":"If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","source-field":"Source field","source-field-required":"Source field is required.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","originator-alarm-originator":"Alarm Originator","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","use-metadata-period-in-seconds-patterns":"Use metadata period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds metadata pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","period-in-seconds-pattern-hint":"Period in seconds pattern, use ${metaKeyName} to substitute variables from metadata","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required","alarm-status-filter":"Alarm status filter","alarm-status-list-empty":"Alarm status list is empty","no-alarm-status-matching":"No alarm status matching were found.",propagate:"Propagate",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From Template","from-template-required":"From Template is required","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","request-id-metadata-attribute":"Request Id Metadata attribute name","timeout-sec":"Timeout in seconds","timeout-required":"Timeout is required","min-timeout-message":"Only 0 minimum timeout value is allowed.","endpoint-url-pattern":"Endpoint URL pattern","endpoint-url-pattern-required":"Endpoint URL pattern is required","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory","read-timeout":"Read timeout in millis","read-timeout-hint":"The value of 0 means an infinite timeout","max-parallel-requests-count":"Max number of parallel requests","max-parallel-requests-count-hint":"The value of 0 specifies no limit in parallel processing",headers:"Headers","headers-hint":"Use ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata",topic:"Topic","topic-required":"Topic is required","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","gcp-project-id":"GCP project ID","gcp-project-id-required":"GCP project ID is required","gcp-service-account-key":"GCP service account key file","gcp-service-account-key-required":"GCP service account key file is required","pubsub-topic-name":"Topic name","pubsub-topic-name-required":"Topic name is required","message-attributes":"Message attributes","message-attributes-hint":"Use ${metaKeyName} in name/value fields to substitute variables from metadata","connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","device-id":"Device ID","device-id-required":"Device ID is required.","clean-session":"Clean session","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","credentials-pem-hint":"At least Server CA certificate file or a pair of Client certificate and Client private key files are required","credentials-sas":"Shared Access Signature","sas-key":"SAS Key","sas-key-required":"SAS Key is required.",hostname:"Hostname","hostname-required":"Hostname is required.","azure-ca-cert":"CA certificate file","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"Server CA certificate file *","private-key":"Client private key file *",cert:"Client certificate file *","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password","use-system-smtp-settings":"Use system SMTP settings","use-metadata-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys are present","check-all-keys-hint":"If selected, checks that all specified keys are present in the message data and metadata.","check-relation-to-specific-entity":"Check relation to specific entity","check-relation-hint":"Checks existence of relation to specific entity or to any entity based on direction and relation type.","delete-relation-to-specific-entity":"Delete relation to specific entity","delete-relation-hint":"Deletes relation from the originator of the incoming message to the specified entity or list of entities based on direction and type.","remove-current-relations":"Remove current relations","remove-current-relations-hint":"Removes current relations from the originator of the incoming message based on direction and type.","change-originator-to-related-entity":"Change originator to related entity","change-originator-to-related-entity-hint":"Used to process submitted message as a message from another entity.","start-interval-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use ${metaKeyName} to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use ${metaKeyName} to substitute variables from metadata","smtp-protocol":"Protocol","smtp-host":"SMTP host","smtp-host-required":"SMTP host is required.","smtp-port":"SMTP port","smtp-port-required":"You must supply a smtp port.","smtp-port-range":"SMTP port should be in a range from 1 to 65535.","timeout-msec":"Timeout ms","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS","tls-version":"TLS version","enable-proxy":"Enable proxy","use-system-proxy-properties":"Use system proxy properties","proxy-host":"Proxy host","proxy-host-required":"Proxy host is required.","proxy-port":"Proxy port","proxy-port-required":"Proxy port is required.","proxy-port-range":"Proxy port should be in a range from 1 to 65535.","proxy-user":"Proxy user","proxy-password":"Proxy password","proxy-scheme":"Proxy scheme","numbers-to-template":"Phone Numbers To Template","numbers-to-template-required":"Phone Numbers To Template is required","numbers-to-template-hint":"Comma separated Phone Numbers, use ${metaKeyName} to substitute variables from metadata","sms-message-template":"SMS message Template","sms-message-template-required":"SMS message Template is required","sms-message-template-hint":"SMS message template, use ${metaKeyName} to substitute variables from metadata","use-system-sms-settings":"Use system SMS provider settings","min-period-0-seconds-message":"Only 0 second minimum period is allowed.","max-pending-messages":"Maximum pending messages","max-pending-messages-required":"Maximum pending messages is required.","max-pending-messages-range":"Maximum pending messages should be in a range from 1 to 100000.","originator-types-filter":"Originator types filter","interval-seconds":"Interval in seconds","interval-seconds-required":"Interval is required.","min-interval-seconds-message":"Only 1 second minimum interval is allowed.","output-timeseries-key-prefix":"Output timeseries key prefix","output-timeseries-key-prefix-required":"Output timeseries key prefix required.","separator-hint":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","entity-details-zip":"Zip","entity-details-address":"Address","entity-details-address2":"Address2","entity-details-additional_info":"Additional Info","entity-details-phone":"Phone","entity-details-email":"Email","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","entity-details-list-empty":"No entity details selected.","no-entity-details-matching":"No entity details matching were found.","custom-table-name":"Custom table name","custom-table-name-required":"Table Name is required","custom-table-hint":"You should enter the table name without prefix 'cs_tb_'.","message-field":"Message field","message-field-required":"Message field is required.","table-col":"Table column","table-col-required":"Table column is required.","latitude-key-name":"Latitude key name","longitude-key-name":"Longitude key name","latitude-key-name-required":"Latitude key name is required.","longitude-key-name-required":"Longitude key name is required.","fetch-perimeter-info-from-message-metadata":"Fetch perimeter information from message metadata","perimeter-circle":"Circle","perimeter-polygon":"Polygon","perimeter-type":"Perimeter type","circle-center-latitude":"Center latitude","circle-center-latitude-required":"Center latitude is required.","circle-center-longitude":"Center longitude","circle-center-longitude-required":"Center longitude is required.","range-unit-meter":"Meter","range-unit-kilometer":"Kilometer","range-unit-foot":"Foot","range-unit-mile":"Mile","range-unit-nautical-mile":"Nautical mile","range-units":"Range units",range:"Range","range-required":"Range is required.","polygon-definition":"Polygon definition","polygon-definition-required":"Polygon definition is required.","polygon-definition-hint":"Please, use the following format for manual definition of polygon: [[lat1,lon1],[lat2,lon2], ... ,[latN,lonN]].","min-inside-duration":"Minimal inside duration","min-inside-duration-value-required":"Minimal inside duration is required","min-inside-duration-time-unit":"Minimal inside duration time unit","min-outside-duration":"Minimal outside duration","min-outside-duration-value-required":"Minimal outside duration is required","min-outside-duration-time-unit":"Minimal outside duration time unit","tell-failure-if-absent":"Tell Failure","tell-failure-if-absent-hint":'If at least one selected key doesn\'t exist the outbound message will report "Failure".',"get-latest-value-with-ts":"Fetch Latest telemetry with Timestamp","get-latest-value-with-ts-hint":'If selected, latest telemetry values will be added to the outbound message metadata with timestamp, e.g: "temp": "{\\"ts\\":1574329385897,\\"value\\":42}"',"use-redis-queue":"Use redis queue for message persistence","trim-redis-queue":"Trim redis queue","redis-queue-max-size":"Redis queue max size","add-metadata-key-values-as-kafka-headers":"Add Message metadata key-value pairs to Kafka record headers","add-metadata-key-values-as-kafka-headers-hint":"If selected, key-value pairs from message metadata will be added to the outgoing records headers as byte arrays with predefined charset encoding.","charset-encoding":"Charset encoding","charset-encoding-required":"Charset encoding is required.","charset-us-ascii":"US-ASCII","charset-iso-8859-1":"ISO-8859-1","charset-utf-8":"UTF-8","charset-utf-16be":"UTF-16BE","charset-utf-16le":"UTF-16LE","charset-utf-16":"UTF-16","select-queue-hint":"The queue name can be selected from a drop-down list or add a custom name.","persist-alarm-rules":"Persist state of alarm rules","fetch-alarm-rules":"Fetch state of alarm rules"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}},!0)}(e)}return e.ctorParameters=function(){return[{type:n.TranslateService}]},e=b([t.NgModule({declarations:[F],imports:[r.CommonModule,a.SharedModule],exports:[xe,Ae,Be,He,F]}),h("design:paramtypes",[n.TranslateService])],e)}();e.RuleNodeCoreConfigModule=ze,e.ɵa=F,e.ɵb=xe,e.ɵba=he,e.ɵbb=Ce,e.ɵbc=ve,e.ɵbd=Fe,e.ɵbe=se,e.ɵbf=ne,e.ɵbg=ae,e.ɵbh=oe,e.ɵbi=ie,e.ɵbj=le,e.ɵbk=Ae,e.ɵbl=Te,e.ɵbm=qe,e.ɵbn=Se,e.ɵbo=Ie,e.ɵbp=ke,e.ɵbq=Ne,e.ɵbr=Ve,e.ɵbs=Ee,e.ɵbt=Be,e.ɵbu=Le,e.ɵbv=Me,e.ɵbw=Pe,e.ɵbx=Re,e.ɵby=we,e.ɵbz=Oe,e.ɵc=x,e.ɵca=De,e.ɵcb=Ke,e.ɵcc=He,e.ɵcd=Ge,e.ɵce=Ue,e.ɵcf=je,e.ɵd=T,e.ɵe=q,e.ɵf=S,e.ɵg=I,e.ɵh=k,e.ɵi=N,e.ɵj=V,e.ɵk=E,e.ɵl=A,e.ɵm=L,e.ɵn=X,e.ɵo=ee,e.ɵp=te,e.ɵq=re,e.ɵr=me,e.ɵs=ue,e.ɵt=de,e.ɵu=pe,e.ɵv=ce,e.ɵw=fe,e.ɵx=ge,e.ɵy=ye,e.ɵz=be,Object.defineProperty(e,"__esModule",{value:!0})})); //# sourceMappingURL=rulenode-core-config.umd.min.js.map \ No newline at end of file diff --git a/transport/lwm2m/pom.xml b/transport/lwm2m/pom.xml index 8f27b32823..e75c5212d7 100644 --- a/transport/lwm2m/pom.xml +++ b/transport/lwm2m/pom.xml @@ -45,11 +45,38 @@ + + org.thingsboard.common.transport + transport-api + + + org.springframework + spring-context-support + + + org.springframework + spring-context + + + org.slf4j + slf4j-api + + + org.slf4j + log4j-over-slf4j + + + ch.qos.logback + logback-core + + + ch.qos.logback + logback-classic + org.eclipse.leshan leshan-server-cf - org.eclipse.leshan leshan-client-cf @@ -59,7 +86,6 @@ org.eclipse.leshan leshan-server-redis - org.springframework.boot spring-boot-starter-test @@ -78,37 +104,23 @@ org.eclipse.californium californium-core - ${californium.version} test-jar test + + org.eclipse.californium + californium-core + + + + + org.eclipse.californium element-connector - ${californium.version} test-jar test - - - org.thingsboard.common.transport - lwm2m - - - org.thingsboard.common - queue - - - org.springframework.boot - spring-boot-starter-web - - - com.sun.winsw - winsw - bin - exe - provided - diff --git a/transport/lwm2m/src/main/data/credentials/serverKeyStore.jks b/transport/lwm2m/src/main/data/credentials/serverKeyStore.jks index b5b6c9fa3c..9336dd1168 100644 Binary files a/transport/lwm2m/src/main/data/credentials/serverKeyStore.jks and b/transport/lwm2m/src/main/data/credentials/serverKeyStore.jks differ diff --git a/transport/lwm2m/src/main/data/credentials/shell/lwM2M_credentials.sh b/transport/lwm2m/src/main/data/credentials/shell/lwM2M_credentials.sh new file mode 100755 index 0000000000..3167cca2b5 --- /dev/null +++ b/transport/lwm2m/src/main/data/credentials/shell/lwM2M_credentials.sh @@ -0,0 +1,403 @@ +#!/bin/bash +# +# Copyright © 2016-2021 The Thingsboard Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +#p) CLIENT_CN=LwX50900000000 +#s) client_start=0 +#f) client_finish=1 +#a) CLIENT_ALIAS=client_alias_00000000 +#b) BOOTSTRAP_ALIAS=bootstrap +#d) SERVER_ALIAS=server +#j) SERVER_STORE=serverKeyStore.jks +#k) CLIENT_STORE=clientKeyStore.jks +#c) CLIENT_STORE_PWD=client_ks_password +#w) SERVER_STORE_PWD=server_ks_password + +#while test $# -gt 0; do +# case "$1" in +# -h|--help) +# echo "$package - attempt to capture frames" +# echo " " +# echo "$package [options] application [arguments]" +# echo " " +# echo "options:" +# echo "-h, --help show brief help" +# echo "-a, --action=ACTION specify an action to use" +# echo "-o, --output-dir=DIR specify a directory to store output in" +# exit 0 +# ;; +# -a) +# shift +# if test $# -gt 0; then +# export PROCESS=$1 +# else +# echo "no process specified" +# exit 1 +# fi +# shift +# ;; +# --action*) +# export PROCESS=`echo $1 | sed -e 's/^[^=]*=//g'` +# shift +# ;; +# -o) +# shift +# if test $# -gt 0; then +# export OUTPUT=$1 +# else +# echo "no output dir specified" +# exit 1 +# fi +# shift +# ;; +# --output-dir*) +# export OUTPUT=`echo $1 | sed -e 's/^[^=]*=//g'` +# shift +# ;; +# *) +# break +# ;; +# esac +#done + + +while getopts p:s:f:a:b:d:j:k:c:w: flag; do + case "${flag}" in + p) client_prefix=${OPTARG} ;; + s) client_start=${OPTARG} ;; + f) client_finish=${OPTARG} ;; + a) client_alias=${OPTARG} ;; + b) bootstrap_alias=${OPTARG} ;; + d) server_alias=${OPTARG} ;; + j) key_store_server_file=${OPTARG} ;; + k) key_store_client_file=${OPTARG} ;; + c) client_key_store_pwd=${OPTARG} ;; + w) server_key_store_pwd=${OPTARG} ;; + esac +done + +# cd to dir of script +script_dir=$(dirname $0) +echo "script_dir: $script_dir" +cd $script_dir +# source the properties: +. ./lwM2M_keygen.properties + + +if [ -n "$client_prefix" ]; then + CLIENT_PREFIX=$client_prefix +fi + +if [ -z "$client_start" ]; then + client_start=0 +fi + +if [ -z "$client_finish" ]; then + client_finish=1 +fi + +if [ -n "$client_alias" ]; then + CLIENT_ALIAS=$client_alias +fi + +if [ -n "$bootstrap_alias" ]; then + BOOTSTRAP_ALIAS=$bootstrap_alias +fi + +if [ -n "$server_alias" ]; then + SERVER_ALIAS=$server_alias +fi + +if [ -n "$key_store_server_file" ]; then + SERVER_STORE=$key_store_server_file +fi + +if [ -n "$key_store_client_file" ]; then + CLIENT_STORE=$key_store_client_file +fi + +if [ -n "$client_key_store_pwd" ]; then + CLIENT_STORE_PWD=$client_key_store_pwd +fi + +if [ -n "$server_key_store_pwd" ]; then + SERVER_STORE_PWD=$server_key_store_pwd +fi + +echo "==Start==" +echo "CLIENT_PREFIX: $CLIENT_PREFIX" +echo "client_start: $client_start" +echo "client_finish: $client_finish" +echo "CLIENT_ALIAS: $CLIENT_ALIAS" +echo "BOOTSTRAP_ALIAS: $BOOTSTRAP_ALIAS" +echo "SERVER_ALIAS: $SERVER_ALIAS" +echo "SERVER_STORE: $SERVER_STORE" +echo "CLIENT_STORE: $CLIENT_STORE" +echo "CLIENT_STORE_PWD: $CLIENT_STORE_PWD" +echo "SERVER_STORE_PWD: $SERVER_STORE_PWD" + +end_point() { + echo "$CLIENT_PREFIX$(printf "%08d" $CLIENT_NUMBER)" +} +client_alias_point() { + echo "$CLIENT_ALIAS$(printf "%08d" $CLIENT_NUMBER)" +} + +# Generation of the keystore. +echo "${H0}====START========${RESET}" +echo "${H1}Server Keystore : ${RESET}" +echo "${H1}==================${RESET}" +echo "${H2}Creating the trusted root CA key and certificate...${RESET}" +# -keysize +# 1024 (when using -genkeypair) +keytool \ + -genkeypair \ + -alias $ROOT_KEY_ALIAS \ + -keyalg EC \ + -dname "CN=$ROOT_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \ + -validity $VALIDITY \ + -storetype $STORETYPE \ + -keypass $SERVER_STORE_PWD \ + -keystore $SERVER_STORE \ + -storepass $SERVER_STORE_PWD + +echo +echo "${H2}Creating server key and self-signed certificate ...${RESET}" +keytool \ + -genkeypair \ + -alias $SERVER_ALIAS \ + -keyalg EC \ + -dname "CN=$SERVER_SELF_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \ + -validity $VALIDITY \ + -storetype $STORETYPE \ + -keypass $SERVER_STORE_PWD \ + -keystore $SERVER_STORE \ + -storepass $SERVER_STORE_PWD +keytool \ + -exportcert \ + -alias $SERVER_ALIAS \ + -keystore $SERVER_STORE \ + -storepass $SERVER_STORE_PWD | + keytool \ + -importcert \ + -alias $SERVER_SELF_ALIAS \ + -keystore $SERVER_STORE \ + -storepass $SERVER_STORE_PWD \ + -noprompt + +echo +echo "${H2}Creating server certificate signed by root CA...${RESET}" +keytool \ + -certreq \ + -alias $SERVER_ALIAS \ + -dname "CN=$SERVER_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \ + -keystore $SERVER_STORE \ + -storepass $SERVER_STORE_PWD | + keytool \ + -gencert \ + -alias $ROOT_KEY_ALIAS \ + -keystore $SERVER_STORE \ + -storepass $SERVER_STORE_PWD \ + -storetype $STORETYPE \ + -validity $VALIDITY | + keytool \ + -importcert \ + -alias $SERVER_ALIAS \ + -keystore $SERVER_STORE \ + -storepass $SERVER_STORE_PWD + +echo +echo "${H2}Creating bootstrap key and self-signed certificate ...${RESET}" +keytool \ + -genkeypair \ + -alias $BOOTSTRAP_ALIAS \ + -keyalg EC \ + -dname "CN=$BOOTSTRAP_SELF_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \ + -validity $VALIDITY \ + -storetype $STORETYPE \ + -keypass $SERVER_STORE_PWD \ + -keystore $SERVER_STORE \ + -storepass $SERVER_STORE_PWD +keytool \ + -exportcert \ + -alias $BOOTSTRAP_ALIAS \ + -keystore $SERVER_STORE \ + -storepass $SERVER_STORE_PWD | + keytool \ + -importcert \ + -alias $BOOTSTRAP_SELF_ALIAS \ + -keystore $SERVER_STORE \ + -storepass $SERVER_STORE_PWD \ + -noprompt + +echo +echo "${H2}Creating bootstrap certificate signed by root CA...${RESET}" +keytool \ + -certreq \ + -alias $BOOTSTRAP_ALIAS \ + -dname "CN=$BOOTSTRAP_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \ + -keystore $SERVER_STORE \ + -storepass $SERVER_STORE_PWD | + keytool \ + -gencert \ + -alias $ROOT_KEY_ALIAS \ + -keystore $SERVER_STORE \ + -storepass $SERVER_STORE_PWD \ + -storetype $STORETYPE \ + -validity $VALIDITY | + keytool \ + -importcert \ + -alias $BOOTSTRAP_ALIAS \ + -keystore $SERVER_STORE \ + -storepass $SERVER_STORE_PWD + +echo +echo "${H1}Client Keystore : ${RESET}" +echo "${H1}==================${RESET}" +#echo "${H2}Creating client key and self-signed certificate with expected CN...${RESET}" +#keytool \ +# -genkeypair \ +# -alias $CLIENT_ALIAS \ +# -keyalg EC \ +# -dname "CN=$CLIENT_SELF_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \ +# -validity $VALIDITY \ +# -storetype $STORETYPE \ +# -keypass $CLIENT_STORE_PWD \ +# -keystore $CLIENT_STORE \ +# -storepass $CLIENT_STORE_PWD +#keytool \ +# -exportcert \ +# -alias $CLIENT_ALIAS \ +# -keystore $CLIENT_STORE \ +# -storepass $CLIENT_STORE_PWD | \ +# keytool \ +# -importcert \ +# -alias $CLIENT_SELF_ALIAS \ +# -keystore $CLIENT_STORE \ +# -storepass $CLIENT_STORE_PWD \ +# -noprompt + +echo +echo "${H2}Import root certificate just to be able to import need by root CA with expected CN...${RESET}" +keytool \ + -exportcert \ + -alias $ROOT_KEY_ALIAS \ + -keystore $SERVER_STORE \ + -storepass $SERVER_STORE_PWD | + keytool \ + -importcert \ + -alias $ROOT_KEY_ALIAS \ + -keystore $CLIENT_STORE \ + -storepass $CLIENT_STORE_PWD \ + -noprompt + +#echo +#echo "${H2}Creating client certificate signed by root CA with expected CN...${RESET}" +#keytool \ +# -certreq \ +# -alias $CLIENT_ALIAS \ +# -dname "CN=$CLIENT_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \ +# -keystore $CLIENT_STORE \ +# -storepass $CLIENT_STORE_PWD | \ +# keytool \ +# -gencert \ +# -alias $ROOT_KEY_ALIAS \ +# -keystore $SERVER_STORE \ +# -storepass $SERVER_STORE_PWD \ +# -storetype $STORETYPE \ +# -validity $VALIDITY | \ +# keytool \ +# -importcert \ +# -alias $CLIENT_ALIAS \ +# -keystore $CLIENT_STORE \ +# -storepass $CLIENT_STORE_PWD \ +# -noprompt + +cert_end_point() { + echo "${H2}Creating client key and self-signed certificate with expected CN $CLIENT_SELF_CN ${RESET}" + keytool \ + -genkeypair \ + -alias $CLIENT_CN_ALIAS \ + -keyalg EC \ + -dname "CN=$CLIENT_SELF_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \ + -validity $VALIDITY \ + -storetype $STORETYPE \ + -keypass $CLIENT_STORE_PWD \ + -keystore $CLIENT_STORE \ + -storepass $CLIENT_STORE_PWD + keytool \ + -exportcert \ + -alias $CLIENT_CN_ALIAS \ + -keystore $CLIENT_STORE \ + -storepass $CLIENT_STORE_PWD | + keytool \ + -importcert \ + -alias $CLIENT_SELF_ALIAS \ + -keystore $CLIENT_STORE \ + -storepass $CLIENT_STORE_PWD \ + -noprompt + + echo + echo "${H2}Creating client certificate signed by root CA with expected $CLIENT_CN_NAME ${RESET}" + keytool \ + -certreq \ + -alias $CLIENT_CN_ALIAS \ + -dname "CN=$CLIENT_CN_NAME, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \ + -keystore $CLIENT_STORE \ + -storepass $CLIENT_STORE_PWD | + keytool \ + -gencert \ + -alias $ROOT_KEY_ALIAS \ + -keystore $SERVER_STORE \ + -storepass $SERVER_STORE_PWD \ + -storetype $STORETYPE \ + -validity $VALIDITY | + keytool \ + -importcert \ + -alias $CLIENT_CN_ALIAS \ + -keystore $CLIENT_STORE \ + -storepass $CLIENT_STORE_PWD \ + -noprompt +} + +while [ "$CLIENT_NUMBER" != "$client_finish" ]; do + CLIENT_CN_NAME=$(end_point) + CLIENT_CN_ALIAS=$(client_alias_point) + echo "$CLIENT_CN_NAME" + echo "$CLIENT_CN_ALIAS" + cert_end_point + CLIENT_NUMBER=$(($CLIENT_NUMBER + 1)) + echo "number $CLIENT_NUMBER" + echo "finish $client_finish" +done + +echo +echo "${H0}!!! Warning ${H2}Migrate ${H1}${SERVER_STORE} ${H2}to ${H1}PKCS12 ${H2}which is an industry standard format..${RESET}" +keytool \ + -importkeystore \ + -srckeystore $SERVER_STORE \ + -destkeystore $SERVER_STORE \ + -deststoretype pkcs12 \ + -srcstorepass $SERVER_STORE_PWD + +echo +echo "${H0}!!! Warning ${H2}Migrate ${H1}${CLIENT_STORE} ${H2}to ${H1}PKCS12 ${H2}which is an industry standard format..${RESET}" +keytool \ + -importkeystore \ + -srckeystore $CLIENT_STORE \ + -destkeystore $CLIENT_STORE \ + -deststoretype pkcs12 \ + -srcstorepass $CLIENT_STORE_PWD diff --git a/transport/lwm2m/src/main/data/credentials/shell/lwM2M_keygen.properties b/transport/lwm2m/src/main/data/credentials/shell/lwM2M_keygen.properties new file mode 100644 index 0000000000..18f851e4dd --- /dev/null +++ b/transport/lwm2m/src/main/data/credentials/shell/lwM2M_keygen.properties @@ -0,0 +1,57 @@ +# +# Copyright © 2016-2017 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. +# + +# Keystore common parameters +ROOT_KEY_ALIAS=rootCA +DOMAIN_SUFFIX="$(hostname)" +ROOT_CN="$DOMAIN_SUFFIX $ROOT_KEY_ALIAS" +ORGANIZATIONAL_UNIT=Thingsboard +ORGANIZATION=Thingsboard +CITY=SF +STATE_OR_PROVINCE=CA +TWO_LETTER_COUNTRY_CODE=US +VALIDITY=36500 #days +STORETYPE="JKS" + +#Server +SERVER_STORE=serverKeyStore1.jks +SERVER_STORE_PWD=server_ks_password1 +SERVER_ALIAS=server1 +SERVER_CN="$DOMAIN_SUFFIX server LwM2M signed by root CA" +SERVER_SELF_ALIAS=server_self_signed +SERVER_SELF_CN="$DOMAIN_SUFFIX server LwM2M self-signed" +BOOTSTRAP_ALIAS=bootstrap1 +BOOTSTRAP_CN="$DOMAIN_SUFFIX bootstrap server LwM2M signed by root CA" +BOOTSTRAP_SELF_ALIAS=bootstrap_self_signed +BOOTSTRAP_SELF_CN="$DOMAIN_SUFFIX bootstrap server LwM2M self-signed" + +# Client +CLIENT_STORE=clientKeyStore1.jks +CLIENT_STORE_PWD=client_ks_password1 +CLIENT_ALIAS=client_alias_1 +CLIENT_PREFIX=LwX509_ +CLIENT_SELF_ALIAS=client_self_signed +CLIENT_SELF_CN="$DOMAIN_SUFFIX client LwM2M self-signed" + +# Color output stuff +red=`tput setaf 1` +green=`tput setaf 2` +blue=`tput setaf 4` +bold=`tput bold` +H0=${red}${bold} +H1=${green}${bold} +H2=${blue} +RESET=`tput sgr0` diff --git a/transport/lwm2m/src/main/data/models/LWM2M_Connectivity_Monitoring-v1_0_2.xml b/transport/lwm2m/src/main/data/models/LWM2M_Connectivity_Monitoring-v1_0_2.xml deleted file mode 100644 index a479030a1c..0000000000 --- a/transport/lwm2m/src/main/data/models/LWM2M_Connectivity_Monitoring-v1_0_2.xml +++ /dev/null @@ -1,167 +0,0 @@ - - - - - Connectivity Monitoring - - This LwM2M Object enables monitoring of parameters related to network connectivity. In this general connectivity Object, the Resources are limited to the most general cases common to most network bearers. It is recommended to read the description, which refers to relevant standard development organizations (e.g. 3GPP, IEEE). The goal of the Connectivity Monitoring Object is to carry information reflecting the more up to date values of the current connection for monitoring purposes. Resources such as Link Quality, Radio Signal Strength, Cell ID are retrieved during connected mode at least for cellular networks. - - 4 - urn:oma:lwm2m:oma:4 - 1.0 - 1.0 - Single - Optional - - - Network Bearer - R - Single - Mandatory - Integer - 0-50 - - - Indicates the network bearer used for the current LwM2M communication session from the network bearer list below. The number range is split into three categories: 0 - 20 are Cellular Bearers 21 - 40 are Wireless Bearers 41 - 50 are Wireline Bearers More specifically: 0: GSM cellular network 1: TD-SCDMA cellular network 2: WCDMA cellular network 3: CDMA2000 cellular network 4: WiMAX cellular network 5: LTE-TDD cellular network 6: LTE-FDD cellular network 7: NB-IoT 8 - 20: Reserved for other types of cellular network 21: WLAN network 22: Bluetooth network 23: IEEE 802.15.4 network 24 - 40: Reserved for other types of local wireless network 41: Ethernet 42: DSL 43: PLC 44 - 50: reserved for other types of wireline networks. - - - - Available Network Bearer - R - Multiple - Mandatory - Integer - 0-50 - - - Indicates a list of current available network bearer. Each Resource Instance has a value from the network bearer list. - - - - Radio Signal Strength - R - Single - Mandatory - Integer - - dBm - - Indicates the average value of the received signal strength indication used in the current network bearer (as indicated by Resource 0 of this Object). For the following network bearers the signal strength parameters indicated below are represented by this resource: GSM: RSSI UMTS: RSCP LTE: RSRP NB-IoT: NRSRP For more details on Network Measurement Report, refer to the appropriate Cellular or Wireless Network standards, (e.g. for LTE Cellular Network refer to 3GPP TS 36.133 specification). - - - - Link Quality - R - Single - Optional - Integer - - - - This contains received link quality e.g. LQI for IEEE 802.15.4 (range 0...255), RxQual Downlink for GSM (range 0...7, refer to [3GPP 44.018] for more details on Network Measurement Report encoding), RSRQ for LTE, (refer to [3GPP 36.214]), NRSRQ for NB-IoT (refer to [3GPP 36.214]). - - - - IP Addresses - R - Multiple - Mandatory - String - - - - The IP addresses assigned to the connectivity interface. (e.g. IPv4, IPv6, etc.) - - - - Router IP Addresses - R - Multiple - Optional - String - - - - The IP address of the next-hop IP router, on each of the interfaces specified in resource 4 (IP Addresses). Note: This IP Address doesn’t indicate the Server IP address. - - - - Link Utilization - R - Single - Optional - Integer - 0-100 - % - - The percentage indicating the average utilization of the link to the next-hop IP router. - - - - APN - R - Multiple - Optional - String - - - - Access Point Name in case Network Bearer Resource is a Cellular Network. - - - - Cell ID - R - Single - Optional - Integer - - - - Serving Cell ID in case Network Bearer Resource is a Cellular Network. As specified in TS [3GPP 23.003] and in [3GPP. 24.008]. Range (0...65535) in GSM/EDGE UTRAN Cell ID has a length of 28 bits. Cell Identity in WCDMA/TD-SCDMA. Range: (0...268435455). LTE Cell ID has a length of 28 bits. Parameter definitions in [3GPP 25.331]. - - - - SMNC - R - Single - Optional - Integer - 0-999 - % - - Serving Mobile Network Code. This is applicable when the Network Bearer Resource value is referring to a cellular network. As specified in TS [3GPP 23.003]. - - - - SMCC - R - Single - Optional - Integer - 0-999 - - - Serving Mobile Country Code. This is applicable when the Network Bearer Resource value is referring to a cellular network. As specified in TS [3GPP 23.003]. - - - - - - diff --git a/transport/lwm2m/src/main/data/models/LwM2M_BinaryAppDataContainer-v1_0_1.xml b/transport/lwm2m/src/main/data/models/LwM2M_BinaryAppDataContainer-v1_0_1.xml deleted file mode 100644 index b0e55397fb..0000000000 --- a/transport/lwm2m/src/main/data/models/LwM2M_BinaryAppDataContainer-v1_0_1.xml +++ /dev/null @@ -1,164 +0,0 @@ - - - - - - BinaryAppDataContainer - - 19 - urn:oma:lwm2m:oma:19 - 1.0 - 1.0 - Multiple - Optional - - Data - RW - - - - Multiple - Mandatory - Opaque - - - - - Data Priority - RW - Single - Optional - Integer - 1 bytes - - - - Data Creation Time - RW - Single - Optional - Time - - - - - Data Description - RW - Single - Optional - String - 32 bytes - - - - Data Format - RW - Single - Optional - String - 32 bytes - - - - App ID - RW - Single - Optional - Integer - 2 bytes - - - - - - diff --git a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml index fa174123bf..700b428906 100644 --- a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml +++ b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml @@ -42,6 +42,7 @@ zk: # LWM2M server parameters transport: + # Local LwM2M transport parameters lwm2m: # Enable/disable lvm2m transport protocol. enabled: "${LWM2M_ENABLED:true}" @@ -51,7 +52,13 @@ transport: timeout: "${LWM2M_TIMEOUT:120000}" # model_path_file: "${LWM2M_MODEL_PATH_FILE:./common/transport/lwm2m/src/main/resources/models/}" model_path_file: "${LWM2M_MODEL_PATH_FILE:}" - support_deprecated_ciphers_enable: "${LWM2M_SUPPORT_DEPRECATED_CIPHERS_ENABLED:true}" + recommended_ciphers: "${LWM2M_RECOMMENDED_CIPHERS:false}" + recommended_supported_groups: "${LWM2M_RECOMMENDED_SUPPORTED_GROUPS:false}" + request_pool_size: "${LWM2M_REQUEST_POOL_SIZE:100}" + request_error_pool_size: "${LWM2M_REQUEST_ERROR_POOL_SIZE:10}" + registered_pool_size: "${LWM2M_REGISTERED_POOL_SIZE:10}" + update_registered_pool_size: "${LWM2M_UPDATE_REGISTERED_POOL_SIZE:10}" + un_registered_pool_size: "${LWM2M_UN_REGISTERED_POOL_SIZE:10}" secure: # Only Certificate_x509: # To get helps about files format and how to generate it, see: https://github.com/eclipse/leshan/wiki/Credential-files-format @@ -64,24 +71,19 @@ transport: root_alias: "${LWM2M_SERVER_ROOT_CA:rootca}" enable_gen_psk_rpk: "${ENABLE_GEN_PSK_RPK:true}" server: + id: "${LWM2M_SERVER_ID:123}" bind_address: "${LWM2M_BIND_ADDRESS:0.0.0.0}" - bind_port: "${LWM2M_BIND_PORT:5685}" - bind_port_cert: "${LWM2M_BIND_PORT_CERT:5687}" + bind_port_no_sec_psk: "${LWM2M_BIND_PORT_NO_SEC_PSK:5685}" + bind_port_no_sec_rpk: "${LWM2M_BIND_PORT_NO_SEC_RPK:5687}" + bind_port_no_sec_x509: "${LWM2M_BIND_PORT_NO_SEC_X509:5689}" secure: - start_all: "${START_SERVER_ALL:true}" - #leshan.core (V1_1) - #DTLS security modes: - #0: Pre-Shared Key mode - #1: Raw Public Key mode - #2: Certificate mode X509 - #3: NoSec mode * - #OMA-TS-LightweightM2M_Core-V1_1_1-20190617-A (add) - #4: Certificate mode X509 with EST - # If only startAll == false - dtls_mode: "${LWM2M_SECURITY_MODE:1}" bind_address: "${LWM2M_BIND_ADDRESS:0.0.0.0}" - bind_port: "${LWM2M_BIND_PORT_SEC:5686}" - bind_port_cert: "${LWM2M_BIND_PORT_SEC_CERT:5688}" + start_psk: "${START_SERVER_PSK:true}" + start_rpk: "${START_SERVER_RPK:true}" + start_x509: "${START_SERVER_X509:true}" + bind_port_psk: "${LWM2M_BIND_PORT_SEC_PSK:5686}" + bind_port_rpk: "${LWM2M_BIND_PORT_SEC_RPK:5688}" + bind_port_x509: "${LWM2M_BIND_PORT_SEC_X509:5690}" # Only RPK: Public & Private Key # create_rpk: "${CREATE_RPK:}" public_x: "${LWM2M_SERVER_PUBLIC_X:405354ea8893471d9296afbc8b020a5c6201b0bb25812a53b849d4480fa5f069}" @@ -91,16 +93,19 @@ transport: alias: "${LWM2M_KEYSTORE_ALIAS_SERVER:server}" bootstrap: enable: "${BOOTSTRAP:true}" + id: "${LWM2M_SERVER_ID:111}" bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}" - bind_port: "${LWM2M_BIND_PORT_BS:5689}" - bind_port_cert: "${LWM2M_BIND_PORT_SER_BS:5691}" + bind_port_no_sec_psk: "${LWM2M_BIND_PORT_NO_SEC_BS:5691}" + bind_port_no_sec_rpk: "${LWM2M_BIND_PORT_NO_SEC_BS:5693}" + bind_port_no_sec_x509: "${LWM2M_BIND_PORT_NO_SEC_BS:5695}" secure: - start_all: "${START_BOOTSTRAP_ALL:true}" - # If only startAll == false - dtls_mode: "${LWM2M_SECURITY_MODE_BS:1}" bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}" - bind_port: "${LWM2M_BIND_PORT_SEC_BS:5690}" - bind_port_cert: "${LWM2M_BIND_PORT_SEC_CERT_BS:5692}" + start_psk: "${START_SERVER_PSK_BS:true}" + start_rpk: "${START_SERVER_RPK_BS:true}" + start_x509: "${START_SERVER_X509_BS:true}" + bind_port_psk: "${LWM2M_BIND_PORT_SEC_PSK_BS:5692}" + bind_port_rpk: "${LWM2M_BIND_PORT_SER_RPK_BS:5694}" + bind_port_x509: "${LWM2M_BIND_PORT_SEC_X509_BS:5696}" # Only RPK: Public & Private Key public_x: "${LWM2M_SERVER_PUBLIC_X_BS:993ef2b698c6a9c0c1d8be78b13a9383c0854c7c7c7a504d289b403794648183}" public_y: "${LWM2M_SERVER_PUBLIC_Y_BS:267412d5fc4e5ceb2257cb7fd7f76ebdac2fa9aa100afb162e990074cc0bfaa2}" diff --git a/ui-ngx/angular.json b/ui-ngx/angular.json index 3cb140c837..590cd73399 100644 --- a/ui-ngx/angular.json +++ b/ui-ngx/angular.json @@ -228,6 +228,7 @@ }, "defaultProject": "thingsboard", "cli": { - "packageManager": "yarn" + "packageManager": "yarn", + "analytics": false } -} +} \ No newline at end of file diff --git a/ui-ngx/src/app/core/api/widget-api.models.ts b/ui-ngx/src/app/core/api/widget-api.models.ts index b26abdb320..1579ae8d1e 100644 --- a/ui-ngx/src/app/core/api/widget-api.models.ts +++ b/ui-ngx/src/app/core/api/widget-api.models.ts @@ -53,6 +53,7 @@ import { EntityDataService } from '@core/api/entity-data.service'; import { PageData } from '@shared/models/page/page-data'; import { TranslateService } from '@ngx-translate/core'; import { AlarmDataService } from '@core/api/alarm-data.service'; +import { IDashboardController } from '@home/components/dashboard-page/dashboard-page.models'; export interface TimewindowFunctions { onUpdateTimewindow: (startTimeMs: number, endTimeMs: number, interval?: number) => void; @@ -137,6 +138,7 @@ export interface StateParams { export type StateControllerHolder = () => IStateController; export interface IStateController { + dashboardCtrl: IDashboardController; getStateParams(): StateParams; getStateParamsByStateId(stateId: string): StateParams; openState(id: string, params?: StateParams, openRightLayout?: boolean): void; diff --git a/ui-ngx/src/app/core/http/device-profile.service.ts b/ui-ngx/src/app/core/http/device-profile.service.ts index f48ac77456..e0a0bf5174 100644 --- a/ui-ngx/src/app/core/http/device-profile.service.ts +++ b/ui-ngx/src/app/core/http/device-profile.service.ts @@ -21,10 +21,9 @@ import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; import { Observable } from 'rxjs'; import { PageData } from '@shared/models/page/page-data'; import { DeviceProfile, DeviceProfileInfo, DeviceTransportType } from '@shared/models/device.models'; -import { isDefinedAndNotNull } from '@core/utils'; -import { - ObjectLwM2M, ServerSecurityConfig -} from '../../modules/home/components/profile/device/lwm2m/profile-config.models'; +import { isDefinedAndNotNull, isEmptyStr } from '@core/utils'; +import { ObjectLwM2M, ServerSecurityConfig } from '@home/components/profile/device/lwm2m/profile-config.models'; +import { SortOrder } from '@shared/models/page/sort-order'; @Injectable({ providedIn: 'root' @@ -33,7 +32,8 @@ export class DeviceProfileService { constructor( private http: HttpClient - ) { } + ) { + } public getDeviceProfiles(pageLink: PageLink, config?: RequestConfig): Observable> { return this.http.get>(`/api/deviceProfiles${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config)); @@ -43,16 +43,31 @@ export class DeviceProfileService { return this.http.get(`/api/deviceProfile/${deviceProfileId}`, defaultHttpOptionsFromConfig(config)); } - public getLwm2mObjects(objectIds: number [], config?: RequestConfig): Observable { - return this.http.get(`/api/lwm2m/deviceProfile/${objectIds}`, defaultHttpOptionsFromConfig(config)); + public getLwm2mObjects(sortOrder: SortOrder, objectIds?: number[], searchText?: string, config?: RequestConfig): + Observable> { + let url = `/api/lwm2m/deviceProfile/?sortProperty=${sortOrder.property}&sortOrder=${sortOrder.direction}`; + if (isDefinedAndNotNull(objectIds) && objectIds.length > 0) { + url += `&objectIds=${objectIds}`; + } + if (isDefinedAndNotNull(searchText) && !isEmptyStr(searchText)) { + url += `&searchText=${searchText}`; + } + return this.http.get>(url, defaultHttpOptionsFromConfig(config)); } - public getLwm2mBootstrapSecurityInfo(securityMode: string, bootstrapServerIs: boolean, config?: RequestConfig): Observable { - return this.http.get(`/api/lwm2m/deviceProfile/bootstrap/${securityMode}/${bootstrapServerIs}`, defaultHttpOptionsFromConfig(config)); + public getLwm2mBootstrapSecurityInfo(securityMode: string, bootstrapServerIs: boolean, + config?: RequestConfig): Observable { + return this.http.get( + `/api/lwm2m/deviceProfile/bootstrap/${securityMode}/${bootstrapServerIs}`, + defaultHttpOptionsFromConfig(config) + ); } public getLwm2mObjectsPage(pageLink: PageLink, config?: RequestConfig): Observable> { - return this.http.get>(`/api/lwm2m/deviceProfile/objects${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config)); + return this.http.get>( + `/api/lwm2m/deviceProfile/objects${pageLink.toQuery()}`, + defaultHttpOptionsFromConfig(config) + ); } public saveDeviceProfile(deviceProfile: DeviceProfile, config?: RequestConfig): Observable { diff --git a/ui-ngx/src/app/modules/dashboard/dashboard-pages.module.ts b/ui-ngx/src/app/modules/dashboard/dashboard-pages.module.ts index f09c80fea4..b7e11982f9 100644 --- a/ui-ngx/src/app/modules/dashboard/dashboard-pages.module.ts +++ b/ui-ngx/src/app/modules/dashboard/dashboard-pages.module.ts @@ -19,7 +19,6 @@ import { CommonModule } from '@angular/common'; import { SharedModule } from '@shared/shared.module'; import { HomeComponentsModule } from '@modules/home/components/home-components.module'; import { HomeDialogsModule } from '@app/modules/home/dialogs/home-dialogs.module'; -import { DashboardModule } from '@home/pages/dashboard/dashboard.module'; import { DashboardPagesRoutingModule } from './dashboard-pages.routing.module'; @NgModule({ @@ -28,7 +27,6 @@ import { DashboardPagesRoutingModule } from './dashboard-pages.routing.module'; SharedModule, HomeComponentsModule, HomeDialogsModule, - DashboardModule, DashboardPagesRoutingModule ] }) diff --git a/ui-ngx/src/app/modules/dashboard/dashboard-pages.routing.module.ts b/ui-ngx/src/app/modules/dashboard/dashboard-pages.routing.module.ts index 1354f9ef2b..5c3bcb8d11 100644 --- a/ui-ngx/src/app/modules/dashboard/dashboard-pages.routing.module.ts +++ b/ui-ngx/src/app/modules/dashboard/dashboard-pages.routing.module.ts @@ -18,7 +18,7 @@ import { Injectable, NgModule } from '@angular/core'; import { ActivatedRouteSnapshot, Resolve, RouterModule, Routes } from '@angular/router'; import { Authority } from '@shared/models/authority.enum'; -import { DashboardPageComponent } from '@home/pages/dashboard/dashboard-page.component'; +import { DashboardPageComponent } from '@home/components/dashboard-page/dashboard-page.component'; import { Dashboard } from '@app/shared/models/dashboard.models'; import { DashboardService } from '@core/http/dashboard.service'; import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; @@ -95,6 +95,7 @@ const routes: Routes = [ exports: [RouterModule], providers: [ WidgetEditorDashboardResolver, + DashboardResolver, { provide: MODULES_MAP, useValue: modulesMap diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/add-widget-dialog.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.html similarity index 100% rename from ui-ngx/src/app/modules/home/pages/dashboard/add-widget-dialog.component.html rename to ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.html diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/add-widget-dialog.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.ts similarity index 100% rename from ui-ngx/src/app/modules/home/pages/dashboard/add-widget-dialog.component.ts rename to ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.ts diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html similarity index 97% rename from ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.html rename to ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html index 5aae43bec6..97f5071955 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html @@ -21,7 +21,7 @@
-
@@ -52,6 +52,7 @@ [dashboardId]="dashboard.id ? dashboard.id.id : ''" [isMobile]="isMobile" [state]="dashboardCtx.state" + [currentState]="currentState" [states]="dashboardConfiguration.states">
@@ -183,11 +184,12 @@ + + + +
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 87ffd997ce..f63af09457 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 @@ -14,58 +14,56 @@ /// limitations under the License. /// -import { - DeviceProfileTransportConfiguration, - DeviceTransportType -} from '@shared/models/device.models'; -import { - Component, - forwardRef, Inject, - Input, - OnInit -} from '@angular/core'; +import { DeviceProfileTransportConfiguration, DeviceTransportType } from '@shared/models/device.models'; +import { Component, forwardRef, Inject, Input } 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 { ATTR, + getDefaultProfileConfig, + Instance, + KEY_NAME, + ObjectLwM2M, OBSERVE, OBSERVE_ATTR, - TELEMETRY, - ObjectLwM2M, getDefaultProfileConfig, KEY_NAME, Instance + ProfileConfigModels, + ResourceLwM2M, + TELEMETRY } from './profile-config.models'; -import { DeviceProfileService } from '../../../../../../core/http/device-profile.service'; -import { deepClone, isUndefined } from '../../../../../../core/utils'; -import { WINDOW } from '../../../../../../core/services/window.service'; +import { DeviceProfileService } from '@core/http/device-profile.service'; +import { deepClone, isDefinedAndNotNull, isUndefined } from '@core/utils'; +import { WINDOW } from '@core/services/window.service'; import { JsonObject } from '@angular/compiler-cli/ngcc/src/packages/entry_point'; -import { isNotNullOrUndefined } from 'codelyzer/util/isNotNullOrUndefined'; +import { Direction } from '@shared/models/page/sort-order'; @Component({ selector: 'tb-profile-lwm2m-device-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, Validators { - - lwm2mDeviceProfileTransportConfFormGroup: FormGroup; - observeAttr = OBSERVE_ATTR as string; - observe = OBSERVE as string; - attribute = ATTR as string; - telemetry = TELEMETRY as string; - keyName = KEY_NAME as string; +export class Lwm2mDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, Validators { + + private configurationValue: ProfileConfigModels; + private requiredValue: boolean; + private disabled = false; + + lwm2mDeviceProfileFormGroup: FormGroup; + lwm2mDeviceConfigFormGroup: FormGroup; + observeAttr = OBSERVE_ATTR; + observe = OBSERVE; + attribute = ATTR; + telemetry = TELEMETRY; + keyName = KEY_NAME; bootstrapServers: string; bootstrapServer: string; lwm2mServer: string; - private configurationValue: {}; - private requiredValue: boolean; - private disabled = false as boolean; - sortFunction = this.sortObjectKeyPathJson; + sortFunction: (key: string, value: object) => object; get required(): boolean { return this.requiredValue; @@ -77,29 +75,33 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro } private propagateChange = (v: any) => { - }; + } constructor(private store: Store, private fb: FormBuilder, private deviceProfileService: DeviceProfileService, @Inject(WINDOW) private window: Window) { - this.lwm2mDeviceProfileTransportConfFormGroup = this.fb.group({ - objectIds: [{}, Validators.required], - observeAttrTelemetry: [{'clientLwM2M': [] as ObjectLwM2M []}, Validators.required], + this.lwm2mDeviceProfileFormGroup = this.fb.group({ + objectIds: [null, Validators.required], + observeAttrTelemetry: [null, Validators.required], shortId: [null, Validators.required], lifetime: [null, Validators.required], defaultMinPeriod: [null, Validators.required], notifIfDisabled: [true, []], - binding: ["U", Validators.required], + binding: ['U', Validators.required], bootstrapServer: [null, Validators.required], lwm2mServer: [null, Validators.required], - configurationJson: [null, Validators.required], }); - this.lwm2mDeviceProfileTransportConfFormGroup.valueChanges.subscribe(value => { - if (!this.disabled) { - this.updateModel(); - } + this.lwm2mDeviceConfigFormGroup = this.fb.group({ + configurationJson: [null, Validators.required] + }); + this.lwm2mDeviceProfileFormGroup.valueChanges.subscribe((value) => { + this.updateDeviceProfileValue(value); + }); + this.lwm2mDeviceConfigFormGroup.valueChanges.subscribe(() => { + this.updateModel(); }); + this.sortFunction = this.sortObjectKeyPathJson; } registerOnChange(fn: any): void { @@ -109,33 +111,34 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro registerOnTouched(fn: any): void { } - ngOnInit() { - } - setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; if (isDisabled) { - this.lwm2mDeviceProfileTransportConfFormGroup.disable({emitEvent: false}); + this.lwm2mDeviceProfileFormGroup.disable({emitEvent: false}); + this.lwm2mDeviceConfigFormGroup.disable({emitEvent: false}); } else { - this.lwm2mDeviceProfileTransportConfFormGroup.enable({emitEvent: false}); + this.lwm2mDeviceProfileFormGroup.enable({emitEvent: false}); + this.lwm2mDeviceConfigFormGroup.enable({emitEvent: false}); } } writeValue(value: any | null): void { - value = (Object.keys(value).length == 0) ? getDefaultProfileConfig() : value; - this.lwm2mDeviceProfileTransportConfFormGroup.patchValue({ - configurationJson: value - }, - {emitEvent: false}); - this.configurationValue = this.lwm2mDeviceProfileTransportConfFormGroup.getRawValue().configurationJson; + this.configurationValue = (Object.keys(value).length === 0) ? getDefaultProfileConfig() : value; + this.lwm2mDeviceConfigFormGroup.patchValue({ + configurationJson: this.configurationValue + }, {emitEvent: false}); this.initWriteValue(); } - private initWriteValue(): void { - let modelValue = {"objectIds": null, "objectsList": []}; + private initWriteValue = (): void => { + const modelValue = {objectIds: null, objectsList: []}; modelValue.objectIds = this.getObjectsFromJsonAllConfig(); if (modelValue.objectIds !== null) { - this.deviceProfileService.getLwm2mObjects(modelValue.objectIds).subscribe( + const sortOrder = { + property: 'id', + direction: Direction.ASC + }; + this.deviceProfileService.getLwm2mObjects(sortOrder, modelValue.objectIds, null).subscribe( (objectsList) => { modelValue.objectsList = objectsList; this.updateWriteValue(modelValue); @@ -146,240 +149,176 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro } } - private updateWriteValue(value: any): void { - let objectsList = deepClone(value.objectsList); - this.lwm2mDeviceProfileTransportConfFormGroup.patchValue({ + private updateWriteValue = (value: any): void => { + const objectsList = value.objectsList; + this.lwm2mDeviceProfileFormGroup.patchValue({ objectIds: value, - observeAttrTelemetry: {clientLwM2M: this.getObserveAttrTelemetryObjects(objectsList)}, - shortId: this.configurationValue['bootstrap'].servers.shortId, - lifetime: this.configurationValue['bootstrap'].servers.lifetime, - defaultMinPeriod: this.configurationValue['bootstrap'].servers.defaultMinPeriod, - notifIfDisabled: this.configurationValue['bootstrap'].servers.notifIfDisabled, - binding: this.configurationValue['bootstrap'].servers.binding, - bootstrapServer: this.configurationValue['bootstrap'].bootstrapServer, - lwm2mServer: this.configurationValue['bootstrap'].lwm2mServer + observeAttrTelemetry: this.getObserveAttrTelemetryObjects(objectsList), + shortId: this.configurationValue.bootstrap.servers.shortId, + lifetime: this.configurationValue.bootstrap.servers.lifetime, + defaultMinPeriod: this.configurationValue.bootstrap.servers.defaultMinPeriod, + notifIfDisabled: this.configurationValue.bootstrap.servers.notifIfDisabled, + binding: this.configurationValue.bootstrap.servers.binding, + bootstrapServer: this.configurationValue.bootstrap.bootstrapServer, + lwm2mServer: this.configurationValue.bootstrap.lwm2mServer }, {emitEvent: false}); } - private updateModel() { + private updateModel = (): void => { let configuration: DeviceProfileTransportConfiguration = null; - if (this.lwm2mDeviceProfileTransportConfFormGroup.valid) { - this.upDateValueToJson(); - configuration = this.lwm2mDeviceProfileTransportConfFormGroup.getRawValue().configurationJson; + if (this.lwm2mDeviceConfigFormGroup.valid && this.lwm2mDeviceProfileFormGroup.valid) { + configuration = this.lwm2mDeviceConfigFormGroup.value.configurationJson; configuration.type = DeviceTransportType.LWM2M; } this.propagateChange(configuration); } - private updateObserveAttrTelemetryObjectFormGroup(objectsList: ObjectLwM2M[]) { - this.lwm2mDeviceProfileTransportConfFormGroup.patchValue({ - observeAttrTelemetry: {clientLwM2M: this.getObserveAttrTelemetryObjects(objectsList)} + private updateObserveAttrTelemetryObjectFormGroup = (objectsList: ObjectLwM2M[]): void => { + this.lwm2mDeviceProfileFormGroup.patchValue({ + observeAttrTelemetry: this.getObserveAttrTelemetryObjects(objectsList) }, {emitEvent: false}); - this.lwm2mDeviceProfileTransportConfFormGroup.get("observeAttrTelemetry").markAsPristine({ - onlySelf: true - }); } - upDateValueToJson(): void { - this.upDateValueToJsonTab_0(); - this.upDateValueToJsonTab_1(); - } - - upDateValueToJsonTab_0(): void { - if (!this.lwm2mDeviceProfileTransportConfFormGroup.get("observeAttrTelemetry").pristine) { - this.upDateObserveAttrTelemetryFromGroupToJson(this.lwm2mDeviceProfileTransportConfFormGroup.get("observeAttrTelemetry").value['clientLwM2M']); - this.lwm2mDeviceProfileTransportConfFormGroup.get("observeAttrTelemetry").markAsPristine({ - onlySelf: true - }); - this.upDateJsonAllConfig(); - } - } - - upDateValueToJsonTab_1(): void { - this.upDateValueServersToJson(); - if (!this.lwm2mDeviceProfileTransportConfFormGroup.get('bootstrapServer').pristine) { - this.configurationValue['bootstrap'].bootstrapServer = this.lwm2mDeviceProfileTransportConfFormGroup.get('bootstrapServer').value; - this.lwm2mDeviceProfileTransportConfFormGroup.get('bootstrapServer').markAsPristine({ - onlySelf: true - }); - this.upDateJsonAllConfig(); - } - if (!this.lwm2mDeviceProfileTransportConfFormGroup.get('lwm2mServer').pristine) { - this.configurationValue['bootstrap'].lwm2mServer = this.lwm2mDeviceProfileTransportConfFormGroup.get('lwm2mServer').value; - this.lwm2mDeviceProfileTransportConfFormGroup.get('lwm2mServer').markAsPristine({ - onlySelf: true - }); - this.upDateJsonAllConfig(); - } - } - - upDateValueServersToJson(): void { - if (!this.lwm2mDeviceProfileTransportConfFormGroup.get('shortId').pristine) { - this.configurationValue['bootstrap'].servers.shortId = this.lwm2mDeviceProfileTransportConfFormGroup.get('shortId').value; - this.lwm2mDeviceProfileTransportConfFormGroup.get('shortId').markAsPristine({ - onlySelf: true - }); - this.upDateJsonAllConfig(); - } - if (!this.lwm2mDeviceProfileTransportConfFormGroup.get('lifetime').pristine) { - this.configurationValue['bootstrap'].servers.lifetime = this.lwm2mDeviceProfileTransportConfFormGroup.get('lifetime').value; - this.lwm2mDeviceProfileTransportConfFormGroup.get('lifetime').markAsPristine({ - onlySelf: true - }); - this.upDateJsonAllConfig(); - } - if (!this.lwm2mDeviceProfileTransportConfFormGroup.get('defaultMinPeriod').pristine) { - this.configurationValue['bootstrap'].servers.defaultMinPeriod = this.lwm2mDeviceProfileTransportConfFormGroup.get('defaultMinPeriod').value; - this.lwm2mDeviceProfileTransportConfFormGroup.get('defaultMinPeriod').markAsPristine({ - onlySelf: true - }); - this.upDateJsonAllConfig(); - } - if (!this.lwm2mDeviceProfileTransportConfFormGroup.get('notifIfDisabled').pristine) { - this.configurationValue['bootstrap'].servers.notifIfDisabled = this.lwm2mDeviceProfileTransportConfFormGroup.get('notifIfDisabled').value; - this.lwm2mDeviceProfileTransportConfFormGroup.get('notifIfDisabled').markAsPristine({ - onlySelf: true - }); - this.upDateJsonAllConfig(); - } - if (!this.lwm2mDeviceProfileTransportConfFormGroup.get('binding').pristine) { - this.configurationValue['bootstrap'].servers.binding = this.lwm2mDeviceProfileTransportConfFormGroup.get('binding').value; - this.lwm2mDeviceProfileTransportConfFormGroup.get('binding').markAsPristine({ - onlySelf: true - }); + private updateDeviceProfileValue(config): void { + if (this.lwm2mDeviceProfileFormGroup.valid) { + this.updateObserveAttrTelemetryFromGroupToJson(config.observeAttrTelemetry.clientLwM2M); + this.configurationValue.bootstrap.bootstrapServer = config.bootstrapServer; + this.configurationValue.bootstrap.lwm2mServer = config.lwm2mServer; + const bootstrapServers = this.configurationValue.bootstrap.servers; + bootstrapServers.shortId = config.shortId; + bootstrapServers.lifetime = config.lifetime; + bootstrapServers.defaultMinPeriod = config.defaultMinPeriod; + bootstrapServers.notifIfDisabled = config.notifIfDisabled; + bootstrapServers.binding = config.binding; this.upDateJsonAllConfig(); + this.updateModel(); } } - getObserveAttrTelemetryObjects(listObject: ObjectLwM2M[]): ObjectLwM2M [] { - let clientObserveAttr = deepClone(listObject) as ObjectLwM2M[]; + private getObserveAttrTelemetryObjects = (listObject: ObjectLwM2M[]): object => { + const clientObserveAttrTelemetry = listObject; if (this.configurationValue[this.observeAttr]) { - let observeArray = this.configurationValue[this.observeAttr][this.observe] as Array; - let attributeArray = this.configurationValue[this.observeAttr][this.attribute] as Array; - let telemetryArray = this.configurationValue[this.observeAttr][this.telemetry] as Array; - let keyNameJson = this.configurationValue[this.observeAttr][this.keyName] as JsonObject; - if (this.includesInstancesNo(attributeArray, telemetryArray, clientObserveAttr)) { - this.addInstances(attributeArray, telemetryArray, clientObserveAttr); + const observeArray = this.configurationValue[this.observeAttr][this.observe]; + const attributeArray = this.configurationValue[this.observeAttr][this.attribute]; + const telemetryArray = this.configurationValue[this.observeAttr][this.telemetry]; + const keyNameJson = this.configurationValue[this.observeAttr][this.keyName]; + if (this.includesNotZeroInstance(attributeArray, telemetryArray)) { + this.addInstances(attributeArray, telemetryArray, clientObserveAttrTelemetry); + } + if (isDefinedAndNotNull(observeArray)) { + this.updateObserveAttrTelemetryObjects(observeArray, clientObserveAttrTelemetry, 'observe'); + } + if (isDefinedAndNotNull(attributeArray)) { + this.updateObserveAttrTelemetryObjects(attributeArray, clientObserveAttrTelemetry, 'attribute'); + } + if (isDefinedAndNotNull(telemetryArray)) { + this.updateObserveAttrTelemetryObjects(telemetryArray, clientObserveAttrTelemetry, 'telemetry'); + } + if (isDefinedAndNotNull(keyNameJson)) { + this.updateKeyNameObjects(keyNameJson, clientObserveAttrTelemetry); } - if (observeArray) this.updateObserveAttrTelemetryObjects(observeArray, clientObserveAttr, "observe"); - if (attributeArray) this.updateObserveAttrTelemetryObjects(attributeArray, clientObserveAttr, "attribute"); - if (telemetryArray) this.updateObserveAttrTelemetryObjects(telemetryArray, clientObserveAttr, "telemetry"); - if (keyNameJson) this.updateKeyNameObjects(deepClone(keyNameJson), clientObserveAttr); } - clientObserveAttr.forEach(obj => { - obj.instances.sort((a,b) => a.id - b.id);; - }) - return clientObserveAttr; + return {clientLwM2M: clientObserveAttrTelemetry}; } - includesInstancesNo(attributeArray: Array, telemetryArray: Array, clientObserveAttr: ObjectLwM2M[]): boolean { - let isIdIndex = (element) => !element.includes("/0/"); - return attributeArray.findIndex(isIdIndex) >= 0 || telemetryArray.findIndex(isIdIndex) >= 0 - + private includesNotZeroInstance = (attribute: string[], telemetry: string[]): boolean => { + const isNotZeroInstanceId = (instance) => !instance.includes('/0/'); + return attribute.some(isNotZeroInstanceId) || telemetry.some(isNotZeroInstanceId); } - addInstances(attributeArray: Array, telemetryArray: Array, clientObserveAttr: ObjectLwM2M[]): void { - let attr = [] as Array; - [...attributeArray].filter(x => (!x.includes("/0/"))).forEach(x => { - attr.push(this.convertPathToInstance(x)); - }); - let telemetry = [] as Array; - [...telemetryArray].filter(x => (!x.includes("/0/"))).forEach(x => { - telemetry.push(this.convertPathToInstance(x)); - }); - let instancesNoZero = new Set(attr.concat(telemetry).sort()); - instancesNoZero.forEach(path => { - let pathParameter = Array.from(path.split('/'), Number); - let objectLwM2M = clientObserveAttr.find(x => (x.id === pathParameter[0])); + private addInstances = (attribute: string[], telemetry: string[], clientObserveAttrTelemetry: ObjectLwM2M[]): void => { + const instancesPath = attribute.concat(telemetry) + .filter(instance => !instance.includes('/0/')) + .map(instance => instance.slice(1, instance.lastIndexOf('/'))) + .sort(this.sortPath); + + new Set(instancesPath).forEach(path => { + const pathParameter = Array.from(path.split('/'), Number); + const objectLwM2M = clientObserveAttrTelemetry.find(x => x.id === pathParameter[0]); if (objectLwM2M) { - let instance = deepClone(objectLwM2M.instances[0]) as Instance; + const instance = deepClone(objectLwM2M.instances[0]); instance.id = pathParameter[1]; objectLwM2M.instances.push(instance); } - }) - } - - convertPathToInstance(path: string): string { - let newX = Array.from(path.substring(1).split('/'), Number); - return [newX[0], newX[1]].join("/"); + }); } - updateObserveAttrTelemetryObjects(isParameter: Array, clientObserveAttr: ObjectLwM2M[], nameParameter: string): void { - isParameter.forEach(attr => { - let idKeys = Array.from(attr.substring(1).split('/'), Number); - clientObserveAttr - .forEach(e => { - if (e.id == idKeys[0]) { - let instance = e.instances.find(e => e.id == idKeys[1]); - if (isNotNullOrUndefined(instance)) { - instance.resources.find(e => e.id == idKeys[2])[nameParameter] = true; - } - } - }); + private updateObserveAttrTelemetryObjects = (parameters: string[], clientObserveAttrTelemetry: ObjectLwM2M[], + nameParameter: string): void => { + parameters.forEach(parameter => { + const [objectId, instanceId, resourceId] = Array.from(parameter.substring(1).split('/'), Number); + clientObserveAttrTelemetry.find(objectLwm2m => objectLwm2m.id === objectId) + .instances.find(itrInstance => itrInstance.id === instanceId) + .resources.find(resource => resource.id === resourceId) + [nameParameter] = true; }); } - updateKeyNameObjects(nameJson: JsonObject, clientObserveAttr: ObjectLwM2M[]): void { - let keyName = JSON.parse(JSON.stringify(nameJson)); - Object.keys(keyName).forEach(function (key) { - let idKeys = Array.from(key.substring(1).split('/'), Number); - clientObserveAttr - .forEach(e => { - if (e.id == idKeys[0]) { - e.instances.find(e => e.id == idKeys[1]).resources - .find(e => e.id == idKeys[2]).keyName = keyName[key]; - } - }); + private updateKeyNameObjects = (nameJson: JsonObject, clientObserveAttrTelemetry: ObjectLwM2M[]): void => { + const keyName = JSON.parse(JSON.stringify(nameJson)); + Object.keys(keyName).forEach(key => { + const [objectId, instanceId, resourceId] = Array.from(key.substring(1).split('/'), Number); + clientObserveAttrTelemetry.find(objectLwm2m => objectLwm2m.id === objectId) + .instances.find(instance => instance.id === instanceId) + .resources.find(resource => resource.id === resourceId) + .keyName = keyName[key]; }); } - upDateObserveAttrTelemetryFromGroupToJson(val: ObjectLwM2M []): void { - let observeArray = [] as Array; - let attributeArray = [] as Array; - let telemetryArray = [] as Array; - let observeJson = JSON.parse(JSON.stringify(val)); + private updateObserveAttrTelemetryFromGroupToJson = (val: ObjectLwM2M[]): void => { + const observeArray: Array = []; + const attributeArray: Array = []; + const telemetryArray: Array = []; + const keyNameNew = {}; + const observeJson: ObjectLwM2M[] = JSON.parse(JSON.stringify(val)); + const paths = new Set(); let pathObj; let pathInst; - let pathRes + let pathRes; observeJson.forEach(obj => { - Object.entries(obj).forEach(([key, value]) => { + for (const [key, value] of Object.entries(obj)) { if (key === 'id') { pathObj = value; } if (key === 'instances') { - let instancesJson = JSON.parse(JSON.stringify(value)) as []; + const instancesJson = value as Instance[]; if (instancesJson.length > 0) { instancesJson.forEach(instance => { - Object.entries(instance).forEach(([key, value]) => { - if (key === 'id') { - pathInst = value; + for (const [instanceKey, instanceValue] of Object.entries(instance)) { + if (instanceKey === 'id') { + pathInst = instanceValue; } - if (key === 'resources') { - let resourcesJson = JSON.parse(JSON.stringify(value)) as []; + if (instanceKey === 'resources') { + const resourcesJson = instanceValue as ResourceLwM2M[]; if (resourcesJson.length > 0) { resourcesJson.forEach(res => { - Object.entries(res).forEach(([key, value]) => { - if (key === 'id') { - // pathRes = value - pathRes = '/' + pathObj + '/' + pathInst + '/' + value; - } else if (key === 'observe' && value) { - observeArray.push(pathRes) - } else if (key === 'attribute' && value) { - attributeArray.push(pathRes) - } else if (key === 'telemetry' && value) { - telemetryArray.push(pathRes) + for (const [resourceKey, value] of Object.entries(res)) { + if (resourceKey === 'id') { + pathRes = `/${pathObj}/${pathInst}/${value}`; + } else if (resourceKey === 'observe' && value) { + observeArray.push(pathRes); + } else if (resourceKey === 'attribute' && value) { + attributeArray.push(pathRes); + paths.add(pathRes); + } else if (resourceKey === 'telemetry' && value) { + telemetryArray.push(pathRes); + paths.add(pathRes); + } + else if (resourceKey === this.keyName && paths.has(pathRes)) { + console.warn(pathRes, value); + keyNameNew[pathRes] = value; } - }); + } }); } } - }); + } }); } } - }); + } }); if (isUndefined(this.configurationValue[this.observeAttr])) { this.configurationValue[this.observeAttr] = { @@ -392,68 +331,34 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro this.configurationValue[this.observeAttr][this.attribute] = attributeArray; this.configurationValue[this.observeAttr][this.telemetry] = telemetryArray; } - this.updateKeyName(); - } - - sortObjectKeyPathJson(key, value) { - if (key == "keyName") { - return Object.keys(value).sort((a, b) => { - let aLC = Array.from(a.substring(1).split('/'), Number); - let bLC = Array.from(b.substring(1).split('/'), Number); - return aLC[0] == bLC[0] ? aLC[1] - bLC[1] : aLC[0] - bLC[0]; - }).reduce((r, k) => (r[k] = value[k], r), {}); + this.configurationValue[this.observeAttr][this.keyName] = this.sortObjectKeyPathJson('keyName', keyNameNew); + } + + sortObjectKeyPathJson = (key: string, value: object): object => { + if (key === 'keyName') { + return Object.keys(value).sort(this.sortPath).reduce((obj, keySort) => { + obj[keySort] = value[keySort]; + return obj; + }, {}); + } else if (key === 'observe' || key === 'attribute' || key === 'telemetry') { + return Object.values(value).sort(this.sortPath).reduce((arr, arrValue) => { + arr.push(arrValue); + return arr; + }, []); } else { - return value + return value; } } - updateKeyName(): void { - let paths = new Set(); - if (this.configurationValue[this.observeAttr][this.attribute]) { - this.configurationValue[this.observeAttr][this.attribute].forEach(path => { - paths.add(path); - }); - } - if (this.configurationValue[this.observeAttr][this.telemetry]) { - this.configurationValue[this.observeAttr][this.telemetry].forEach(path => { - paths.add(path); - }); - } - let keyNameNew = {}; - paths.forEach(path => { - let pathParameter = this.findIndexsForIds(path); - if (pathParameter.length === 3) { - let value = this.lwm2mDeviceProfileTransportConfFormGroup.get("observeAttrTelemetry").value['clientLwM2M'][pathParameter[0]].instances[pathParameter[1]].resources[pathParameter[2]][this.keyName]; - keyNameNew[path] = value; - } + private sortPath = (a, b): number => { + return a.localeCompare(b, undefined, { + numeric: true, + sensitivity: 'base' }); - this.configurationValue[this.observeAttr][this.keyName] = this.sortObjectKeyPathJson("keyName", keyNameNew); } - findIndexsForIds(path: string): number[] { - let pathParameter = Array.from(path.substring(1).split('/'), Number); - let pathParameterIndexes = [] as number[]; - let objectsOld = deepClone(this.lwm2mDeviceProfileTransportConfFormGroup.get("observeAttrTelemetry").value.clientLwM2M) as ObjectLwM2M[]; - let isIdIndex = (element) => element.id === pathParameter[0]; - let objIndex = objectsOld.findIndex(isIdIndex); - if (objIndex >= 0) { - pathParameterIndexes.push(objIndex); - isIdIndex = (element) => element.id === pathParameter[1]; - let instIndex = objectsOld[objIndex].instances.findIndex(isIdIndex); - if (instIndex >= 0) { - pathParameterIndexes.push(instIndex); - isIdIndex = (element) => element.id === pathParameter[2]; - let resIndex = objectsOld[objIndex].instances[instIndex].resources.findIndex(isIdIndex); - if (resIndex >= 0) { - pathParameterIndexes.push(resIndex); - } - } - } - return pathParameterIndexes; - } - - getObjectsFromJsonAllConfig(): number [] { - let objectsIds = new Set(); + private getObjectsFromJsonAllConfig = (): number[] => { + const objectsIds = new Set(); if (this.configurationValue[this.observeAttr]) { if (this.configurationValue[this.observeAttr][this.observe]) { this.configurationValue[this.observeAttr][this.observe].forEach(obj => { @@ -474,50 +379,45 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro return (objectsIds.size > 0) ? Array.from(objectsIds) : null; } - upDateJsonAllConfig(): void { - this.lwm2mDeviceProfileTransportConfFormGroup.patchValue({ + private upDateJsonAllConfig = (): void => { + this.lwm2mDeviceConfigFormGroup.patchValue({ configurationJson: this.configurationValue }, {emitEvent: false}); - this.lwm2mDeviceProfileTransportConfFormGroup.markAsPristine({ - onlySelf: true - }); } - addObjectsList(value: ObjectLwM2M[]): void { - this.updateObserveAttrTelemetryObjectFormGroup(deepClone(value)); + addObjectsList = (value: ObjectLwM2M[]): void => { + this.updateObserveAttrTelemetryObjectFormGroup(value); } - removeObjectsList(value: ObjectLwM2M): void { - let objectsOld = deepClone(this.lwm2mDeviceProfileTransportConfFormGroup.get("observeAttrTelemetry").value.clientLwM2M); + removeObjectsList = (value: ObjectLwM2M): void => { + const objectsOld = this.lwm2mDeviceProfileFormGroup.get('observeAttrTelemetry').value.clientLwM2M; const isIdIndex = (element) => element.id === value.id; - let index = objectsOld.findIndex(isIdIndex); + const index = objectsOld.findIndex(isIdIndex); if (index >= 0) { objectsOld.splice(index, 1); } - this.updateObserveAttrTelemetryObjectFormGroup(objectsOld); this.removeObserveAttrTelemetryFromJson(this.observe, value.id); this.removeObserveAttrTelemetryFromJson(this.telemetry, value.id); this.removeObserveAttrTelemetryFromJson(this.attribute, value.id); - this.removeObserveAttrTelemetryFromJson(this.attribute, value.id); this.removeKeyNameFromJson(value.id); + this.updateObserveAttrTelemetryObjectFormGroup(objectsOld); this.upDateJsonAllConfig(); } - removeObserveAttrTelemetryFromJson(observeAttrTel: string, id: number): void { - let isIdIndex = (element) => Array.from(element.substring(1).split('/'), Number)[0] === id; + private removeObserveAttrTelemetryFromJson = (observeAttrTel: string, id: number): void => { + const isIdIndex = (element) => element.startsWith(`/${id}`); let index = this.configurationValue[this.observeAttr][observeAttrTel].findIndex(isIdIndex); while (index >= 0) { this.configurationValue[this.observeAttr][observeAttrTel].splice(index, 1); - index = this.configurationValue[this.observeAttr][observeAttrTel].findIndex(isIdIndex); + index = this.configurationValue[this.observeAttr][observeAttrTel].findIndex(isIdIndex, index); } } - removeKeyNameFromJson(id: number): void { - let keyNmaeJson = this.configurationValue[this.observeAttr][this.keyName]; - Object.keys(keyNmaeJson).forEach(function (key) { - let idKey = Array.from(key.substring(1).split('/'), Number)[0]; - if (idKey === id) { - delete keyNmaeJson[key]; + private removeKeyNameFromJson = (id: number): void => { + const keyNameJson = this.configurationValue[this.observeAttr][this.keyName]; + Object.keys(keyNameJson).forEach(key => { + if (key.startsWith(`/${id}`)) { + delete keyNameJson[key]; } }); } @@ -527,10 +427,10 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro if (!isPath) { isPath = this.findPathInJson(path, this.telemetry); } - return (isPath) ? true : false; + return !!isPath; } - findPathInJson(path: string, side: string): string { + private findPathInJson = (path: string, side: string): string => { if (this.configurationValue[this.observeAttr]) { if (this.configurationValue[this.observeAttr][side]) { return this.configurationValue[this.observeAttr][side].find( diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances-list.component.html b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances-list.component.html index 7268022256..3d03987660 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances-list.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances-list.component.html @@ -15,43 +15,44 @@ limitations under the License. --> - - {{ 'device-profile.lwm2m.instances-list' | translate }} - - - {{instanceIdInputFor}} - close - - - {{ 'device-profile.lwm2m.instances-input' | translate }} - - - {{ translate.get('device-profile.lwm2m.valid-id-instance-no-min', { - instance: instanceIdInput.value.toString(), min: instanceIdValueMin.toString() - }) | async }} - - - {{ translate.get('device-profile.lwm2m.valid-id-instance-no-max', { - instance: instanceIdInput.value.toString(), max: instanceIdValueMax.toString() - }) | async }} - - - {{ translate.get('device-profile.lwm2m.instances-input-holder', { - instance: instanceIdInput.value.toString(), max: instanceIdValueMax.toString() - }) | async }} - - - - +
+ + {{ 'device-profile.lwm2m.instances-list' | translate }} + + + {{instanceId}} + close + + + + + {{ 'device-profile.lwm2m.instances-input' | translate }} + + + + {{ translate.get('device-profile.lwm2m.valid-id-instance', { + count: 2, instance: instanceId, min: instanceIdValueMin + }) | async }} + + + {{ translate.get('device-profile.lwm2m.valid-id-instance', { + count: 1, instance: instanceId, max: instanceIdValueMax + }) | async }} + + + {{ translate.get('device-profile.lwm2m.valid-id-instance', { + count: 0, instance: instanceId, max: instanceIdValueMax + }) | async }} + + +
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances-list.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances-list.component.ts index ec4b2c1fe9..c556cef7c8 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances-list.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances-list.component.ts @@ -14,90 +14,48 @@ /// limitations under the License. /// -import { - Component, - forwardRef, - Input, - OnInit, - ViewChild, - ElementRef, -} from '@angular/core'; -import { - ControlValueAccessor, - FormBuilder, - FormGroup, - NG_VALUE_ACCESSOR, Validators -} from '@angular/forms'; -import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { Component, forwardRef, } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; import { Store } from '@ngrx/store'; -import { AppState } from '../../../../../../core/core.state'; -import { MatChipList } from '@angular/material/chips'; -import { - INSTANCES_ID_VALUE_MAX, - INSTANCES_ID_VALUE_MIN -} from './profile-config.models'; +import { AppState } from '@core/core.state'; +import { INSTANCES_ID_VALUE_MAX, INSTANCES_ID_VALUE_MIN, KEY_REGEXP_NUMBER } from './profile-config.models'; import { TranslateService } from '@ngx-translate/core'; -import { DeviceProfileService } from '../../../../../../core/http/device-profile.service'; +import { DeviceProfileService } from '@core/http/device-profile.service'; @Component({ selector: 'tb-profile-lwm2m-object-add-instances-list', templateUrl: './lwm2m-object-add-instances-list.component.html', - styleUrls: ['./lwm2m-object-add-instances-list.component.scss'], + styleUrls: [], providers: [{ - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => Lwm2mObjectAddInstancesListComponent), - multi: true - }] + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => Lwm2mObjectAddInstancesListComponent), + multi: true + }] }) -export class Lwm2mObjectAddInstancesListComponent implements ControlValueAccessor, OnInit, Validators { +export class Lwm2mObjectAddInstancesListComponent implements ControlValueAccessor { - lwm2mObjectListFormGroup: FormGroup; - private requiredValue: boolean; - // TODO: voba - check this fix - // private instancesIdsList: Set | null; - instancesIdsList: Set | null; - filteredObjectsList: Array; - private disabled = false as boolean; - private dirty = false as boolean; - instanceIdValueMin = INSTANCES_ID_VALUE_MIN as number - instanceIdValueMax = INSTANCES_ID_VALUE_MAX as number + private disabled = false; + private dirty = false; - get required(): boolean { - return this.requiredValue; - } - - @Input() - set required(value: boolean) { - const newVal = coerceBooleanProperty(value); - if (this.requiredValue !== newVal) { - this.requiredValue = newVal; - this.updateValidators(); - } - } - - @ViewChild('instanceIdInput') instanceIdInput: ElementRef; - @ViewChild('chipList', {static: true}) chipList: MatChipList; + instancesListFormGroup: FormGroup; + instancesId = new Set(); + instanceIdValueMin = INSTANCES_ID_VALUE_MIN; + instanceIdValueMax = INSTANCES_ID_VALUE_MAX; - private propagateChange = (v: any) => { - }; + private propagateChange = (v: any) => { }; constructor(private store: Store, public translate: TranslateService, private deviceProfileService: DeviceProfileService, private fb: FormBuilder) { - this.lwm2mObjectListFormGroup = this.fb.group({ - instancesIdsList: [null], - instanceIdInput: [null] + this.instancesListFormGroup = this.fb.group({ + instanceIdInput: [null, [ + Validators.min(this.instanceIdValueMin), + Validators.max(this.instanceIdValueMax), + Validators.pattern(KEY_REGEXP_NUMBER)]] }); } - updateValidators() { - this.lwm2mObjectListFormGroup.get('instanceIdInput').setValidators([ - Validators.min(this.instanceIdValueMin), - Validators.max(this.instanceIdValueMax)]); - this.lwm2mObjectListFormGroup.get('instanceIdInput').updateValueAndValidity(); - } - registerOnChange(fn: any): void { this.propagateChange = fn; } @@ -105,77 +63,38 @@ export class Lwm2mObjectAddInstancesListComponent implements ControlValueAccesso registerOnTouched(fn: any): void { } - ngOnInit() { - } - - ngAfterViewInit(): void { - } - setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; if (isDisabled) { - this.lwm2mObjectListFormGroup.disable({emitEvent: false}); + this.instancesListFormGroup.disable({emitEvent: false}); } else { - this.lwm2mObjectListFormGroup.enable({emitEvent: false}); + this.instancesListFormGroup.enable({emitEvent: false}); } } writeValue(value: Set): void { - this.instancesIdsList = new Set(); if (value && value.size) { - value.forEach(item => this.instancesIdsList.add(item)); - this.lwm2mObjectListFormGroup.get('instancesIdsList').setValue(this.instancesIdsList); + this.instancesId = value; } - this.updateValidators(); this.dirty = false; } - add(value: number): void { - if (!isNaN(value) && this.lwm2mObjectListFormGroup.get('instanceIdInput').valid) { - this.instancesIdsList.add(value); - this.lwm2mObjectListFormGroup.get('instancesIdsList').setValue(this.instancesIdsList); - this.propagateChange(this.instancesIdsList); - this.dirty = true + add = (): void => { + if (this.instancesListFormGroup.get('instanceIdInput').valid && Number.isFinite(Number(this.instanceId))) { + this.instancesId.add(Number(this.instanceId)); + this.instancesListFormGroup.get('instanceIdInput').setValue(null); + this.propagateChange(this.instancesId); + this.dirty = true; } - this.clear(); - } - - remove(object: number) { - this.instancesIdsList.delete(object); - this.lwm2mObjectListFormGroup.get('instancesIdsList').setValue(this.instancesIdsList); - this.propagateChange(this.instancesIdsList); - this.dirty = true - this.clear(); - } - - displayFn(object?: number): number | undefined { - return object ? object : undefined; } - clear() { - this.lwm2mObjectListFormGroup.get('instanceIdInput').patchValue(null, {emitEvent: true}); - this.instanceIdInput.nativeElement.value = ""; - setTimeout(() => { - this.instanceIdInput.nativeElement.blur(); - this.instanceIdInput.nativeElement.focus(); - }, 0); + remove = (object: number): void => { + this.instancesId.delete(object); + this.propagateChange(this.instancesId); + this.dirty = true; } - onkeydown(e: KeyboardEvent) { - if (e.keyCode == 189 || e.keyCode == 187 || e.keyCode == 109 || e.keyCode == 107) { - return false; - } else if (e.keyCode == 8) { - if (this.lwm2mObjectListFormGroup.get('instanceIdInput').value == null) { - this.clear(); - } - this.instanceIdInput.nativeElement.focus(); - } - } - - onFocus() { - if (this.dirty) { - this.lwm2mObjectListFormGroup.get('instanceIdInput').updateValueAndValidity({onlySelf: true, emitEvent: true}); - this.dirty = false; - } + get instanceId(): number { + return this.instancesListFormGroup.get('instanceIdInput').value; } } diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances.component.html b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances.component.html index 69991d794b..34a8d6a843 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances.component.html @@ -15,7 +15,7 @@ limitations under the License. --> -
+ {{data.objectName}}    (object [{{data.objectId}}]) @@ -30,24 +30,21 @@
-
- - -
+ +
- -
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances.component.ts index 5d2b636c13..91dbc824fc 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances.component.ts @@ -16,12 +16,11 @@ import { Component, Inject, OnInit } from '@angular/core'; import { DialogComponent } from '@shared/components/dialog.component'; -import { ControlValueAccessor, FormBuilder, FormGroup } from '@angular/forms'; +import { FormBuilder, FormGroup } from '@angular/forms'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { Router } from '@angular/router'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; -import { TranslateService } from '@ngx-translate/core'; export interface Lwm2mObjectAddInstancesData { instancesIds: Set; @@ -36,9 +35,7 @@ export interface Lwm2mObjectAddInstancesData { }) export class Lwm2mObjectAddInstancesComponent extends DialogComponent implements OnInit { - jsonFormGroup: FormGroup; - // title: string; - + instancesFormGroup: FormGroup; submitted = false; constructor(protected store: Store, @@ -49,11 +46,10 @@ export class Lwm2mObjectAddInstancesComponent extends DialogComponent - + - + @@ -48,7 +48,7 @@ - + {{ 'device-profile.lwm2m.object-list-empty' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.ts index 601ae10e2c..718c9281d7 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.ts @@ -14,39 +14,22 @@ /// limitations under the License. /// -import { - Component, - forwardRef, - Input, - OnInit, - ViewChild, - ElementRef, - Output, - EventEmitter -} from '@angular/core'; -import { - ControlValueAccessor, - FormBuilder, - FormGroup, - NG_VALUE_ACCESSOR, Validators -} from '@angular/forms'; -import {coerceBooleanProperty} from '@angular/cdk/coercion'; -import {Store} from '@ngrx/store'; -import {AppState} from '../../../../../../core/core.state'; -import {MatChipList} from '@angular/material/chips'; -import {MatAutocomplete} from '@angular/material/autocomplete'; -import {Observable} from 'rxjs'; -import {filter, map, mergeMap, share, tap} from 'rxjs/operators'; -import {ObjectLwM2M} from './profile-config.models'; -import {TranslateService} from '@ngx-translate/core'; -import {DeviceProfileService} from '../../../../../../core/http/device-profile.service'; -import {PageLink} from '../../../../../../shared/models/page/page-link'; -import {Direction} from '../../../../../../shared/models/page/sort-order'; +import { Component, ElementRef, EventEmitter, forwardRef, Input, OnInit, Output, ViewChild } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Observable } from 'rxjs'; +import { filter, map, mergeMap, publishReplay, refCount, tap } from 'rxjs/operators'; +import { ObjectLwM2M } from './profile-config.models'; +import { TranslateService } from '@ngx-translate/core'; +import { DeviceProfileService } from '@core/http/device-profile.service'; +import { Direction } from '@shared/models/page/sort-order'; +import { isDefined, isDefinedAndNotNull, isEmptyStr, isString } from '@core/utils'; @Component({ selector: 'tb-profile-lwm2m-object-list', templateUrl: './lwm2m-object-list.component.html', - styleUrls: [], providers: [ { provide: NG_VALUE_ACCESSOR, @@ -56,16 +39,16 @@ import {Direction} from '../../../../../../shared/models/page/sort-order'; }) export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, Validators { - lwm2mObjectListFormGroup: FormGroup; private requiredValue: boolean; - modelValue: Array | null; + private dirty = false; + private lw2mModels: Observable>; + private modelValue: Array = []; + + lwm2mListFormGroup: FormGroup; objectsList: Array = []; filteredObjectsList: Observable>; - // TODO: voba - check this fix - // private disabled = false as boolean; - disabled = false as boolean; - searchText = '' as string; - private dirty = false as boolean;Lwm2mObjectAddInstancesListComponent + disabled = false; + searchText = ''; get required(): boolean { return this.requiredValue; @@ -73,11 +56,8 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V @Input() set required(value: boolean) { - const newVal = coerceBooleanProperty(value); - if (this.requiredValue !== newVal) { - this.requiredValue = newVal; - this.updateValidators(); - } + this.requiredValue = coerceBooleanProperty(value); + this.updateValidators(); } @Output() @@ -87,25 +67,23 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V removeList = new EventEmitter(); @ViewChild('objectInput') objectInput: ElementRef; - @ViewChild('objectAutocomplete') matAutocomplete: MatAutocomplete; - @ViewChild('chipList', {static: true}) chipList: MatChipList; private propagateChange = (v: any) => { - }; + } constructor(private store: Store, public translate: TranslateService, private deviceProfileService: DeviceProfileService, private fb: FormBuilder) { - this.lwm2mObjectListFormGroup = this.fb.group({ + this.lwm2mListFormGroup = this.fb.group({ objectsList: [this.objectsList], - objectLwm2m: [null, this.required ? [Validators.required] : []] + objectLwm2m: [''] }); } - updateValidators() { - this.lwm2mObjectListFormGroup.get('objectLwm2m').setValidators(this.required ? [Validators.required] : []); - this.lwm2mObjectListFormGroup.get('objectLwm2m').updateValueAndValidity(); + private updateValidators = (): void => { + this.lwm2mListFormGroup.get('objectLwm2m').setValidators(this.required ? [Validators.required] : []); + this.lwm2mListFormGroup.get('objectLwm2m').updateValueAndValidity(); } registerOnChange(fn: any): void { @@ -116,118 +94,117 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V } ngOnInit() { - this.filteredObjectsList = this.lwm2mObjectListFormGroup.get('objectLwm2m').valueChanges + this.filteredObjectsList = this.lwm2mListFormGroup.get('objectLwm2m').valueChanges .pipe( tap((value) => { if (value && typeof value !== 'string') { this.add(value); } else if (value === null) { - this.clear(this.objectInput.nativeElement.value); + this.clear(); } }), - filter((value) => typeof value === 'string'), - map((value) => value ? (typeof value === 'string' ? value : value.name) : ''), - mergeMap(name => this.fetchListObjects(name)), - share() + filter(searchText => isString(searchText)), + mergeMap(searchText => this.fetchListObjects(searchText)) ); } - ngAfterViewInit(): void { - } - setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; if (isDisabled) { - this.lwm2mObjectListFormGroup.disable({emitEvent: false}); + this.lwm2mListFormGroup.disable({emitEvent: false}); + if (isDefined(this.objectInput)) { + this.clear(); + } } else { - this.lwm2mObjectListFormGroup.enable({emitEvent: false}); + this.lwm2mListFormGroup.enable({emitEvent: false}); } } writeValue(value: any): void { this.searchText = ''; - if (value.hasOwnProperty("objectIds") && value["objectIds"] != null && value["objectIds"].length > 0) { - this.modelValue = [...value["objectIds"]]; - this.objectsList = value["objectsList"]; - this.lwm2mObjectListFormGroup.get('objectsList').setValue(this.objectsList); - } else { - this.objectsList = []; - this.lwm2mObjectListFormGroup.get('objectsList').setValue(this.objectsList); - this.modelValue = null; - } - this.dirty = true; - } - - reset() { - this.objectsList = []; - this.lwm2mObjectListFormGroup.get('objectsList').setValue(this.objectsList); - this.modelValue = null; - if (this.objectInput) { - this.objectInput.nativeElement.value = ''; + if (isDefinedAndNotNull(value)) { + if (Array.isArray(value.objectIds)) { + this.modelValue = value.objectIds; + this.objectsList = value.objectsList; + } else { + this.objectsList = []; + this.modelValue = []; + } + this.lwm2mListFormGroup.get('objectsList').setValue(this.objectsList, {emitEvents: false}); + this.dirty = false; } - this.lwm2mObjectListFormGroup.get('objectLwm2m').patchValue('', {emitEvent: false}); - this.propagateChange(this.modelValue); - this.dirty = true; } - add(object: ObjectLwM2M): void { - if (!this.modelValue || this.modelValue.indexOf(object.id) === -1) { - if (!this.modelValue) { - this.modelValue = []; - } + private add(object: ObjectLwM2M): void { + if (this.modelValue.indexOf(object.id) === -1) { this.modelValue.push(object.id); this.objectsList.push(object); - this.lwm2mObjectListFormGroup.get('objectsList').setValue(this.objectsList); + this.lwm2mListFormGroup.get('objectsList').setValue(this.objectsList); this.addList.next(this.objectsList); } - this.propagateChange(this.modelValue); this.clear(); } - remove(object: ObjectLwM2M) { + remove = (object: ObjectLwM2M): void => { let index = this.objectsList.indexOf(object); if (index >= 0) { this.objectsList.splice(index, 1); - this.lwm2mObjectListFormGroup.get('objectsList').setValue(this.objectsList); + this.lwm2mListFormGroup.get('objectsList').setValue(this.objectsList); index = this.modelValue.indexOf(object.id); this.modelValue.splice(index, 1); this.removeList.next(object); if (!this.modelValue.length) { this.modelValue = null; } - this.propagateChange(this.modelValue); this.clear(); } } - displayObjectLwm2mFn(object?: ObjectLwM2M): string | undefined { + displayObjectLwm2mFn = (object?: ObjectLwM2M): string | undefined => { return object ? object.name : undefined; } - fetchListObjects(searchText?: string): Observable> { + private fetchListObjects = (searchText?: string): Observable> => { this.searchText = searchText; - const pageLink = new PageLink(10, 0, searchText, { - property: 'name', - direction: Direction.ASC - }); - return this.deviceProfileService.getLwm2mObjectsPage(pageLink, {ignoreLoading: true}).pipe( - map(pageData => { - let data = pageData.data; - return data; - }) + const filters = {names: [], ids: []}; + if (isDefinedAndNotNull(searchText) && !isEmptyStr(searchText)) { + const ids = searchText.match(/\d+/g); + filters.ids = ids !== null ? ids.map(Number) : filters.ids; + filters.names = searchText.trim().toUpperCase().split(' '); + } + const predicate = objectLwM2M => filters.names.find(word => objectLwM2M.name.toUpperCase().includes(word)) + || filters.ids.includes(objectLwM2M.id); + return this.getLwM2mModels().pipe( + map(objectLwM2Ms => searchText ? objectLwM2Ms.filter(predicate) : objectLwM2Ms) ); } - onFocus() { - if (this.dirty) { - this.lwm2mObjectListFormGroup.get('objectLwm2m').updateValueAndValidity({onlySelf: true, emitEvent: true}); - this.dirty = false; + private getLwM2mModels(): Observable> { + if (!this.lw2mModels) { + const sortOrder = { + property: 'name', + direction: Direction.ASC + }; + this.lw2mModels = this.deviceProfileService.getLwm2mObjects(sortOrder).pipe( + publishReplay(1), + refCount() + ); + } + return this.lw2mModels; + } + + + onFocus = (): void => { + if (!this.dirty) { + this.lwm2mListFormGroup.get('objectLwm2m').updateValueAndValidity({onlySelf: true, emitEvent: true}); + this.dirty = true; } } - clear(value: string = '') { + private clear = (value: string = ''): void => { this.objectInput.nativeElement.value = value; - this.lwm2mObjectListFormGroup.get('objectLwm2m').patchValue(value, {emitEvent: true}); + this.searchText = ''; + this.lwm2mListFormGroup.get('objectLwm2m').patchValue(value); setTimeout(() => { this.objectInput.nativeElement.blur(); this.objectInput.nativeElement.focus(); diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resource.component.html b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resource.component.html index 2750aa694d..86c232d488 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resource.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resource.component.html @@ -17,12 +17,10 @@ -->
-
-
-
- -
+ *ngFor="let resourceLwM2M of resourceFormArray.controls; let i = index; trackBy: trackByParams"> +
+
+
device-profile.lwm2m.observe-label @@ -72,10 +70,9 @@ {{ 'device-profile.lwm2m.key-name_label' | translate }} - {{ 'device-profile.lwm2m.key-name' | translate }} {{ 'device-profile.lwm2m.required' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resource.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resource.component.ts index 1a72d7fd9c..4441a285b2 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resource.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resource.component.ts @@ -14,20 +14,12 @@ /// limitations under the License. /// -import { Component, forwardRef, Input, OnInit } from '@angular/core'; -import { - ControlValueAccessor, - FormArray, FormBuilder, - FormGroup, - NG_VALUE_ACCESSOR, Validators -} from '@angular/forms'; -import { - CAMEL_CASE_REGEXP, - ResourceLwM2M -} from '@home/components/profile/device/lwm2m/profile-config.models'; +import { Component, forwardRef, Input } from '@angular/core'; +import { ControlValueAccessor, FormArray, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { ResourceLwM2M } from '@home/components/profile/device/lwm2m/profile-config.models'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { deepClone, isUndefined } from '@core/utils'; +import _ from 'lodash'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; @Component({ @@ -43,13 +35,13 @@ import { coerceBooleanProperty } from '@angular/cdk/coercion'; ] }) -export class Lwm2mObserveAttrTelemetryResourceComponent implements ControlValueAccessor, OnInit, Validators { +export class Lwm2mObserveAttrTelemetryResourceComponent implements ControlValueAccessor { - resourceFormGroup : FormGroup; - - disabled = false as boolean; private requiredValue: boolean; + resourceFormGroup: FormGroup; + disabled = false; + get required(): boolean { return this.requiredValue; } @@ -61,9 +53,12 @@ export class Lwm2mObserveAttrTelemetryResourceComponent implements ControlValueA this.requiredValue = newVal; } } + constructor(private store: Store, private fb: FormBuilder) { - this.resourceFormGroup = this.fb.group({'resources': this.fb.array([])}); + this.resourceFormGroup = this.fb.group({ + resources: this.fb.array([]) + }); this.resourceFormGroup.valueChanges.subscribe(value => { if (!this.disabled) { this.propagateChangeState(value.resources); @@ -71,9 +66,6 @@ export class Lwm2mObserveAttrTelemetryResourceComponent implements ControlValueA }); } - ngOnInit(): void { - } - registerOnTouched(fn: any): void { } @@ -85,10 +77,6 @@ export class Lwm2mObserveAttrTelemetryResourceComponent implements ControlValueA return this.resourceFormGroup.get('resources') as FormArray; } - resourceLwm2mFormArray(instance: FormGroup): FormArray { - return instance.get('resources') as FormArray; - } - setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; if (isDisabled) { @@ -98,31 +86,16 @@ export class Lwm2mObserveAttrTelemetryResourceComponent implements ControlValueA } } - getDisabledState(): boolean { - return this.disabled; - } - - updateValueKeyName (event: any, z: number): void { - this.resourceFormArray.at(z).patchValue( {keyName: this.keysToCamel(deepClone(event.target.value))} ); - } - - keysToCamel(o: any): string { - let val = o.split(" "); - let playStore = []; - val.forEach(function (item, k){ - item = item.replace(CAMEL_CASE_REGEXP, ''); - item = (k===0)? item.charAt(0).toLowerCase() + item.substr(1) : item.charAt(0).toUpperCase() + item.substr(1) - playStore.push(item); - }); - return playStore.join(''); + updateValueKeyName = (event: Event, index: number): void => { + this.resourceFormArray.at(index).patchValue({keyName: _.camelCase((event.target as HTMLInputElement).value)}); } - createResourceLwM2M(resourcesLwM2MJson: ResourceLwM2M []): void { - if(resourcesLwM2MJson.length === this.resourceFormArray.length) { - this.resourceFormArray.patchValue(resourcesLwM2MJson, {emitEvent: false}) + createResourceLwM2M(resourcesLwM2M: ResourceLwM2M[]): void { + if (resourcesLwM2M.length === this.resourceFormArray.length) { + this.resourceFormArray.patchValue(resourcesLwM2M, {emitEvent: false}); } else { this.resourceFormArray.clear(); - resourcesLwM2MJson.forEach(resourceLwM2M => { + resourcesLwM2M.forEach(resourceLwM2M => { this.resourceFormArray.push(this.fb.group({ id: resourceLwM2M.id, name: resourceLwM2M.name, @@ -131,18 +104,17 @@ export class Lwm2mObserveAttrTelemetryResourceComponent implements ControlValueA telemetry: resourceLwM2M.telemetry, keyName: [resourceLwM2M.keyName, Validators.required] })); - }) + }); } } - private propagateChange = (v: any) => { - }; + private propagateChange = (v: any) => { }; registerOnChange(fn: any): void { this.propagateChange = fn; } - private propagateChangeState(value: any): void { + private propagateChangeState = (value: any): void => { if (value && this.resourceFormGroup.valid) { this.propagateChange(value); } else { @@ -150,7 +122,7 @@ export class Lwm2mObserveAttrTelemetryResourceComponent implements ControlValueA } } - trackByParams(index: number): number { + trackByParams = (index: number): number => { return index; } } diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.html b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.html index a8cad71b7b..22f21ce9b7 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.html @@ -15,26 +15,27 @@ limitations under the License. --> -
+
- -
{{ objectLwM2M.get('name').value}} (object [{{ objectLwM2M.get('id').value}}])
- + + {{ objectLwM2M.get('name').value}} <id: {{ objectLwM2M.get('id').value}}> + + +
-
- + +
-
- - -
+
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.ts index 3705c9c0f7..2419e007c2 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.ts @@ -15,7 +15,7 @@ /// -import { Component, forwardRef, Input, OnInit, Output } from '@angular/core'; +import { Component, forwardRef, Input } from '@angular/core'; import { AbstractControl, ControlValueAccessor, @@ -28,23 +28,14 @@ import { import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; -import { - ATTR, - Instance, - ObjectLwM2M, - OBSERVE, - ResourceLwM2M, - TELEMETRY -} from './profile-config.models'; -import { isNotNullOrUndefined } from 'codelyzer/util/isNotNullOrUndefined'; -import { deepClone, isUndefined } from '@core/utils'; +import { Instance, ObjectLwM2M, ResourceLwM2M } from './profile-config.models'; +import { deepClone, isDefinedAndNotNull, isEqual, isUndefined } from '@core/utils'; import { MatDialog } from '@angular/material/dialog'; import { TranslateService } from '@ngx-translate/core'; import { Lwm2mObjectAddInstancesComponent, Lwm2mObjectAddInstancesData } from '@home/components/profile/device/lwm2m/lwm2m-object-add-instances.component'; -import { Control } from 'leaflet'; @Component({ selector: 'tb-profile-lwm2m-observe-attr-telemetry', @@ -59,22 +50,17 @@ import { Control } from 'leaflet'; ] }) -export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor, OnInit, Validators { +export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor { + + private requiredValue: boolean; valuePrev = null as any; observeAttrTelemetryFormGroup: FormGroup; - observe = OBSERVE as string; - attribute = ATTR as string; - telemetry = TELEMETRY as string; - private requiredValue: boolean; get required(): boolean { return this.requiredValue; } - @Input() - disabled: boolean; - @Input() set required(value: boolean) { const newVal = coerceBooleanProperty(value); @@ -84,10 +70,13 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor, } } + @Input() + disabled: boolean; + constructor(private store: Store, private fb: FormBuilder, private dialog: MatDialog, - private translate: TranslateService) { + public translate: TranslateService) { this.observeAttrTelemetryFormGroup = this.fb.group({ clientLwM2M: this.fb.array([]) }); @@ -98,22 +87,17 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor, }); } - ngOnInit(): void { - } - - private propagateChange = (v: any) => { - }; - + private propagateChange = (v: any) => { }; registerOnChange(fn: any): void { this.propagateChange = fn; } - private propagateChangeState(value: any): void { + private propagateChangeState = (value: any): void => { if (value) { if (this.valuePrev === null) { - this.valuePrev = "init"; - } else if (this.valuePrev === "init") { + this.valuePrev = 'init'; + } else if (this.valuePrev === 'init') { this.valuePrev = value; } else if (JSON.stringify(value) !== JSON.stringify(this.valuePrev)) { this.valuePrev = value; @@ -139,92 +123,78 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor, } } - getDisabledState(): boolean { - return this.disabled; - } - writeValue(value: any): void { - this.buildClientObjectsLwM2M(value.clientLwM2M); + if (isDefinedAndNotNull(value)) { + this.buildClientObjectsLwM2M(value.clientLwM2M); + } } - private buildClientObjectsLwM2M(objectsLwM2M: ObjectLwM2M []): void { + private buildClientObjectsLwM2M = (objectsLwM2M: ObjectLwM2M []): void => { this.observeAttrTelemetryFormGroup.setControl('clientLwM2M', this.createObjectsLwM2M(objectsLwM2M) ); } - createObjectsLwM2M(objectsLwM2MJson: ObjectLwM2M []): FormArray { - return this.fb.array(objectsLwM2MJson.map((objectLwM2M) => { + private createObjectsLwM2M = (objectsLwM2M: ObjectLwM2M[]): FormArray => { + return this.fb.array(objectsLwM2M.map((objectLwM2M) => { return this.fb.group({ id: objectLwM2M.id, name: objectLwM2M.name, multiple: objectLwM2M.multiple, mandatory: objectLwM2M.mandatory, instances: this.createInstanceLwM2M(objectLwM2M.instances) - }) - })) + }); + })); } - createInstanceLwM2M(instanceLwM2MJson: Instance []): FormArray { - return this.fb.array(instanceLwM2MJson.map((instanceLwM2M, index) => { + private createInstanceLwM2M = (instancesLwM2M: Instance[]): FormArray => { + return this.fb.array(instancesLwM2M.map((instanceLwM2M) => { return this.fb.group({ id: instanceLwM2M.id, - [this.observe]: {value: false, disabled: this.disabled}, - [this.attribute]: {value: false, disabled: this.disabled}, - [this.telemetry]: {value: false, disabled: this.disabled}, resources: {value: instanceLwM2M.resources, disabled: this.disabled} - }) - })) + }); + })); } - clientLwM2MFormArray(formGroup: FormGroup): FormArray { - return formGroup.get('clientLwM2M') as FormArray; + get clientLwM2MFormArray(): FormArray { + return this.observeAttrTelemetryFormGroup.get('clientLwM2M') as FormArray; } - instancesLwm2mFormArray(objectLwM2M: AbstractControl): FormArray { + instancesLwm2mFormArray = (objectLwM2M: AbstractControl): FormArray => { return objectLwM2M.get('instances') as FormArray; } - changeInstanceResourcesCheckBox(value: boolean, instance: AbstractControl, type: string): void { - let resources = instance.get('resources').value as ResourceLwM2M [] + changeInstanceResourcesCheckBox = (value: boolean, instance: AbstractControl, type: string): void => { + const resources = deepClone(instance.get('resources').value as ResourceLwM2M[]); resources.forEach(resource => resource[type] = value); instance.get('resources').patchValue(resources); this.propagateChange(this.observeAttrTelemetryFormGroup.value); } - updateValidators() { - this.observeAttrTelemetryFormGroup.get('clientLwM2M').setValidators(this.required ? [Validators.required] : []); + private updateValidators = (): void => { + this.observeAttrTelemetryFormGroup.get('clientLwM2M').setValidators(this.required ? Validators.required : []); this.observeAttrTelemetryFormGroup.get('clientLwM2M').updateValueAndValidity(); } - trackByParams(index: number): number { + trackByParams = (index: number, element: any): number => { return index; } - getIndeterminate(instance: AbstractControl, type: string) { - const resources = instance.get('resources').value as ResourceLwM2M []; - if (isNotNullOrUndefined(resources)) { - const isType = (element) => element[type] === true; - let checkedResource = resources.filter(isType); - if (checkedResource.length === 0) return false; - else if (checkedResource.length === resources.length) { - instance.patchValue({[type]: true}); - return false; - } else return true; + getIndeterminate = (instance: AbstractControl, type: string): boolean => { + const resources = instance.get('resources').value as ResourceLwM2M[]; + if (isDefinedAndNotNull(resources)) { + const checkedResource = resources.filter(resource => resource[type]); + return checkedResource.length !== 0 && checkedResource.length !== resources.length; } return false; } - - getChecked(instance: AbstractControl, type: string) { - const resources = instance.get('resources').value as ResourceLwM2M []; - if (isNotNullOrUndefined(resources)) { - return resources.some(resource => resource[type]); - } - return false; + getChecked = (instance: AbstractControl, type: string): boolean => { + const resources = instance.get('resources').value as ResourceLwM2M[]; + return isDefinedAndNotNull(resources) && resources.every(resource => resource[type]); } - getExpended(objectLwM2M: AbstractControl) { + getExpended = (objectLwM2M: AbstractControl): boolean => { return this.instancesLwm2mFormArray(objectLwM2M).length === 1; } @@ -234,119 +204,96 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor, * 2) Field in object: == Mandatory/Optional * If this field is “Multiple” then the number of Object Instance can be from 0 to many (Object Instance ID MAX_ID=65535). * If this field is “Single” then the number of Object Instance can be from 0 to 1. (max count == 1) - * If the Object field “Mandatory” is “Mandatory” and the Object field “Instances” is “Single” then, the number of Object Instance MUST be 1. - * 1. == Multiple (true), == Optional (false) => Object Instance ID MIN_ID=0 MAX_ID=65535 (может ни одного не быть) - * 2. == Multiple (true), == Mandatory (true) => Object Instance ID MIN_ID=0 MAX_ID=65535 (min один обязательный) - * 3. == Single (false), == Optional (false) => Object Instance ID cnt_max = 1 cnt_min = 0 (может ни одного не быть) - * 4. == Single (false), == Mandatory (true) => Object Instance ID cnt_max = cnt_min = 1 (всегда есть один) - * @param $event - * @param objectId - * @param objectName + * If the Object field “Mandatory” is “Mandatory” and the Object field “Instances” is “Single” then - + * the number of Object Instance MUST be 1. + * 1. == Multiple (true), == Optional (false) - + * Object Instance ID MIN_ID=0 MAX_ID=65535 (может ни одного не быть) + * 2. == Multiple (true), == Mandatory (true) - + * Object Instance ID MIN_ID=0 MAX_ID=65535 (min один обязательный) + * 3. == Single (false), == Optional (false) - + * Object Instance ID cnt_max = 1 cnt_min = 0 (может ни одного не быть) + * 4. == Single (false), == Mandatory (true) - + * Object Instance ID cnt_max = cnt_min = 1 (всегда есть один) */ - addInstances($event: Event, object: ObjectLwM2M): void { + addInstances = ($event: Event, object: ObjectLwM2M): void => { if ($event) { $event.stopPropagation(); $event.preventDefault(); } - let instancesIds = new Set(); - object.instances.forEach(inst => { - instancesIds.add(inst.id); - }); this.dialog.open(Lwm2mObjectAddInstancesComponent, { disableClose: true, panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], data: { - instancesIds: this.setInstancesIds(object.instances), + instancesIds: this.instancesToSetId(object.instances), objectName: object.name, objectId: object.id } }).afterClosed().subscribe( (res: Lwm2mObjectAddInstancesData | undefined) => { - if (isNotNullOrUndefined(res)) { + if (isDefinedAndNotNull(res)) { this.updateInstancesIds(res); } } ); } - updateInstancesIds(data: Lwm2mObjectAddInstancesData) { - let valueNew = new Set(); - let valueOld = new Set(); - data.instancesIds.forEach(value => { - valueNew.add(value); - }) - let oldInstances = (this.observeAttrTelemetryFormGroup.get('clientLwM2M').value as ObjectLwM2M []).find(e => e.id == data.objectId).instances; - oldInstances.forEach(inst => { - valueOld.add(inst.id); + private updateInstancesIds = (data: Lwm2mObjectAddInstancesData): void => { + const objectLwM2MFormGroup = (this.observeAttrTelemetryFormGroup.get('clientLwM2M') as FormArray).controls + .find(e => e.value.id === data.objectId) as FormGroup; + const instancesArray = objectLwM2MFormGroup.value.instances as Instance []; + const instancesFormArray = objectLwM2MFormGroup.get('instances') as FormArray; + const instance0 = deepClone(instancesFormArray.at(0).value as Instance); + instance0.resources.forEach(r => { + r.attribute = false; + r.telemetry = false; + r.observe = false; }); - if (JSON.stringify(Array.from(valueOld)) != JSON.stringify(Array.from(valueNew))) { - let idsDel = this.diffBetweenSet(valueNew, this.deepCloneSet(valueOld)); - let idsAdd = this.diffBetweenSet(valueOld, this.deepCloneSet(valueNew)); + const valueOld = this.instancesToSetId(instancesArray); + if (!isEqual(valueOld, data.instancesIds)) { + const idsDel = this.diffBetweenSet(valueOld, data.instancesIds); + const idsAdd = this.diffBetweenSet(data.instancesIds, valueOld); if (idsAdd.size) { - this.addInstancesNew(data.objectId, idsAdd) + this.addInstancesNew(idsAdd, objectLwM2MFormGroup, instancesFormArray, instance0); } if (idsDel.size) { - this.delInstances(data.objectId, idsDel); + this.deleteInstances(idsDel, objectLwM2MFormGroup, instancesFormArray, instance0); } } } - delInstances(objectId: number, idsDel: Set): void { - let isIdIndex = (element) => element.id == objectId; - let objectIndex = (this.observeAttrTelemetryFormGroup.get('clientLwM2M').value as ObjectLwM2M []).findIndex(isIdIndex); - idsDel.forEach(x => { - isIdIndex = (element) => element.value.id == x; - let instancesFormArray = ((this.observeAttrTelemetryFormGroup.get('clientLwM2M') as FormArray).controls[objectIndex].get("instances") as FormArray); - let instanceIndex = instancesFormArray.controls.findIndex(isIdIndex); - instancesFormArray.removeAt(instanceIndex); - }) + private addInstancesNew = (idsAdd: Set, objectLwM2MFormGroup: FormGroup, instancesFormArray: FormArray, + instanceNew: Instance): void => { + idsAdd.forEach(x => { + this.pushInstance(instancesFormArray, x, instanceNew); + }); + (instancesFormArray.controls as FormGroup[]).sort((a, b) => a.value.id - b.value.id); } - addInstancesNew(objectId: number, idsAdd: Set): void { - let instancesValue = (this.observeAttrTelemetryFormGroup.get('clientLwM2M').value as ObjectLwM2M []).find(e => e.id == objectId).instances; - let instancesFormArray = ((this.observeAttrTelemetryFormGroup.get('clientLwM2M') as FormArray).controls.find(e => e.value.id == objectId).get("instances") as FormArray) as FormArray; - idsAdd.forEach(x => { - let instanceNew = deepClone(instancesValue[0]) as Instance; - instanceNew.id = x; - instanceNew.resources.forEach(r => { - r.attribute = false; - r.telemetry = false; - r.observe = false; - }); - instancesFormArray.push(this.fb.group({ - id: x, - [this.observe]: {value: false, disabled: this.disabled}, - [this.attribute]: {value: false, disabled: this.disabled}, - [this.telemetry]: {value: false, disabled: this.disabled}, - resources: {value: instanceNew.resources, disabled: this.disabled} - })); + private deleteInstances = (idsDel: Set, objectLwM2MFormGroup: FormGroup, instancesFormArray: FormArray, + instance0: Instance): void => { + idsDel.forEach(x => { + const instanceIndex = instancesFormArray.value.findIndex(element => element.id === x); + instancesFormArray.removeAt(instanceIndex); }); - (instancesFormArray.controls as FormGroup[]).sort((a,b) => a.value.id - b.value.id); + if (instancesFormArray.length === 0) { + this.pushInstance(instancesFormArray, 0, instance0); + } + (instancesFormArray.controls as FormGroup[]).sort((a, b) => a.value.id - b.value.id); } - deepCloneSet(oldSet: Set): Set { - let newSet = new Set(); - oldSet.forEach(p => { - newSet.add(p); - }) - return newSet; + private pushInstance = (instancesFormArray: FormArray, x: number, instanceNew: Instance): void => { + instancesFormArray.push(this.fb.group({ + id: x, + resources: {value: instanceNew.resources, disabled: this.disabled} + })); } - diffBetweenSet(firstSet: Set, secondSet: Set): Set { - firstSet.forEach(p => { - secondSet.delete(p); - }) - return secondSet + private diffBetweenSet(firstSet: Set, secondSet: Set): Set { + return new Set([...Array.from(firstSet)].filter(x => !secondSet.has(x))); } - setInstancesIds(instances: Instance []): Set { - let instancesIds = new Set(); - if (instances && instances.length) { - instances.forEach(inst => { - instancesIds.add(inst.id); - }); - } - return instancesIds; + private instancesToSetId = (instances: Instance[]): Set => { + return new Set(instances.map(x => x.id)); } } diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/profile-config.models.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/profile-config.models.ts index cc88dcad16..49ed272821 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/profile-config.models.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/profile-config.models.ts @@ -14,8 +14,6 @@ /// limitations under the License. /// -import { AbstractControl, ValidationErrors } from '@angular/forms'; - export const OBSERVE_ATTR = 'observeAttr'; export const OBSERVE = 'observe'; export const ATTR = 'attribute'; @@ -23,37 +21,29 @@ export const TELEMETRY = 'telemetry'; export const KEY_NAME = 'keyName'; export const DEFAULT_ID_SERVER = 123; export const DEFAULT_ID_BOOTSTRAP = 111; -export const DEFAULT_HOST_NAME = "localhost"; +export const DEFAULT_HOST_NAME = 'localhost'; export const DEFAULT_PORT_SERVER_NO_SEC = 5685; -export const DEFAULT_PORT_SERVER_SEC = 5686; -export const DEFAULT_PORT_SERVER_SEC_CERT = 5688; -export const DEFAULT_PORT_BOOTSTRAP_NO_SEC = 5689; -export const DEFAULT_PORT_BOOTSTRAP_SEC = 5690; -export const DEFAULT_PORT_BOOTSTRAP_SEC_CERT = 5692; +export const DEFAULT_PORT_BOOTSTRAP_NO_SEC = 5691; export const DEFAULT_CLIENT_HOLD_OFF_TIME = 1; export const DEFAULT_LIFE_TIME = 300; -export const DEFAULT_DEFAULT_MIN_PERIOD = 1; +export const DEFAULT_MIN_PERIOD = 1; export const DEFAULT_NOTIF_IF_DESIBLED = true; -export const DEFAULT_BINDING = "U"; +export const DEFAULT_BINDING = 'U'; export const DEFAULT_BOOTSTRAP_SERVER_ACCOUNT_TIME_OUT = 0; -export const LEN_MAX_PUBLIC_KEY_PSK = 182; -export const LEN_MAX_PUBLIC_KEY_RPK_X509 = 3000; -export const KEY_IDENT_REGEXP_PSK = /^[0-9a-fA-F]{64,64}$/; -export const KEY_PRIVATE_REGEXP = /^[0-9a-fA-F]{134,134}$/; -export const KEY_PUBLIC_REGEXP_PSK = /^[0-9a-fA-F]{182,182}$/; -export const KEY_PUBLIC_REGEXP_X509 = /^[0-9a-fA-F]{0,3000}$/; -export const CAMEL_CASE_REGEXP = /[-_&@.,*+!?^${}()|[\]\\]/g; +export const LEN_MAX_PUBLIC_KEY_RPK = 182; +export const LEN_MAX_PUBLIC_KEY_X509 = 3000; +export const KEY_REGEXP_HEX_DEC = /^[-+]?[0-9A-Fa-f]+\.?[0-9A-Fa-f]*?$/; +export const KEY_REGEXP_NUMBER = /^(\-?|\+?)\d*$/; export const INSTANCES_ID_VALUE_MIN = 0; export const INSTANCES_ID_VALUE_MAX = 65535; -//ok export enum SECURITY_CONFIG_MODE { PSK = 'PSK', RPK = 'RPK', X509 = 'X509', NO_SEC = 'NO_SEC' } -//ok + export const SECURITY_CONFIG_MODE_NAMES = new Map( [ [SECURITY_CONFIG_MODE.PSK, 'Pre-Shared Key'], @@ -62,103 +52,88 @@ export const SECURITY_CONFIG_MODE_NAMES = new Map( [SECURITY_CONFIG_MODE.NO_SEC, 'No Security'], ] ); -//ok + export interface BootstrapServersSecurityConfig { - shortId: number, - lifetime: number, - defaultMinPeriod: number, - notifIfDisabled: boolean, - binding: string + shortId: number; + lifetime: number; + defaultMinPeriod: number; + notifIfDisabled: boolean; + binding: string; } -//ok export interface ServerSecurityConfig { - host?: string, - port?: number, - bootstrapServerIs?: boolean, - securityMode: string, - clientPublicKeyOrId?: string, - clientSecretKey?: string, + host?: string; + port?: number; + bootstrapServerIs?: boolean; + securityMode: string; + clientPublicKeyOrId?: string; + clientSecretKey?: string; serverPublicKey?: string; - clientHoldOffTime?: number, - serverId?: number, - bootstrapServerAccountTimeout: number + clientHoldOffTime?: number; + serverId?: number; + bootstrapServerAccountTimeout: number; } -//ok interface BootstrapSecurityConfig { - servers: BootstrapServersSecurityConfig, - bootstrapServer: ServerSecurityConfig, - lwm2mServer: ServerSecurityConfig + servers: BootstrapServersSecurityConfig; + bootstrapServer: ServerSecurityConfig; + lwm2mServer: ServerSecurityConfig; } -//ok export interface ProfileConfigModels { - bootstrap: BootstrapSecurityConfig, - observeAttr: { - observe: string [], - attribute: string [], - telemetry: string [], - keyName: [] - } + bootstrap: BootstrapSecurityConfig; + observeAttr: ObservableAttributes; +} + +export interface ObservableAttributes { + observe: string[]; + attribute: string[]; + telemetry: string[]; + keyName: string[]; } -//ok export function getDefaultBootstrapServersSecurityConfig(): BootstrapServersSecurityConfig { return { shortId: DEFAULT_ID_SERVER, lifetime: DEFAULT_LIFE_TIME, - defaultMinPeriod: DEFAULT_DEFAULT_MIN_PERIOD, + defaultMinPeriod: DEFAULT_MIN_PERIOD, notifIfDisabled: DEFAULT_NOTIF_IF_DESIBLED, binding: DEFAULT_BINDING - } + }; } -//ok export function getDefaultBootstrapServerSecurityConfig(hostname: any): ServerSecurityConfig { return { host: hostname, - port: getDefaultPortBootstrap(), + port: DEFAULT_PORT_BOOTSTRAP_NO_SEC, bootstrapServerIs: true, securityMode: SECURITY_CONFIG_MODE.NO_SEC.toString(), serverPublicKey: '', clientHoldOffTime: DEFAULT_CLIENT_HOLD_OFF_TIME, serverId: DEFAULT_ID_BOOTSTRAP, bootstrapServerAccountTimeout: DEFAULT_BOOTSTRAP_SERVER_ACCOUNT_TIME_OUT - } + }; } -//ok + export function getDefaultLwM2MServerSecurityConfig(hostname): ServerSecurityConfig { const DefaultLwM2MServerSecurityConfig = getDefaultBootstrapServerSecurityConfig(hostname); DefaultLwM2MServerSecurityConfig.bootstrapServerIs = false; - DefaultLwM2MServerSecurityConfig.port = getDefaultPortServer(); + DefaultLwM2MServerSecurityConfig.port = DEFAULT_PORT_SERVER_NO_SEC; DefaultLwM2MServerSecurityConfig.serverId = DEFAULT_ID_SERVER; return DefaultLwM2MServerSecurityConfig; } -//ok -export function getDefaultPortBootstrap(securityMode?: string): number { - return (!securityMode || securityMode === SECURITY_CONFIG_MODE.NO_SEC.toString()) ? DEFAULT_PORT_BOOTSTRAP_NO_SEC : - (securityMode === SECURITY_CONFIG_MODE.X509.toString()) ? DEFAULT_PORT_BOOTSTRAP_SEC_CERT : DEFAULT_PORT_BOOTSTRAP_SEC; -} -//ok -export function getDefaultPortServer(securityMode?: string): number { - return (!securityMode || securityMode === SECURITY_CONFIG_MODE.NO_SEC.toString()) ? DEFAULT_PORT_SERVER_NO_SEC : - (securityMode === SECURITY_CONFIG_MODE.X509.toString()) ? DEFAULT_PORT_SERVER_SEC_CERT : DEFAULT_PORT_SERVER_SEC; -} -//ok function getDefaultProfileBootstrapSecurityConfig(hostname: any): BootstrapSecurityConfig { return { servers: getDefaultBootstrapServersSecurityConfig(), bootstrapServer: getDefaultBootstrapServerSecurityConfig(hostname), lwm2mServer: getDefaultLwM2MServerSecurityConfig(hostname) - } + }; } -//ok export function getDefaultProfileConfig(hostname?: any): ProfileConfigModels { return { - bootstrap: getDefaultProfileBootstrapSecurityConfig((hostname)? hostname : DEFAULT_HOST_NAME), + bootstrap: getDefaultProfileBootstrapSecurityConfig((hostname) ? hostname : DEFAULT_HOST_NAME), observeAttr: { observe: [], attribute: [], @@ -168,19 +143,18 @@ export function getDefaultProfileConfig(hostname?: any): ProfileConfigModels { }; } -//ok export interface ResourceLwM2M { - id: number, - name: string, - observe: boolean, - attribute: boolean, - telemetry: boolean, - keyName: string + id: number; + name: string; + observe: boolean; + attribute: boolean; + telemetry: boolean; + keyName: string; } -//ok + export interface Instance { - id: number, - resources: ResourceLwM2M[] + id: number; + resources: ResourceLwM2M[]; } /** @@ -190,23 +164,10 @@ export interface Instance { * mandatory == false => Optional */ export interface ObjectLwM2M { - id: number, - name: string, - multiple?: boolean, - mandatory?: boolean, - instances?: Instance [] -} - -export function getChangeInstancesIds (): ChangeInstancesIds { - let changeInstancesIds: ChangeInstancesIds; - changeInstancesIds.add = new Set(); - changeInstancesIds.del = new Set(); - return changeInstancesIds; - -} - -export interface ChangeInstancesIds { - add: Set, - del: Set + id: number; + name: string; + multiple?: boolean; + mandatory?: boolean; + instances?: Instance []; } diff --git a/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html index 1cfcbd7dd0..d8f25c0321 100644 --- a/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html @@ -136,6 +136,41 @@
+ + + {{ 'widget-action.open-in-separate-dialog' | translate }} + +
+ + widget-action.dialog-title + + + + {{ 'widget-action.dialog-hide-dashboard-toolbar' | translate }} + + + widget-action.dialog-width + + + {{ 'widget-action.dialog-size-range-error' | translate }} + + + {{ 'widget-action.dialog-size-range-error' | translate }} + + + + widget-action.dialog-height + + + {{ 'widget-action.dialog-size-range-error' | translate }} + + + {{ 'widget-action.dialog-size-range-error' | translate }} + + +
+
> = + new InjectionToken>('EMBED_DASHBOARD_DIALOG_TOKEN'); + diff --git a/ui-ngx/src/app/modules/home/components/widget/dialog/embed-dashboard-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/dialog/embed-dashboard-dialog.component.html new file mode 100644 index 0000000000..04bc5708b6 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/dialog/embed-dashboard-dialog.component.html @@ -0,0 +1,40 @@ + +
+ +

{{title}}

+ + +
+ + +
+
+ +
+
+ +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/dialog/embed-dashboard-dialog.component.scss b/ui-ngx/src/app/modules/home/components/widget/dialog/embed-dashboard-dialog.component.scss new file mode 100644 index 0000000000..b13379a744 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/dialog/embed-dashboard-dialog.component.scss @@ -0,0 +1,42 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host { + .dashboard-state-dialog { + .mat-dialog-content.dashboard-state-dialog-content { + max-height: 100%; + } + + @media screen and (max-width: 599px) { + width: 100% !important; + height: 100% !important; + } + + @media screen and (min-width: 600px) { + width: 480px; + height: 600px; + } + + @media screen and (min-width: 960px) { + width: 768px; + height: 600px; + } + + @media screen and (min-width: 1280px) { + width: 1000px; + height: 800px; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/dialog/embed-dashboard-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/dialog/embed-dashboard-dialog.component.ts new file mode 100644 index 0000000000..0d12f3c22c --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/dialog/embed-dashboard-dialog.component.ts @@ -0,0 +1,79 @@ +/// +/// Copyright © 2016-2021 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + Component, + ComponentFactoryResolver, + Inject, + Injector, + OnInit, + ViewChild, + ViewContainerRef +} from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Router } from '@angular/router'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Dashboard } from '@shared/models/dashboard.models'; + +export interface EmbedDashboardDialogData { + dashboard: Dashboard; + state: string; + title: string; + hideToolbar: boolean; + width?: number; + height?: number; +} + +@Component({ + selector: 'tb-embed-dashboard-dialog', + templateUrl: './embed-dashboard-dialog.component.html', + styleUrls: ['./embed-dashboard-dialog.component.scss'] +}) +export class EmbedDashboardDialogComponent extends DialogComponent + implements OnInit { + + @ViewChild('dashboardContent', {read: ViewContainerRef, static: true}) dashboardContentContainer: ViewContainerRef; + + dashboard = this.data.dashboard; + state = this.data.state; + title = this.data.title; + hideToolbar = this.data.hideToolbar; + + dialogStyle: any = {}; + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: EmbedDashboardDialogData, + public dialogRef: MatDialogRef) { + super(store, router, dialogRef); + if (this.data.width) { + this.dialogStyle.width = this.data.width + 'vw'; + } + if (this.data.height) { + this.dialogStyle.height = this.data.height + 'vh'; + } + } + + ngOnInit(): void { + } + + close(): void { + this.dialogRef.close(null); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-form.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-form.component.html index 8b62ab54b7..e0b30518b7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-form.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-form.component.html @@ -35,7 +35,7 @@ required > -
profile-tab +
{{'gateway.security-type' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts index ef5555e5eb..7838f7e7ba 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts @@ -22,6 +22,7 @@ import { ComponentFactoryResolver, ComponentRef, ElementRef, + Inject, Injector, Input, NgZone, @@ -54,7 +55,7 @@ import { AppState } from '@core/core.state'; import { WidgetService } from '@core/http/widget.service'; import { UtilsService } from '@core/services/utils.service'; import { forkJoin, Observable, of, ReplaySubject, Subscription, throwError } from 'rxjs'; -import { deepClone, isDefined, objToBase64URI } from '@core/utils'; +import { deepClone, insertVariable, isDefined, objToBase64, objToBase64URI } from '@core/utils'; import { IDynamicWidgetComponent, WidgetContext, @@ -93,6 +94,9 @@ import { EntityDataService } from '@core/api/entity-data.service'; import { TranslateService } from '@ngx-translate/core'; import { NotificationType } from '@core/notification/notification.models'; import { AlarmDataService } from '@core/api/alarm-data.service'; +import { MatDialog } from '@angular/material/dialog'; +import { ComponentType } from '@angular/cdk/portal'; +import { EMBED_DASHBOARD_DIALOG_TOKEN } from '@home/components/widget/dialog/embed-dashboard-dialog-token'; @Component({ selector: 'tb-widget', @@ -161,6 +165,8 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI private componentFactoryResolver: ComponentFactoryResolver, private elementRef: ElementRef, private injector: Injector, + private dialog: MatDialog, + @Inject(EMBED_DASHBOARD_DIALOG_TOKEN) private embedDashboardDialogComponent: ComponentType, private widgetService: WidgetService, private resources: ResourcesService, private timeService: TimeService, @@ -1007,7 +1013,11 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI const params = deepClone(this.widgetContext.stateController.getStateParams()); this.updateEntityParams(params, targetEntityParamName, targetEntityId, entityName, entityLabel); if (type === WidgetActionType.openDashboardState) { - this.widgetContext.stateController.openState(targetDashboardStateId, params, descriptor.openRightLayout); + if (descriptor.openInSeparateDialog) { + this.openDashboardStateInDialog(descriptor, entityId, entityName, additionalParams, entityLabel); + } else { + this.widgetContext.stateController.openState(targetDashboardStateId, params, descriptor.openRightLayout); + } } else { this.widgetContext.stateController.updateState(targetDashboardStateId, params, descriptor.openRightLayout); } @@ -1083,6 +1093,61 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI } } + private openDashboardStateInDialog(descriptor: WidgetActionDescriptor, + entityId?: EntityId, entityName?: string, additionalParams?: any, entityLabel?: string) { + const dashboard = deepClone(this.widgetContext.stateController.dashboardCtrl.dashboardCtx.getDashboard()); + const stateObject: StateObject = {}; + stateObject.params = {}; + const targetEntityParamName = descriptor.stateEntityParamName; + const targetDashboardStateId = descriptor.targetDashboardStateId; + let targetEntityId: EntityId; + if (descriptor.setEntityId) { + targetEntityId = entityId; + } + this.updateEntityParams(stateObject.params, targetEntityParamName, targetEntityId, entityName, entityLabel); + if (targetDashboardStateId) { + stateObject.id = targetDashboardStateId; + } + let title = descriptor.dialogTitle; + if (!title) { + if (targetDashboardStateId && dashboard.configuration.states) { + const dashboardState = dashboard.configuration.states[targetDashboardStateId]; + if (dashboardState) { + title = dashboardState.name; + } + } + } + if (!title) { + title = dashboard.title; + } + title = this.utils.customTranslation(title, title); + const params = stateObject.params; + const paramsEntityName = params && params.entityName ? params.entityName : ''; + const paramsEntityLabel = params && params.entityLabel ? params.entityLabel : ''; + title = insertVariable(title, 'entityName', paramsEntityName); + title = insertVariable(title, 'entityLabel', paramsEntityLabel); + for (const prop of Object.keys(params)) { + if (params[prop] && params[prop].entityName) { + title = insertVariable(title, prop + ':entityName', params[prop].entityName); + } + if (params[prop] && params[prop].entityLabel) { + title = insertVariable(title, prop + ':entityLabel', params[prop].entityLabel); + } + } + this.dialog.open(this.embedDashboardDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + dashboard, + state: objToBase64([ stateObject ]), + title, + hideToolbar: descriptor.dialogHideDashboardToolbar, + width: descriptor.dialogWidth, + height: descriptor.dialogHeight + } + }); + } + private elementClick($event: Event) { const e = ($event.target || $event.srcElement) as Element; if (e.id) { diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html index fbebf66d4c..245cb6503b 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html @@ -108,9 +108,15 @@
- - {{ 'device.is-gateway' | translate }} - +
+ + {{ 'device.is-gateway' | translate }} + + + {{ 'device.overwrite-activity-time' | translate }} + +
device.description diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts index 66f115af93..1cba31aa24 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts @@ -39,8 +39,8 @@ import { BaseData, HasId } from '@shared/models/base-data'; import { EntityType } from '@shared/models/entity-type.models'; import { DeviceProfileService } from '@core/http/device-profile.service'; import { EntityId } from '@shared/models/id/entity-id'; -import { Observable, of, Subscription } from 'rxjs'; -import { map, mergeMap, tap } from 'rxjs/operators'; +import { Observable, of, Subscription, throwError } from 'rxjs'; +import { catchError, map, mergeMap, tap } from 'rxjs/operators'; import { DeviceService } from '@core/http/device.service'; import { ErrorStateMatcher } from '@angular/material/core'; import { StepperSelectionEvent } from '@angular/cdk/stepper'; @@ -108,6 +108,7 @@ export class DeviceWizardDialogComponent extends name: ['', Validators.required], label: [''], gateway: [false], + overwriteActivityTime: [false], transportType: [DeviceTransportType.DEFAULT, Validators.required], addProfileType: [0], deviceProfileId: [null, Validators.required], @@ -314,6 +315,7 @@ export class DeviceWizardDialogComponent extends deviceProfileId: profileId, additionalInfo: { gateway: this.deviceWizardFormGroup.get('gateway').value, + overwriteActivityTime: this.deviceWizardFormGroup.get('overwriteActivityTime').value, description: this.deviceWizardFormGroup.get('description').value }, customerId: null @@ -333,7 +335,15 @@ export class DeviceWizardDialogComponent extends mergeMap( (deviceCredentials) => { const deviceCredentialsValue = {...deviceCredentials, ...this.credentialsFormGroup.value.credential}; - return this.deviceService.saveDeviceCredentials(deviceCredentialsValue); + return this.deviceService.saveDeviceCredentials(deviceCredentialsValue).pipe( + catchError(e => { + return this.deviceService.deleteDevice(device.id.id).pipe( + mergeMap(() => { + return throwError(e); + } + )); + }) + ); } ), map(() => true)); diff --git a/ui-ngx/src/app/modules/home/models/services.map.ts b/ui-ngx/src/app/modules/home/models/services.map.ts index 9e33301ed7..64e94f24df 100644 --- a/ui-ngx/src/app/modules/home/models/services.map.ts +++ b/ui-ngx/src/app/modules/home/models/services.map.ts @@ -32,7 +32,8 @@ import { DashboardService } from '@core/http/dashboard.service'; import { UserService } from '@core/http/user.service'; import { AlarmService } from '@core/http/alarm.service'; import { Router } from '@angular/router'; -import { BroadcastService } from "@core/services/broadcast.service"; +import { BroadcastService } from '@core/services/broadcast.service'; +import { ImportExportService } from '@home/components/import-export/import-export.service'; export const ServicesMap = new Map>( [ @@ -53,6 +54,7 @@ export const ServicesMap = new Map>( ['utils', UtilsService], ['translate', TranslateService], ['http', HttpClient], - ['router', Router] + ['router', Router], + ['importExport', ImportExportService] ] ); diff --git a/ui-ngx/src/app/modules/home/pages/api-usage/api-usage.module.ts b/ui-ngx/src/app/modules/home/pages/api-usage/api-usage.module.ts index a95b396d92..47bf4bd0d2 100644 --- a/ui-ngx/src/app/modules/home/pages/api-usage/api-usage.module.ts +++ b/ui-ngx/src/app/modules/home/pages/api-usage/api-usage.module.ts @@ -19,7 +19,6 @@ import { CommonModule } from '@angular/common'; import { SharedModule } from '@app/shared/shared.module'; import { HomeComponentsModule } from '@modules/home/components/home-components.module'; import { ApiUsageComponent } from '@home/pages/api-usage/api-usage.component'; -import { DashboardModule } from '@home/pages/dashboard/dashboard.module'; import { ApiUsageRoutingModule } from '@home/pages/api-usage/api-usage-routing.module'; @NgModule({ @@ -31,7 +30,6 @@ import { ApiUsageRoutingModule } from '@home/pages/api-usage/api-usage-routing.m CommonModule, SharedModule, HomeComponentsModule, - DashboardModule, ApiUsageRoutingModule ] }) diff --git a/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts b/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts index 4907a70ea3..f5f3ee05fa 100644 --- a/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts @@ -24,7 +24,7 @@ import { CustomersTableConfigResolver } from './customers-table-config.resolver' import { DevicesTableConfigResolver } from '@modules/home/pages/device/devices-table-config.resolver'; import { AssetsTableConfigResolver } from '../asset/assets-table-config.resolver'; import { DashboardsTableConfigResolver } from '@modules/home/pages/dashboard/dashboards-table-config.resolver'; -import { DashboardPageComponent } from '@home/pages/dashboard/dashboard-page.component'; +import { DashboardPageComponent } from '@home/components/dashboard-page/dashboard-page.component'; import { BreadCrumbConfig } from '@shared/components/breadcrumb'; import { dashboardBreadcumbLabelFunction, DashboardResolver } from '@home/pages/dashboard/dashboard-routing.module'; import { EdgesTableConfigResolver } from '@home/pages/edge/edges-table-config.resolver'; diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-routing.module.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-routing.module.ts index 7cf02aea32..55c1fc0d28 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-routing.module.ts @@ -20,7 +20,7 @@ import { ActivatedRouteSnapshot, Resolve, RouterModule, Routes } from '@angular/ import { EntitiesTableComponent } from '../../components/entity/entities-table.component'; import { Authority } from '@shared/models/authority.enum'; import { DashboardsTableConfigResolver } from './dashboards-table-config.resolver'; -import { DashboardPageComponent } from '@home/pages/dashboard/dashboard-page.component'; +import { DashboardPageComponent } from '@home/components/dashboard-page/dashboard-page.component'; import { BreadCrumbConfig, BreadCrumbLabelFunction } from '@shared/components/breadcrumb'; import { Observable } from 'rxjs'; import { Dashboard } from '@app/shared/models/dashboard.models'; diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts index 9b4e8690d7..0c1e7887f0 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts @@ -24,44 +24,19 @@ import { DashboardRoutingModule } from './dashboard-routing.module'; import { MakeDashboardPublicDialogComponent } from '@modules/home/pages/dashboard/make-dashboard-public-dialog.component'; import { HomeComponentsModule } from '@modules/home/components/home-components.module'; import { DashboardTabsComponent } from '@home/pages/dashboard/dashboard-tabs.component'; -import { DashboardPageComponent } from '@home/pages/dashboard/dashboard-page.component'; -import { DashboardToolbarComponent } from './dashboard-toolbar.component'; -import { StatesControllerModule } from '@home/pages/dashboard/states/states-controller.module'; -import { DashboardLayoutComponent } from './layout/dashboard-layout.component'; -import { EditWidgetComponent } from './edit-widget.component'; -import { DashboardWidgetSelectComponent } from './dashboard-widget-select.component'; -import { AddWidgetDialogComponent } from './add-widget-dialog.component'; -import { ManageDashboardLayoutsDialogComponent } from './layout/manage-dashboard-layouts-dialog.component'; -import { DashboardSettingsDialogComponent } from './dashboard-settings-dialog.component'; -import { ManageDashboardStatesDialogComponent } from './states/manage-dashboard-states-dialog.component'; -import { DashboardStateDialogComponent } from './states/dashboard-state-dialog.component'; @NgModule({ declarations: [ DashboardFormComponent, DashboardTabsComponent, ManageDashboardCustomersDialogComponent, - MakeDashboardPublicDialogComponent, - DashboardToolbarComponent, - DashboardPageComponent, - DashboardLayoutComponent, - EditWidgetComponent, - DashboardWidgetSelectComponent, - AddWidgetDialogComponent, - ManageDashboardLayoutsDialogComponent, - DashboardSettingsDialogComponent, - ManageDashboardStatesDialogComponent, - DashboardStateDialogComponent - ], - exports: [ - DashboardPageComponent + MakeDashboardPublicDialogComponent ], imports: [ CommonModule, SharedModule, HomeComponentsModule, HomeDialogsModule, - StatesControllerModule, DashboardRoutingModule ] }) diff --git a/ui-ngx/src/app/modules/home/pages/device/device.component.html b/ui-ngx/src/app/modules/home/pages/device/device.component.html index 400893fbc5..e2202c33ad 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/device.component.html @@ -106,9 +106,15 @@ required>
- - {{ 'device.is-gateway' | translate }} - +
+ + {{ 'device.is-gateway' | translate }} + + + {{ 'device.overwrite-activity-time' | translate }} + +
device.description diff --git a/ui-ngx/src/app/modules/home/pages/device/device.component.ts b/ui-ngx/src/app/modules/home/pages/device/device.component.ts index 309049f0d3..783491ba87 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device/device.component.ts @@ -84,6 +84,7 @@ export class DeviceComponent extends EntityComponent { additionalInfo: this.fb.group( { gateway: [entity && entity.additionalInfo ? entity.additionalInfo.gateway : false], + overwriteActivityTime: [entity && entity.additionalInfo ? entity.additionalInfo.overwriteActivityTime : false], description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''], } ) @@ -96,8 +97,13 @@ export class DeviceComponent extends EntityComponent { this.entityForm.patchValue({deviceProfileId: entity.deviceProfileId}); this.entityForm.patchValue({label: entity.label}); this.entityForm.patchValue({deviceData: entity.deviceData}); - this.entityForm.patchValue({additionalInfo: - {gateway: entity.additionalInfo ? entity.additionalInfo.gateway : false}}); + this.entityForm.patchValue({ + additionalInfo: + { + gateway: entity.additionalInfo ? entity.additionalInfo.gateway : false, + overwriteActivityTime: entity.additionalInfo ? entity.additionalInfo.overwriteActivityTime : false + } + }); this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}}); } diff --git a/ui-ngx/src/app/modules/home/pages/device/device.module.ts b/ui-ngx/src/app/modules/home/pages/device/device.module.ts index b7d8d1e34a..f6b353c8bf 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device.module.ts +++ b/ui-ngx/src/app/modules/home/pages/device/device.module.ts @@ -25,7 +25,6 @@ import { HomeDialogsModule } from '../../dialogs/home-dialogs.module'; import { HomeComponentsModule } from '@modules/home/components/home-components.module'; import { DeviceTabsComponent } from '@home/pages/device/device-tabs.component'; import { SecurityConfigComponent } from '@home/pages/device/lwm2m/security-config.component'; -// TODO: @nickAS21 move to device profile import { SecurityConfigServerComponent } from '@home/pages/device/lwm2m/security-config-server.component'; import { DefaultDeviceConfigurationComponent } from './data/default-device-configuration.component'; import { DeviceConfigurationComponent } from './data/device-configuration.component'; diff --git a/ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config-server.component.html b/ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config-server.component.html index a8f9f2074e..35fad79d8d 100644 --- a/ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config-server.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config-server.component.html @@ -24,7 +24,7 @@ + [value]="securityMode"> {{ credentialTypeLwM2MNamesMap.get(securityConfigLwM2MType[securityMode]) }} @@ -35,43 +35,64 @@
{{ 'device.lwm2m-security-config.client-publicKey-or-id' | translate }} - + {{clientPublicKeyOrId.value?.length || 0}}/{{lenMaxClientPublicKeyOrId}} {{ 'device.lwm2m-security-config.client-publicKey-or-id' | translate }} {{ 'device.lwm2m-security-config.required' | translate }} - + {{ 'device.lwm2m-security-config.client-key' | translate }} - {{ 'device.lwm2m-security-config.pattern_hex_dec_182' | translate }} + {{ translate.get('device.lwm2m-security-config.pattern_hex_dec', { + count: 0 + }) | async }} - + {{ 'device.lwm2m-security-config.client-key' | translate }} - {{ 'device.lwm2m-security-config.pattern_hex_dec' | translate }} + {{ translate.get('device.lwm2m-security-config.pattern_hex_dec', { + count: lenMaxClientPublicKeyOrId + }) | async }} {{ 'device.lwm2m-security-config.client-secret-key' | translate }} - + {{clientSecretKey.value?.length || 0}}/{{lenMaxClientSecretKey}} {{ 'device.lwm2m-security-config.client-secret-key' | translate }} {{ 'device.lwm2m-security-config.required' | translate }} - + {{ 'device.lwm2m-security-config.client-key' | translate }} - {{ 'device.lwm2m-security-config.pattern_hex_dec_134' | translate }} + {{ translate.get('device.lwm2m-security-config.pattern_hex_dec', { + count: 0 + }) | async }} - + {{ 'device.lwm2m-security-config.client-key' | translate }} - {{ 'device-profile.lwm2m.pattern_hex_dec_64' | translate }} + {{ translate.get('device.lwm2m-security-config.pattern_hex_dec', { + count: lenMaxClientSecretKey + }) | async }}
diff --git a/ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config-server.component.ts b/ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config-server.component.ts index 2d5a0ba78e..8d99d3c8d3 100644 --- a/ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config-server.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config-server.component.ts @@ -14,26 +14,23 @@ /// limitations under the License. /// -import {Component, forwardRef, Inject, Input, OnInit, ViewChild} from '@angular/core'; - -import { - ControlValueAccessor, - FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators -} from '@angular/forms'; +import { Component, forwardRef, Inject, Input, OnInit } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; import { - SECURITY_CONFIG_MODE, - SECURITY_CONFIG_MODE_NAMES, - KEY_IDENT_REGEXP_PSK, - ServerSecurityConfig, DeviceCredentialsDialogLwm2mData, + KEY_REGEXP_HEX_DEC, + LEN_MAX_PRIVATE_KEY, LEN_MAX_PSK, - LEN_MAX_PRIVATE_KEY, LEN_MAX_PUBLIC_KEY_RPK, KEY_PRIVATE_REGEXP, LEN_MAX_PUBLIC_KEY_X509, KEY_PUBLIC_REGEXP_X509 + LEN_MAX_PUBLIC_KEY_RPK, + LEN_MAX_PUBLIC_KEY_X509, + SECURITY_CONFIG_MODE, + SECURITY_CONFIG_MODE_NAMES, + ServerSecurityConfig } from '@home/pages/device/lwm2m/security-config.models'; -import {Store} from '@ngrx/store'; -import {AppState} from '@core/core.state'; -import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog'; -import {PageComponent} from '@shared/components/page.component'; -import {MatPaginator} from '@angular/material/paginator'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { PageComponent } from '@shared/components/page.component'; import { TranslateService } from '@ngx-translate/core'; @Component({ @@ -54,13 +51,13 @@ export class SecurityConfigServerComponent extends PageComponent implements OnIn securityConfigLwM2MType = SECURITY_CONFIG_MODE; securityConfigLwM2MTypes = Object.keys(SECURITY_CONFIG_MODE); credentialTypeLwM2MNamesMap = SECURITY_CONFIG_MODE_NAMES; - lenMaxClientPublicKeyOrId = LEN_MAX_PSK; + lenMinClientPublicKeyOrId = 0; + lenMaxClientPublicKeyOrId = LEN_MAX_PUBLIC_KEY_RPK; + lenMinClientSecretKey = LEN_MAX_PRIVATE_KEY; lenMaxClientSecretKey = LEN_MAX_PRIVATE_KEY; @Input() serverFormGroup: FormGroup; - @ViewChild(MatPaginator) paginator: MatPaginator; - constructor(protected store: Store, @Inject(MAT_DIALOG_DATA) public data: DeviceCredentialsDialogLwm2mData, public dialogRef: MatDialogRef, @@ -73,40 +70,59 @@ export class SecurityConfigServerComponent extends PageComponent implements OnIn this.registerDisableOnLoadFormControl(this.serverFormGroup.get('securityMode')); } - updateValueFields(serverData: ServerSecurityConfig): void { + private updateValueFields(serverData: ServerSecurityConfig): void { this.serverFormGroup.patchValue(serverData, {emitEvent: false}); const securityMode = this.serverFormGroup.get('securityMode').value as SECURITY_CONFIG_MODE; this.updateValidate(securityMode); } - updateValidate(securityMode: SECURITY_CONFIG_MODE): void { + private updateValidate(securityMode: SECURITY_CONFIG_MODE): void { switch (securityMode) { case SECURITY_CONFIG_MODE.NO_SEC: this.serverFormGroup.get('clientPublicKeyOrId').setValidators([]); this.serverFormGroup.get('clientSecretKey').setValidators([]); break; case SECURITY_CONFIG_MODE.PSK: + this.lenMinClientPublicKeyOrId = 0; this.lenMaxClientPublicKeyOrId = LEN_MAX_PUBLIC_KEY_RPK; + this.lenMinClientSecretKey = LEN_MAX_PSK; this.lenMaxClientSecretKey = LEN_MAX_PSK; - this.serverFormGroup.get('clientPublicKeyOrId').setValidators([Validators.required]); - this.serverFormGroup.get('clientSecretKey').setValidators([Validators.required, Validators.pattern(KEY_IDENT_REGEXP_PSK)]); + this.setValidatorsSecurity(securityMode); break; case SECURITY_CONFIG_MODE.RPK: - this.lenMaxClientPublicKeyOrId = LEN_MAX_PUBLIC_KEY_X509; + this.lenMinClientPublicKeyOrId = LEN_MAX_PUBLIC_KEY_RPK; + this.lenMaxClientPublicKeyOrId = LEN_MAX_PUBLIC_KEY_RPK; + this.lenMinClientSecretKey = LEN_MAX_PRIVATE_KEY; this.lenMaxClientSecretKey = LEN_MAX_PRIVATE_KEY; - this.serverFormGroup.get('clientPublicKeyOrId').setValidators([Validators.required, Validators.pattern(KEY_PUBLIC_REGEXP_X509)]); - this.serverFormGroup.get('clientSecretKey').setValidators([Validators.required, Validators.pattern(KEY_PRIVATE_REGEXP)]); + this.setValidatorsSecurity(securityMode); break; case SECURITY_CONFIG_MODE.X509: + this.lenMinClientPublicKeyOrId = 0; this.lenMaxClientPublicKeyOrId = LEN_MAX_PUBLIC_KEY_X509; + this.lenMinClientSecretKey = LEN_MAX_PRIVATE_KEY; this.lenMaxClientSecretKey = LEN_MAX_PRIVATE_KEY; - this.serverFormGroup.get('clientPublicKeyOrId').setValidators([Validators.required, Validators.pattern(KEY_PUBLIC_REGEXP_X509)]); - this.serverFormGroup.get('clientSecretKey').setValidators([Validators.required, Validators.pattern(KEY_PRIVATE_REGEXP)]); + this.setValidatorsSecurity(securityMode); break; } this.serverFormGroup.updateValueAndValidity(); } + private setValidatorsSecurity = (securityMode: SECURITY_CONFIG_MODE): void => { + if (securityMode === SECURITY_CONFIG_MODE.PSK) { + this.serverFormGroup.get('clientPublicKeyOrId').setValidators([Validators.required]); + } else { + this.serverFormGroup.get('clientPublicKeyOrId').setValidators([Validators.required, + Validators.pattern(KEY_REGEXP_HEX_DEC), + Validators.minLength(this.lenMinClientPublicKeyOrId), + Validators.maxLength(this.lenMaxClientPublicKeyOrId)]); + } + + this.serverFormGroup.get('clientSecretKey').setValidators([Validators.required, + Validators.pattern(KEY_REGEXP_HEX_DEC), + Validators.minLength(this.lenMinClientSecretKey), + Validators.maxLength(this.lenMaxClientSecretKey)]); + } + securityModeChanged(securityMode: SECURITY_CONFIG_MODE): void { this.updateValidate(securityMode); } diff --git a/ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config.component.html b/ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config.component.html index fb9ce1ca15..e130e40a2c 100644 --- a/ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config.component.html @@ -35,112 +35,130 @@ {{ 'device.lwm2m-security-config.required' | translate }}
- + -
- - device.lwm2m-security-config.mode - - - {{ credentialTypeLwM2MNamesMap.get(securityConfigLwM2MType[securityConfigClientMode]) }} - - - -
+ +
- {{ 'device.lwm2m-security-config.identity' | translate }} - - - {{ 'device.lwm2m-security-config.identity' | translate }} - {{ 'device.lwm2m-security-config.required' | translate }} - + device.lwm2m-security-config.mode + + + {{ credentialTypeLwM2MNamesMap.get(securityConfigLwM2MType[securityConfigClientMode]) }} + + -
-
+ + {{ 'device.lwm2m-security-config.identity' | translate }} + + + {{ 'device.lwm2m-security-config.identity' | translate }} + {{ 'device.lwm2m-security-config.required' | translate }} + + +
+
- - {{ 'device.lwm2m-security-config.client-key' | translate }} - - - - {{clientKey.value?.length || 0}}/{{lenMaxKeyClient}} - - {{ 'device.lwm2m-security-config.client-key' | translate }} - {{ 'device.lwm2m-security-config.required' | translate }} - - - {{ 'device.lwm2m-security-config.client-key' | translate }} - {{ 'device-profile.lwm2m-security-config.pattern_hex_dec_64' | translate }} - - - {{ 'device.lwm2m-security-config.client-key' | translate }} - {{ 'device.lwm2m-security-config.pattern_hex_dec_182' | translate }} - - -
-
- - {{ 'device.lwm2m-security-config.client-certificate' | translate }} - + + {{ 'device.lwm2m-security-config.client-key' | translate }} + + {{clientKey.value?.length || 0}}/{{lenMaxKeyClient}} + + {{ 'device.lwm2m-security-config.client-key' | translate }} + {{ 'device.lwm2m-security-config.required' | translate }} + + + {{ 'device.lwm2m-security-config.client-key' | translate }} + {{ translate.get('device.lwm2m-security-config.pattern_hex_dec', { + count: 0 + }) | async }} + + + {{ 'device.lwm2m-security-config.client-key' | translate }} + {{ translate.get('device.lwm2m-security-config.pattern_hex_dec', { + count: lenMaxKeyClient + }) | async }} + + +
+
+ + {{ 'device.lwm2m-security-config.client-certificate' | translate }} + +
-
+
-
- - - - -
{{ 'device.lwm2m-security-config.bootstrap-server' | translate | uppercase }}
-
-
-
- - -
-
-
- - - - -
{{ 'device.lwm2m-security-config.lwm2m-server' | translate | uppercase }}
-
-
-
- - -
-
-
-
+ +
+ + + + +
{{ 'device.lwm2m-security-config.bootstrap-server' | translate | uppercase }}
+
+
+ +
+ + +
+
+
+
+ + + + +
{{ 'device.lwm2m-security-config.lwm2m-server' | translate | uppercase }}
+
+
+ +
+ + +
+
+
+
+
+
-
-
- - -
-
+ +
+
+ + +
+
+
@@ -150,7 +168,7 @@
- +
{{ this.currentTime | date:'medium'}} @@ -47,8 +47,8 @@ pause_circle_outline - + {{speedValue}}
-
\ No newline at end of file +
diff --git a/ui-ngx/src/app/shared/components/time/history-selector/history-selector.component.ts b/ui-ngx/src/app/shared/components/time/history-selector/history-selector.component.ts index 7cdcc42eec..c5bc1d61c3 100644 --- a/ui-ngx/src/app/shared/components/time/history-selector/history-selector.component.ts +++ b/ui-ngx/src/app/shared/components/time/history-selector/history-selector.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core'; +import { ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, Output } from '@angular/core'; import { interval } from 'rxjs'; import { filter } from 'rxjs/operators'; import { HistorySelectSettings } from '@app/modules/home/components/widget/lib/maps/map-models'; @@ -24,9 +24,9 @@ import { HistorySelectSettings } from '@app/modules/home/components/widget/lib/m templateUrl: './history-selector.component.html', styleUrls: ['./history-selector.component.scss'] }) -export class HistorySelectorComponent implements OnInit, OnChanges { +export class HistorySelectorComponent implements OnChanges { - @Input() settings: HistorySelectSettings + @Input() settings: HistorySelectSettings; @Input() minTime: number; @Input() maxTime: number; @Input() step = 1000; @@ -47,9 +47,6 @@ export class HistorySelectorComponent implements OnInit, OnChanges { constructor(private cd: ChangeDetectorRef) { } - ngOnInit(): void { - } - ngOnChanges() { this.maxTimeIndex = Math.ceil((this.maxTime - this.minTime) / this.step); this.currentTime = this.minTime === Infinity ? null : this.minTime; @@ -57,34 +54,34 @@ export class HistorySelectorComponent implements OnInit, OnChanges { play() { this.playing = true; - if (!this.interval) + if (!this.interval) { this.interval = interval(1000 / this.speed) .pipe( - filter(() => this.playing)).subscribe(() => { - this.index++; - this.currentTime = this.minTime + this.index * this.step; - if (this.index <= this.maxTimeIndex) { - this.cd.detectChanges(); - this.timeUpdated.emit(this.currentTime); - } - else { - this.interval.complete(); - } - }, err => { - console.error(err); - }, () => { - this.currentTime = this.index = this.minTimeIndex; + filter(() => this.playing) + ).subscribe(() => { + this.index++; + this.currentTime = this.minTime + this.index * this.step; + if (this.index <= this.maxTimeIndex) { + this.cd.detectChanges(); + this.timeUpdated.emit(this.currentTime); + } else { this.playing = false; - this.interval = null; + this.interval.complete(); this.cd.detectChanges(); - }); + } + }, err => { + console.error(err); + }, () => { + this.interval = null; + }); + } } - reeneble() { - if (this.playing) { - const position = this.index; + reInit() { + if (this.interval) { this.interval.complete(); - this.index = position; + } + if (this.playing) { this.play(); } } @@ -138,8 +135,9 @@ export class HistorySelectorComponent implements OnInit, OnChanges { this.pause(); } - changeIndex() { - this.currentTime = this.minTime + this.index * this.step; + changeIndex(index: number) { + this.index = index; + this.currentTime = this.minTime + index * this.step; this.timeUpdated.emit(this.currentTime); } } diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index 054aa5a085..8f2095c514 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -454,8 +454,6 @@ export interface DeviceCredentials extends BaseData { credentialsType: DeviceCredentialsType; credentialsId: string; credentialsValue: string; - credentialsLwKey: string; - credentialsLwValue: string; } export interface DeviceCredentialMQTTBasic { diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index 24ea528b46..179a6ff47b 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -345,6 +345,11 @@ export interface WidgetActionDescriptor extends CustomActionDescriptor { targetDashboardStateId?: string; openRightLayout?: boolean; openNewBrowserTab?: boolean; + openInSeparateDialog?: boolean; + dialogTitle?: string; + dialogHideDashboardToolbar?: boolean; + dialogWidth?: number; + dialogHeight?: number; setEntityId?: boolean; stateEntityParamName?: string; } 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 1b5331bf2a..cb2d8e4f00 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -924,9 +924,8 @@ "lwm2m-server": "LwM2M Server", "client-publicKey-or-id": "Client Public Key or Id", "client-secret-key": "Client Secret Key", - "config-json-tab": "Json Client Security Config", - "pattern_hex_dec_64": "must be hex decimal format and 64 characters", - "pattern_hex_dec_182": "must be hex decimal format and 182 characters" + "config-json-tab": "Json Client Security Config", + "pattern_hex_dec": "{ count, plural, 0 {must be hex decimal format} other {must be # characters} }" }, "client-id": "Client ID", "client-id-pattern": "Contains invalid character.", @@ -960,6 +959,7 @@ "unable-delete-device-alias-title": "Unable to delete device alias", "unable-delete-device-alias-text": "Device alias '{{deviceAlias}}' can't be deleted as it used by the following widget(s):
{{widgetsList}}", "is-gateway": "Is gateway", + "overwrite-activity-time": "Overwrite activity time for connected device", "public": "Public", "device-public": "Device is public", "select-device": "Select device", @@ -1136,6 +1136,7 @@ "no-objects-matching": "No objects matching '{{object}}' were found.", "valid-id-instance-no-min": "Instance number '{{instance}}' no validated. Min value='{{min}}'", "valid-id-instance-no-max": "Instance number '{{instance}}' no validated. Max value='{{max}}'", + "valid-id-instance": "Instance number '{{instance}}' no validated. { count, plural, 1 {Max value='{{max}}'} 2 {Min value='{{min}}'} other {Must be only number} }", "model-tab": "LWM2M Model", "add-instances-tip": "Add new instances", "instances-list": "Instances list", @@ -1154,10 +1155,7 @@ "key-name_label": "Key Name in Camel format", "required": " value is required.", "mode": "Security config mode", - "pattern_hex_dec_64": "must be hex decimal format and 64 characters", - "pattern_hex_dec_134": "must be hex decimal format and 134 characters", - "pattern_hex_dec_182": "must be hex decimal format and 182 characters", - "pattern_hex_dec": "must be hex decimal format", + "pattern_hex_dec": "{ count, plural, 0 {must be hex decimal format} other {must be # characters} }", "bootstrap-tab": "Bootstrap", "servers": "Servers", "short-id": "Short ID", @@ -1976,6 +1974,7 @@ "entity-field": "Entity field", "access-token": "Access token", "isgateway": "Is Gateway", + "activity-time-from-gateway-device": "Activity time from gateway device", "description": "Description", "edge-license-key": "License Key", "cloud-endpoint": "Cloud Endpoint", @@ -2538,6 +2537,12 @@ "set-entity-from-widget": "Set entity from widget", "target-dashboard": "Target dashboard", "open-right-layout": "Open right dashboard layout (mobile view)", + "open-in-separate-dialog": "Open in separate dialog", + "dialog-title": "Dialog title", + "dialog-hide-dashboard-toolbar": "Hide dashboard toolbar in dialog", + "dialog-width": "Dialog width in percents relative to viewport width", + "dialog-height": "Dialog height in percents relative to viewport height", + "dialog-size-range-error": "Dialog size percent value should be in a range from 1 to 100.", "open-new-browser-tab": "Open in a new browser tab" }, "widgets-bundle": { diff --git a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json index 28e38022a9..7c95423f80 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json @@ -73,9 +73,9 @@ "enable-proxy": "启用代理", "enable-tls": "启用TLS", "error-verification-url": "域名不应包含符号 “/” 和 “:”。例:thingsboard.io", - "general": "总则", - "general-policy": "General policy", - "general-settings": "常规设置", + "general": "基本设置", + "general-policy": "基本策略", + "general-settings": "基本设置", "mail-from": "邮件来自", "mail-from-required": "邮件发件人必填。", "max-failed-login-attempts": "登录失败之前的最大登录尝试次数", @@ -91,7 +91,7 @@ "minimum-special-characters-range": "特殊字符的最小数目不能为负数", "minimum-uppercase-letters": "最小大写字母数", "minimum-uppercase-letters-range": "大写字母的最小数目不能为负数", - "number-from": "来自的电话号码", + "number-from": "发送方电话号码", "number-from-required": "发送方电话号码必填。", "number-to": "电话号码至", "number-to-required": "电话号码必填。", @@ -182,11 +182,11 @@ "sms-message": "短信", "sms-message-max-length": "短信长度不能超过1600个字符", "sms-message-required": "短消息内容必填。", - "sms-provider": "SMS provider", - "sms-provider-settings": "SMS provider 设置", - "sms-provider-type": "SMS provider 类型", + "sms-provider": "SMS 服务商", + "sms-provider-settings": "SMS 服务商设置", + "sms-provider-type": "SMS 服务商类型", "sms-provider-type-aws-sns": "亚马逊社交网站", - "sms-provider-type-required": "SMS provider 类型必填。", + "sms-provider-type-required": "SMS 服务商类型必填。", "sms-provider-type-twilio": "Twilio", "smtp-host": "SMTP主机", "smtp-host-required": "SMTP主机必填。", @@ -224,15 +224,15 @@ "acknowledge": "应答", "aknowledge-alarm-text": "确定要确认报警吗?", "aknowledge-alarm-title": "确认报警", - "aknowledge-alarms-text": "确定要确认 { count, plural, 1 {1 个警告} other {# 个警告} }?", - "aknowledge-alarms-title": "确认 { count, plural, 1 {1 个警告} other {# 个警告} }", + "aknowledge-alarms-text": "确定要确认 { count, plural, 1 {# 个警告} other {# 个警告} }?", + "aknowledge-alarms-title": "确认 { count, plural, 1 {# 个警告} other {# 个警告} }", "alarm": "警告", "alarm-details": "报警详细信息", - "alarm-filter": "报警过滤器", + "alarm-filter": "报警筛选器", "alarm-required": "警告必填", "alarm-severity-list": "警报严重性列表", "alarm-status": "警告状态", - "alarm-status-filter": "报警状态过滤器", + "alarm-status-filter": "报警状态筛选器", "alarm-status-list": "报警状态列表", "alarm-type-list": "报警类型列表", "alarms": "警告", @@ -242,11 +242,11 @@ "clear": "清除", "clear-alarm-text": "确定要清除警报吗?", "clear-alarm-title": "清除警报", - "clear-alarms-text": "确定要清除 { count, plural, 1 {1 个警告} other {# 个警告} }?", - "clear-alarms-title": "清除 { count, plural, 1 {1 个警告} other {# 个警告} }", + "clear-alarms-text": "确定要清除 { count, plural, 1 {# 个警告} other {# 个警告} }?", + "clear-alarms-title": "清除 { count, plural, 1 {# 个警告} other {# 个警告} }", "clear-time": "清除时间", "created-time": "创建时间", - "details": "细节", + "details": "详情", "display-status": { "ACTIVE_ACK": "Active 已确认", "ACTIVE_UNACK": "Active 未确认", @@ -268,7 +268,7 @@ "originator-type": "Originator 类型", "polling-interval": "警告轮询间隔(秒)", "polling-interval-required": "警告轮询间隔必填。", - "search": "搜索警告", + "search": "查找警告", "search-propagated-alarms": "检索已传递的警报", "search-status": { "ACK": "已确认", @@ -278,7 +278,7 @@ "UNACK": "未确认" }, "select-alarm": "选择警告", - "selected-alarms": "已选择 { count, plural, 1 {1 个警告} other {# 个警告} }", + "selected-alarms": "已选择 { count, plural, 1 {# 个警告} other {# 个警告} }", "severity": "严重程度", "severity-critical": "危险", "severity-indeterminate": "不确定", @@ -297,9 +297,9 @@ "default-state-entity": "默认状态实体", "duplicate-alias": "别名已经存在。", "edit": "编辑别名", - "entity-filter": "实体过滤", - "entity-filter-no-entity-matched": "未找到符合指定过滤条件的实体。", - "filter-type": "过滤类型", + "entity-filter": "实体筛选器", + "entity-filter-no-entity-matched": "未找到符合指定筛选条件的实体。", + "filter-type": "筛选器类型", "filter-type-apiUsageState": "Api使用状态", "filter-type-asset-search-query": "资产搜索查询", "filter-type-asset-search-query-description": "类型为 {{assetTypes}} 且具有 {{relationType}} 关联 {{direction}} {{rootEntity}} 的资产", @@ -320,15 +320,15 @@ "filter-type-entity-view-type-description": "类型为 '{{entityView}}' 的实体视图", "filter-type-relations-query": "关联查询", "filter-type-relations-query-description": "具有 {{relationType}} 关联 {{direction}} {{rootEntity}} 的 {{entities}} ", - "filter-type-required": "过滤类型必填。", + "filter-type-required": "筛选器类型必填。", "filter-type-single-entity": "单个实体", - "filter-type-state-entity": "实体(仪表板状态)", - "filter-type-state-entity-description": "实体令牌(仪表板状态参数)", + "filter-type-state-entity": "仪表板实体状态", + "filter-type-state-entity-description": "仪表板实体令牌状态参数", "last-level-relation": "仅获取最后一级关联", "max-relation-level": "最大关联层级", "name": "别名", "name-required": "别名必填", - "no-entity-filter-specified": "没有指定实体过滤条件", + "no-entity-filter-specified": "没有指定实体筛选器", "resolve-multiple": "解决为多实体", "root-entity": "根实体", "root-state-entity": "使用仪表板状态实体作为根实体", @@ -339,7 +339,7 @@ "api-usage": { "api-usage": "Api 使用统计", "data-points": "数据点", - "data-points-storage-days": "数据点存储天数", + "data-points-storage-days": "日存储数据点数", "email": "Email", "email-messages": "Email messages", "email-messages-daily-activity": "Email messages daily activity", @@ -354,7 +354,7 @@ "javascript-functions-hourly-activity": "JavaScript functions hourly activity", "javascript-functions-monthly-activity": "JavaScript functions monthly activity", "latest-error": "最新错误", - "messages": "信息", + "messages": "消息", "permanent-failures": "${entityName} 永久性故障", "permanent-timeouts": "${entityName} 永久超时", "processing-failures": "${entityName} 处理失败", @@ -381,11 +381,11 @@ "telemetry-persistence-hourly-activity": "Telemetry persistence hourly activity", "telemetry-persistence-monthly-activity": "Telemetry persistence monthly activity", "transport": "Transport", - "transport-daily-activity": "运输日常活动", + "transport-daily-activity": "Transport daily activity", "transport-data-points": "传输数据点", "transport-hourly-activity": "Transport hourly activity", "transport-messages": "传输消息", - "transport-monthly-activity": "运输月度活动", + "transport-monthly-activity": "Transport monthly activity", "view-details": "查看详细信息", "view-statistics": "查看统计信息" }, @@ -406,7 +406,7 @@ "assign-asset-to-customer": "将资产分配给客户", "assign-asset-to-customer-text": "请选择要分配给客户的资产", "assign-assets": "分配资产", - "assign-assets-text": "分配 { count, plural, 1 {1 个资产} other {# 个资产} } 给客户", + "assign-assets-text": "分配 { count, plural, 1 {# 个资产} other {# 个资产} } 给客户", "assign-new-asset": "分配新资产", "assign-to-customer": "分配给客户", "assign-to-customer-text": "请选择客户以分配资产", @@ -416,11 +416,11 @@ "delete-asset-text": "小心!确认后资产及其所有相关数据将不可恢复。", "delete-asset-title": "确定要删除资产 '{{assetName}}'?", "delete-assets": "删除资产", - "delete-assets-action-title": "删除 { count, plural, 1 {1 个资产} other {# 个资产} }", + "delete-assets-action-title": "删除 { count, plural, 1 {# 个资产} other {# 个资产} }", "delete-assets-text": "请注意:确认后,所有选定的资产将被删除,所有相关的数据将变得不可恢复。", - "delete-assets-title": "确定要删除 { count, plural, 1 {1 个资产} other {# 个资产} }?", + "delete-assets-title": "确定要删除 { count, plural, 1 {# 个资产} other {# 个资产} }?", "description": "说明", - "details": "细节", + "details": "详情", "enter-asset-type": "输入资产类型", "events": "事件", "idCopiedMessage": "资产ID已经复制到粘贴板", @@ -440,19 +440,19 @@ "no-assets-matching": "没有找到匹配 '{{entity}}' 的资产。", "no-assets-text": "未找到资产", "public": "公开", - "search": "搜索资产", + "search": "查找资产", "select-asset": "选择资产", "select-asset-type": "选择资产类型", - "selected-assets": "已选择 { count, plural, 1 {1 个资产} other {# 个资产} }", + "selected-assets": "已选择 { count, plural, 1 {# 个资产} other {# 个资产} }", "type": "类型", "type-required": "类型必填。", "unassign-asset": "未分配资产", "unassign-asset-text": "确认后,资产将未分配,客户无法访问。", "unassign-asset-title": "您确定要取消对'{{assetName}}'资产的分配吗?", "unassign-assets": "取消分配资产", - "unassign-assets-action-title": "从客户处取消分配 { count, plural, 1 {1 个资产} other {# 个资产} }", + "unassign-assets-action-title": "从客户处取消分配 { count, plural, 1 {# 个资产} other {# 个资产} }", "unassign-assets-text": "确认后,所有选定的资产将被分配,客户无法访问。", - "unassign-assets-title": "您确定要取消分配 { count, plural, 1 {1 个资产} other {# 个资产} }吗?", + "unassign-assets-title": "您确定要取消分配 { count, plural, 1 {# 个资产} other {# 个资产} }吗?", "unassign-from-customer": "取消分配客户", "view-assets": "查看资产" }, @@ -464,7 +464,7 @@ "attributes-scope": "设备属性范围", "delete-attributes": "删除属性", "delete-attributes-text": "注意,确认后所有选中的属性都会被删除。", - "delete-attributes-title": "您确定要删除 { count, plural, 1 {1 个属性} other {# 个属性} }吗?", + "delete-attributes-title": "您确定要删除 { count, plural, 1 {# 个属性} other {# 个属性} }吗?", "enter-attribute-value": "输入属性值", "key": "键名", "key-required": "属性键必填。", @@ -478,8 +478,8 @@ "scope-latest-telemetry": "最新遥测数据", "scope-server": "服务端属性", "scope-shared": "共享属性", - "selected-attributes": "已选择{ count, plural, 1 {1 个属性} other {# 个属性} }", - "selected-telemetry": "已选择 { count, plural, 1 {1 telemetry unit} other {# telemetry units} }", + "selected-attributes": "已选择{ count, plural, 1 {# 个属性} other {# 个属性} }", + "selected-telemetry": "已选择 { count, plural, 1 {# telemetry unit} other {# telemetry units} }", "show-on-widget": "在部件上显示", "value": "价值", "value-required": "属性值必填。", @@ -491,7 +491,7 @@ "audit-log-details": "审计日志详情", "audit-logs": "审计日志", "clear-search": "清空查找", - "details": "细节", + "details": "详情", "entity-name": "实体名称", "entity-type": "实体类型", "failure-details": "失败详情", @@ -591,11 +591,11 @@ "delete": "删除此客户", "delete-customer-text": "小心!确认后,客户及其所有相关数据将不可恢复。", "delete-customer-title": "您确定要删除客户'{{customerTitle}}'吗?", - "delete-customers-action-title": "删除 { count, plural, 1 {1 个客户} other {# 个客户} }", + "delete-customers-action-title": "删除 { count, plural, 1 {# 个客户} other {# 个客户} }", "delete-customers-text": "小心!确认后,所有选定的客户将被删除,所有相关数据将不可恢复。", - "delete-customers-title": "您确定要删除 { count, plural, 1 {1 个客户} other {# 个客户} }吗?", + "delete-customers-title": "您确定要删除 { count, plural, 1 {# 个客户} other {# 个客户} }吗?", "description": "说明", - "details": "细节", + "details": "详情", "devices": "客户设备", "entity-views": "客户实体视图", "events": "事件", @@ -618,10 +618,10 @@ "public-dashboards": "公共仪表板", "public-devices": "公共设备", "public-entity-views": "公共实体视图", - "search": "搜索客户", + "search": "查找客户", "select-customer": "选择客户", "select-default-customer": "选择默认的客户", - "selected-customers": "已选择 { count, plural, 1 {1 个客户} other {# 个客户} }", + "selected-customers": "已选择 { count, plural, 1 {# 个客户} other {# 个客户} }", "title": "标题", "title-required": "标题必填。" }, @@ -634,7 +634,7 @@ "assign-dashboard-to-customer": "将仪表板分配给客户", "assign-dashboard-to-customer-text": "请选择要分配给客户的仪表板", "assign-dashboards": "分配仪表板", - "assign-dashboards-text": "分配 { count, plural, 1 {1 个仪表板} other {# 个仪表板} } 给客户", + "assign-dashboards-text": "分配 { count, plural, 1 {# 个仪表板} other {# 个仪表板} } 给客户", "assign-new-dashboard": "分配新的仪表板", "assign-to-customer": "分配给客户", "assign-to-customer-text": "请选择客户分配仪表板", @@ -665,19 +665,19 @@ "delete-dashboard-text": "小心!确认后仪表板及其所有相关数据将不可恢复。", "delete-dashboard-title": "您确定要删除仪表板 '{{dashboardTitle}}'吗?", "delete-dashboards": "删除仪表板", - "delete-dashboards-action-title": "删除 { count, plural, 1 {1 个仪表板} other {# 个仪表板} }", + "delete-dashboards-action-title": "删除 { count, plural, 1 {# 个仪表板} other {# 个仪表板} }", "delete-dashboards-text": "小心!确认后所有选定的仪表板将被删除,所有相关数据将不可恢复。", - "delete-dashboards-title": "确定要删除 { count, plural, 1 {1 个仪表板} other {# 个仪表板} }吗?", + "delete-dashboards-title": "确定要删除 { count, plural, 1 {# 个仪表板} other {# 个仪表板} }吗?", "delete-state": "删除仪表板状态", "delete-state-text": "确定要删除仪表板状态 '{{stateName}}' 吗?", "delete-state-title": "删除仪表板状态", "description": "说明", - "details": "细节", + "details": "详情", "display-dashboard-export": "显示导出", "display-dashboard-timewindow": "显示时间窗口", "display-dashboards-selection": "显示仪表板选项", "display-entities-selection": "显示实体选项", - "display-filters": "显示过滤器", + "display-filters": "显示筛选器", "display-title": "显示仪表板标题", "drop-image": "拖拽图像或单击以选择要上传的文件。", "edit-state": "仪表板状态编辑", @@ -688,7 +688,7 @@ "horizontal-margin-required": "需要水平边距值。", "import": "导入仪表板", "import-widget": "导入部件", - "invalid-aliases-config": "无法找到与某些别名过滤器匹配的任何设备。
请联系您的管理员以解决此问题。", + "invalid-aliases-config": "无法找到与某些别名筛选器匹配的任何设备。
请联系您的管理员以解决此问题。", "invalid-dashboard-file-error": "无法导入仪表板: 仪表板数据结构无效。", "invalid-widget-file-error": "无法导入窗口部件: 窗口部件数据结构无效。", "is-root-state": "根状态", @@ -728,7 +728,7 @@ "public-dashboard-title": "仪表板现已公开", "public-link": "公共链接", "public-link-copied-message": "仪表板的公共链接已被复制到剪贴板", - "search": "搜索仪表板", + "search": "查找仪表板", "search-states": "仪表板状态检索", "select-dashboard": "选择仪表板", "select-devices": "选择设备", @@ -736,8 +736,8 @@ "select-state": "选择目标状态", "select-widget-subtitle": "可用的部件类型列表", "select-widget-title": "选择部件", - "selected-dashboards": "已选择 { count, plural, 1 {1 个仪表盘} other {# 个仪表盘} }", - "selected-states": "已选择 { count, plural, 1 {1 个仪表板状态} other {# 个仪表板状态} }", + "selected-dashboards": "已选择 { count, plural, 1 {# 个仪表盘} other {# 个仪表盘} }", + "selected-states": "已选择 { count, plural, 1 {# 个仪表板状态} other {# 个仪表板状态} }", "set-background": "设置背景", "settings": "设置", "show-details": "显示详情", @@ -759,10 +759,10 @@ "unassign-dashboard-text": "确认后,面板将被取消分配,客户将无法访问。", "unassign-dashboard-title": "您确定要取消分配仪表板 '{{dashboardTitle}}'吗?", "unassign-dashboards": "取消分配仪表板", - "unassign-dashboards-action-text": "取消分配 { count, plural, 1 {1 个仪表板} other {# 个仪表板} } 给客户", - "unassign-dashboards-action-title": "取消分配此客户 { count, plural, 1 {1 个仪表板} other {# 个仪表板} }", + "unassign-dashboards-action-text": "取消分配 { count, plural, 1 {# 个仪表板} other {# 个仪表板} } 给客户", + "unassign-dashboards-action-title": "取消分配此客户 { count, plural, 1 {# 个仪表板} other {# 个仪表板} }", "unassign-dashboards-text": "确认后,所有选定的仪表板将被取消分配,客户将无法访问。", - "unassign-dashboards-title": "确定要取消分配仪表板 { count, plural, 1 {1 个仪表板} other {# 个仪表板} } 吗?", + "unassign-dashboards-title": "确定要取消分配仪表板 { count, plural, 1 {# 个仪表板} other {# 个仪表板} } 吗?", "unassign-from-customer": "取消分配客户", "unassign-from-customers": "客户未分配仪表板", "unassign-from-customers-text": "请选择从仪表板中取消分配的客户", @@ -787,8 +787,8 @@ "function-types": "函数类型", "function-types-required": "需要函数类型。", "label": "标签", - "maximum-function-types": "最多允许 { count, plural, 1 {1 个函数类型} other {# 个函数类型} }", - "maximum-timeseries-or-attributes": "最多允许 { count, plural, 1 {1 个 timeseries/属性。} other {# 个 timeseries/属性。} }", + "maximum-function-types": "最多允许 { count, plural, 1 {# 个函数类型} other {# 个函数类型} }", + "maximum-timeseries-or-attributes": "最多允许 { count, plural, 1 {# 个 timeseries/属性。} other {# 个 timeseries/属性。} }", "prev-orig-value-description": "先前的原始值;", "prev-value-description": "上一次函数调用的结果;", "settings": "设置", @@ -813,7 +813,7 @@ "time-to": "时间到" }, "details": { - "details": "细节", + "details": "详情", "edit-json": "编辑JSON", "edit-mode": "编辑模式", "toggle-edit-mode": "切换编辑模式" @@ -821,14 +821,14 @@ "device-profile": { "add": "添加设备配置", "add-alarm-rule": "添加报警规则", - "add-alarm-rule-details": "添加详细信息", + "add-alarm-rule-details": "详情模板:", "add-clear-alarm-rule": "添加清除条件", "add-create-alarm-rule": "添加创建条件", "add-create-alarm-rule-prompt": "请添加创建报警规则", "advanced-settings": "高级设置", "alarm-details": "报警详细信息", "alarm-rule-condition": "报警规则条件", - "alarm-rule-details": "细节", + "alarm-rule-details": "详情", "alarm-rule-relation-types-list": "要传递的关联类型", "alarm-rule-relation-types-list-hint": "如果未选择传递关联类型,则将不按关联类型过滤而传递报警。", "alarm-rules": "报警规则", @@ -852,7 +852,7 @@ "condition-duration-value-range": "持续时间值应在1到2147483647之间。", "condition-duration-value-required": "持续时间值必填。", "condition-during": "在 {{during}} 期间", - "condition-repeat-times": "重复 { count, plural, 1 {1 次} other {# 次} }", + "condition-repeat-times": "重复 { count, plural, 1 {# 次} other {# 次} }", "condition-repeating-value": "事件计数", "condition-repeating-value-pattern": "事件计数应为整数。", "condition-repeating-value-range": "事件计数应在1到2147483647之间。", @@ -874,7 +874,7 @@ "delete-device-profile-text": "注意,确认后设备配置和所有相关数据将不可恢复。", "delete-device-profile-title": "是否确实要删除设备配置 '{{deviceProfileName}}'?", "delete-device-profiles-text": "请注意:确认后,所有选定的设备配置将被删除,所有相关数据将不可恢复。", - "delete-device-profiles-title": "确定要删除 { count, plural, 1 {1 个设备配置} other {# 个设备配置} }?", + "delete-device-profiles-title": "确定要删除 { count, plural, 1 {# 个设备配置} other {# 个设备配置} }?", "description": "说明", "device-profile": "设备配置", "device-profile-details": "设备配置详情", @@ -886,13 +886,13 @@ "edit-alarm-rule-condition": "编辑报警规则条件", "edit-schedule": "编辑报警日程表", "enter-alarm-rule-condition-prompt": "请添加报警规则条件", - "idCopiedMessage": "设备配置 Id 已复制到剪贴板", + "idCopiedMessage": "设备配置 ID 已复制到剪贴板", "mqtt-device-payload-type": "MQTT 设备 Payload", "mqtt-device-payload-type-json": "JSON", "mqtt-device-payload-type-proto": "Protobuf", "mqtt-device-topic-filters": "MQTT 设备 Topic 筛选器", "mqtt-device-topic-filters-unique": "MQTT设备 Topic 筛选器必须唯一。", - "mqtt-payload-type-required": "荷载类型必填。", + "mqtt-payload-type-required": "Payload 类型必填。", "multi-level-wildcards-hint": "[#]可以替换 topic filter 本身,并且必须是 topic 的最后一个符号。例如:# or v1/devices/me/#。", "name": "名称", "name-required": "名称是必需的。", @@ -922,9 +922,9 @@ "provision-strategy-required": "预配置策略必填。", "rpc-response-topic-filter": "RPC响应 Topic 筛选器", "rpc-response-topic-filter-required": "RPC响应 Topic 筛选器必填。", - "schedule": "地铁列车时刻表", - "schedule-any-time": "Active all the time", - "schedule-custom": "定制", + "schedule": "启用规则:", + "schedule-any-time": "始终启用", + "schedule-custom": "自定义启用", "schedule-day": { "friday": "星期五", "monday": "星期一", @@ -936,16 +936,16 @@ }, "schedule-days": "天", "schedule-days-of-week-required": "每周至少选择一天。", - "schedule-specific-time": "在特定时间活跃", + "schedule-specific-time": "定时启用", "schedule-time": "时间", "schedule-time-from": "从", "schedule-time-to": "到", "schedule-type": "计划程序类型", "schedule-type-required": "计划类型必填。", - "search": "搜索设备配置", + "search": "查找设备配置", "select-alarm-severity": "选择报警严重性", "select-queue-hint": "从下拉列表中选择或添加自定义名称。", - "selected-device-profiles": "已选择 { count, plural, 1 {1 个设备配置} other {# 个设备配置} }", + "selected-device-profiles": "已选择 { count, plural, 1 {# 个设备配置} other {# 个设备配置} }", "set-default": "设为默认设备配置", "set-default-device-profile-text": "确认后,设备配置将被标记为默认,并将用于未指定配置的新设备。", "set-default-device-profile-title": "确实要将设备配置 '{{deviceProfileName}}' 设为默认值吗?", @@ -983,7 +983,7 @@ "assign-device-to-customer": "将设备分配给客户", "assign-device-to-customer-text": "请选择要分配给客户的设备", "assign-devices": "分配设备", - "assign-devices-text": "将 {count,plural,1 {1 个设备} other {# 个设备} }分配给客户", + "assign-devices-text": "将 {count,plural,1 {# 个设备} other {# 个设备} }分配给客户", "assign-new-device": "分配新设备", "assign-to-customer": "分配给客户", "assign-to-customer-text": "请选择客户分配设备", @@ -1003,11 +1003,11 @@ "delete-device-text": "小心!确认后设备及其所有相关数据将不可恢复。", "delete-device-title": "您确定要删除设备的{{deviceName}}吗?", "delete-devices": "删除设备", - "delete-devices-action-title": "删除 {count,plural,1 {1 个设备} other {# 个设备} }", + "delete-devices-action-title": "删除 {count,plural,1 {# 个设备} other {# 个设备} }", "delete-devices-text": "小心!确认后所有选定的设备将被删除,所有相关数据将不可恢复。", - "delete-devices-title": "确定要删除{count,plural,1 {1 个设备} other {# 个设备} } 吗?", + "delete-devices-title": "确定要删除{count,plural,1 {# 个设备} other {# 个设备} } 吗?", "description": "说明", - "details": "细节", + "details": "详情", "device": "设备", "device-alias": "设备别名", "device-configuration": "设备配置", @@ -1017,7 +1017,7 @@ "device-list": "设备列表", "device-list-empty": "没有被选中的设备", "device-name-filter-no-device-matched": "找不到以'{{device}}' 开头的设备。", - "device-name-filter-required": "设备名称过滤器必填。", + "device-name-filter-required": "设备名称筛选器必填。", "device-public": "设备公开", "device-required": "设备必填", "device-type": "设备类型", @@ -1056,12 +1056,12 @@ "remove-alias": "删除设备别名", "rsa-key": "RSA公钥", "rsa-key-required": "RSA公钥必填", - "search": "搜索设备", + "search": "查找设备", "secret": "密钥", "secret-required": "密钥必填", "select-device": "选择设备", "select-device-type": "选择设备类型", - "selected-devices": "已选择 { count, plural, 1 {1 个设备} other {# 个设备} }", + "selected-devices": "已选择 { count, plural, 1 {# 个设备} other {# 个设备} }", "transport-configuration": "传输配置", "unable-delete-device-alias-text": "设备别名 '{{deviceAlias}}' 不能够被删除,因为它被下列部件所使用:
{{widgetsList}}", "unable-delete-device-alias-title": "无法删除设备别名", @@ -1069,11 +1069,11 @@ "unassign-device-text": "确认后,设备将被取消分配,客户将无法访问。", "unassign-device-title": "您确定要取消分配设备 '{{deviceName}}'?", "unassign-devices": "取消分配设备", - "unassign-devices-action-title": "取消分配此客户 {count,plural,1 {1 个设备} other {# 个设备} }", + "unassign-devices-action-title": "取消分配此客户 {count,plural,1 {# 个设备} other {# 个设备} }", "unassign-devices-text": "确认后,所有选定的设备将被取消分配,并且客户将无法访问。", - "unassign-devices-title": "确定要取消分配 {count,plural,1 {1 个设备} other {# 个设备} } 吗?", + "unassign-devices-title": "确定要取消分配 {count,plural,1 {# 个设备} other {# 个设备} } 吗?", "unassign-from-customer": "取消分配客户", - "use-device-name-filter": "使用过滤器", + "use-device-name-filter": "使用筛选器", "user-name": "用户名", "user-name-required": "用户名必填。", "view-credentials": "查看凭据", @@ -1123,7 +1123,7 @@ "assign-entity-view-to-customer": "将实体视图分配给客户", "assign-entity-view-to-customer-text": "请选择要分配给客户的实体视图", "assign-entity-views": "分配实体视图", - "assign-entity-views-text": "分配 { count, plural, 1 {1 个实体视图} other {# 个实体视图} } 给客户", + "assign-entity-views-text": "分配 { count, plural, 1 {# 个实体视图} other {# 个实体视图} } 给客户", "assign-new-entity-view": "分配新实体视图", "assign-to-customer": "分配给客户", "assign-to-customer-text": "请选择客户分配实体视图", @@ -1142,11 +1142,11 @@ "delete-entity-view-text": "小心!确认后实体视图及其所有相关数据将不可恢复。", "delete-entity-view-title": "确定要删除实体视图 '{{entityViewName}}'?", "delete-entity-views": "删除实体视图", - "delete-entity-views-action-title": "删除 { count, plural, 1 {1 个实体视图} other {# 个实体视图} }", + "delete-entity-views-action-title": "删除 { count, plural, 1 {# 个实体视图} other {# 个实体视图} }", "delete-entity-views-text": "请注意:确认后,所有选定的实体视图将被删除,所有相关的数据将变得不可恢复。", - "delete-entity-views-title": "确定要删除 { count, plural, 1 {1 实体视图} other {# 实体视图} }?", + "delete-entity-views-title": "确定要删除 { count, plural, 1 {# 实体视图} other {# 实体视图} }?", "description": "说明", - "details": "细节", + "details": "详情", "duplicate-alias-error": "找到重复别名 '{{alias}}'。
实体视图别名必须是唯一的。", "end-date": "结束日期", "end-ts": "结束时间", @@ -1157,7 +1157,7 @@ "entity-view-list": "实体视图列表", "entity-view-list-empty": "没有被选中的实体视图", "entity-view-name-filter-no-entity-view-matched": "找不到以'{{entityView}}' 开头的实体视图。", - "entity-view-name-filter-required": "实体视图名称过滤器必填。", + "entity-view-name-filter-required": "实体视图名称筛选器必填。", "entity-view-public": "实体视图是公共的", "entity-view-required": "实体视图必填。", "entity-view-type": "实体视图类型", @@ -1186,10 +1186,10 @@ "no-keys-found": "找不到密钥。", "public": "公开", "remove-alias": "删除实体视图别名", - "search": "搜索实体视图", + "search": "查找实体视图", "select-entity-view": "选择实体视图", "select-entity-view-type": "选择实体视图类型", - "selected-entity-views": "已选择 { count, plural, 1 {1 个实体视图} other {# 个实体视图} }", + "selected-entity-views": "已选择 { count, plural, 1 {# 个实体视图} other {# 个实体视图} }", "server-attributes": "服务端属性", "server-attributes-placeholder": "服务端属性", "shared-attributes": "共享属性", @@ -1207,11 +1207,11 @@ "unassign-entity-view-text": "确认后,实体视图将未分配,客户无法访问。", "unassign-entity-view-title": "您确定要取消对 '{{entityViewName}}'实体视图的分配吗?", "unassign-entity-views": "取消分配实体视图", - "unassign-entity-views-action-title": "从客户处取消分配{count,plural,1 {1 实体视图} other {# 实体视图} }", + "unassign-entity-views-action-title": "从客户处取消分配{count,plural,1 {# 实体视图} other {# 实体视图} }", "unassign-entity-views-text": "确认后,所有选定的实体视图将被分配,客户无法访问。", - "unassign-entity-views-title": "确定要取消分配 { count, plural, 1 {1 个实体视图} other {# 个实体视图} }吗?", + "unassign-entity-views-title": "确定要取消分配 { count, plural, 1 {# 个实体视图} other {# 个实体视图} }吗?", "unassign-from-customer": "取消分配客户", - "use-entity-view-name-filter": "使用过滤器", + "use-entity-view-name-filter": "使用筛选器", "view-entity-views": "查看实体视图" }, "entity": { @@ -1242,14 +1242,14 @@ "entity-list-empty": "没有选择实体。", "entity-name": "实体名", "entity-name-filter-no-entity-matched": "没有找到以 '{{entity}}' 开头的实体", - "entity-name-filter-required": "实体名过滤器必填。", + "entity-name-filter-required": "实体名筛选器必填。", "entity-type": "实体类型", "entity-type-list": "实体类型列表", "entity-type-list-empty": "没有选择实体类型。", "entity-types": "实体类型", "entity-view-name-starts-with": "以 '{{prefix}}' 开头的实体视图", "key": "键名", - "key-name": "密钥名称", + "key-name": "Key name", "list-of-alarms": "{ count, plural, 1 {个警告} other {# 个警告} }", "list-of-assets": "{ count, plural, 1 {个资产} other {# 个资产} }", "list-of-customers": "{ count, plural, 1 {个客户} other {# 个客户} }", @@ -1281,7 +1281,7 @@ "rulenode-name-starts-with": "名称以 '{{prefix}}' 开头的规则节点", "search": "实体检索", "select-entities": "选择实体", - "selected-entities": "已选择 { count, plural, 1 {1 个实体} other {# 个实体} }", + "selected-entities": "已选择 { count, plural, 1 {# 个实体} other {# 个实体} }", "tenant-name-starts-with": "以 '{{prefix}}' 开头的租户", "tenant-profile-name-starts-with": "名称以 '{{prefix}}' 开头的租户配置", "type": "类型", @@ -1321,7 +1321,7 @@ "type-users": "用户", "unable-delete-entity-alias-text": "实体别名 '{{entityAlias}}' 被以下部件使用不能删除:
{{widgetsList}}", "unable-delete-entity-alias-title": "无法删除实体别名", - "use-entity-name-filter": "用户过滤", + "use-entity-name-filter": "用户筛选器", "user-name-starts-with": "以 '{{prefix}}' 开头的用户" }, "error": { @@ -1374,9 +1374,9 @@ "add-timeseries": "添加 Timeseries", "anonymous": "匿名", "attr-json-key-expression": "属性键JSON表达式", - "attr-topic-key-expression": "属性关键字主题表达式", + "attr-topic-key-expression": "属性键名Topic表达式", "attribute-filter": "属性筛选器", - "attribute-key-expression": "属性关键字表达式", + "attribute-key-expression": "属性键名表达式", "attribute-requests": "属性请求", "attribute-updates": "属性更新", "attributes": "属性", @@ -1400,7 +1400,7 @@ "delete-extension-text": "请注意:确认后,扩展和所有相关数据将变得不可恢复。", "delete-extension-title": "确实要删除扩展名'{{extensionId}}'吗?", "delete-extensions-text": "请注意:确认后,所有选定的扩展将被删除。", - "delete-extensions-title": "确定要删除 { count, plural, 1 {1 个扩展} other {# 个扩展} }吗?", + "delete-extensions-title": "确定要删除 { count, plural, 1 {# 个扩展} other {# 个扩展} }吗?", "device-name-expression": "设备名称表达式", "device-name-filter": "设备名称筛选器", "device-type-expression": "设备类型表达式", @@ -1414,7 +1414,7 @@ "extensions": "扩展", "field-required": "必填字段", "file": "扩展文件", - "filter-expression": "过滤表达式", + "filter-expression": "筛选条件表达式", "host": "主机", "id": "ID", "import-extension": "导入扩展", @@ -1488,7 +1488,7 @@ "response-timeout": "毫秒内响应超时", "response-topic-expression": "响应主题表达式", "retry-interval": "以毫秒为单位的重试间隔", - "selected-extensions": "已选择 { count, plural, 1 {1 个扩展} other {# 个扩展} }", + "selected-extensions": "已选择 { count, plural, 1 {# 个扩展} other {# 个扩展} }", "server-side-rpc": "服务端RPC", "ssl": "Ssl", "sync": { @@ -1504,7 +1504,7 @@ "token": "安全令牌", "topic": "主题", "topic-expression": "主题表达", - "topic-filter": "主题滤波", + "topic-filter": "Topic筛选器", "topic-name-expression": "设备名称主题表达式", "topic-type-expression": "设备类型主题表达式", "transformer": "转换器", @@ -1517,14 +1517,14 @@ }, "filter": { "add": "添加筛选器", - "add-complex": "Add complex", + "add-complex": "添加复合", "add-complex-filter": "添加复合筛选器", "add-filter": "添加筛选器", "add-filter-prompt": "请添加筛选器", - "add-key-filter": "添加密钥筛选器", + "add-key-filter": "添加键名筛选器", "autogenerated-label": "自动生成标签", "complex-filter": "复合筛选器", - "create-new-filter": "创建一个新的!", + "create-new-filter": "请新增!", "current-customer": "当前客户", "current-device": "当前设备", "current-tenant": "当前租户", @@ -1538,26 +1538,26 @@ "edit": "编辑筛选器", "edit-complex-filter": "编辑复合筛选器", "edit-filter-user-params": "编辑筛选器谓词用户参数", - "edit-key-filter": "Edit key filter", + "edit-key-filter": "编辑键名筛选器", "editable": "可编辑", "filter": "筛选器", "filter-required": "筛选器必填。", "filter-user-params": "过滤谓词用户参数", - "filters": "过滤器", + "filters": "筛选器", "ignore-case": "忽略大小写", - "key-filter": "Key filter", - "key-filters": "Key filters", - "key-name": "Key name", - "key-name-required": "Key name 必填。", + "key-filter": "键名筛选器", + "key-filters": "键名筛选器", + "key-name": "键名", + "key-name-required": "键名必填。", "key-type": { "attribute": "属性", - "entity-field": "实体字段", - "key-type": "Key type", + "entity-field": "实体", + "key-type": "键类型", "timeseries": "Timeseries" }, "key-value-type-change-message": "如果您确认新的值类型,所有输入的键过滤器将被删除。", "key-value-type-change-title": "是否确实要更改键值类型?", - "missing-key-filters-error": "Key filters is missing for filter '{{filter}}'.", + "missing-key-filters-error": "筛选器 '{{filter}}' 的键名筛选条件缺失。", "name": "筛选器名称", "name-required": "筛选器名称必填。", "no-dynamic-value": "无动态值", @@ -1565,18 +1565,18 @@ "no-filter-text": "未指定筛选器", "no-filters": "未配置筛选器", "no-filters-found": "找不到筛选器。", - "no-key-filters": "未配置密钥筛选器", + "no-key-filters": "未配置键名筛选器", "operation": { "and": "和", "contains": "包含", "ends-with": "结束于", - "equal": "平等的", + "equal": "等于", "greater": "大于", "greater-or-equal": "大于或等于", "less": "小于", "less-or-equal": "小于或等于", "not-contains": "不包含", - "not-equal": "不相等", + "not-equal": "不等于", "operation": "操作", "or": "或", "starts-with": "开始于" @@ -1584,7 +1584,7 @@ "order-priority": "字段顺序优先级", "preview": "筛选器预览", "remove-filter": "删除筛选器", - "remove-key-filter": "Remove key filter", + "remove-key-filter": "删除键名筛选器", "source-attribute": "源属性", "switch-to-default-value": "切换到默认值", "switch-to-dynamic-value": "切换到动态值", @@ -1592,7 +1592,7 @@ "unable-delete-filter-text": "无法删除筛选器 '{{filter}}' ,因为它由以下小部件使用:
{{widgetsList}}", "unable-delete-filter-title": "无法删除筛选器", "user-parameters": "用户参数", - "value": "价值", + "value": "值", "value-type-required": "键值类型是必需的。", "value-type": { "boolean": "布尔值", @@ -1646,7 +1646,7 @@ "access-token": "访问令牌", "tls": "TLS" }, - "storage": "Storage", + "storage": "存储", "storage-max-file-records": "文件中的最大记录数", "storage-max-files": "最大文件数", "storage-max-files-min": "最小值为1。", @@ -1665,7 +1665,7 @@ "storage-type": "存储类型", "storage-types": { "file-storage": "文件存储", - "memory-storage": "存储器" + "memory-storage": "内存存储" }, "thingsboard": "ThingsBoard", "thingsboard-host": "ThingsBoard主机", @@ -1691,9 +1691,9 @@ "delete-item-text": "注意,确认后此项及其所有相关数据将变得不可恢复。", "delete-item-title": "您确定要删除此项吗?", "delete-items": "删除项目", - "delete-items-action-title": "删除 { count, plural, 1 {1 个元素} other {# 个元素} }", + "delete-items-action-title": "删除 { count, plural, 1 {# 个元素} other {# 个元素} }", "delete-items-text": "注意,确认后所有选择的项目将被删除,所有相关数据将不可恢复。", - "delete-items-title": "确定你要删除 { count, plural, 1 {1 项} other {# 项} }吗?", + "delete-items-title": "确定你要删除 { count, plural, 1 {# 项} other {# 项} }吗?", "item-details": "项目详细信息", "no-items-text": "没有找到项目", "scroll-to-top": "滚动到顶部" @@ -1865,7 +1865,7 @@ }, "relation": { "add": "添加关联", - "add-relation-filter": "添加关联过滤器", + "add-relation-filter": "添加关联筛选器", "additional-info": "附加信息 (JSON)", "any-relation": "任意关联", "any-relation-type": "任何类型", @@ -1873,11 +1873,11 @@ "delete-from-relation-text": "确定删除后,当前实体将与实体 '{{entityName}}' 取消关联", "delete-from-relation-title": "确定要从实体 '{{entityName}}' 删除关联吗?", "delete-from-relations-text": "确定删除所有选择的关联关系后,当前实体将与对应的实体取消关联", - "delete-from-relations-title": "确定删除 { count, plural, 1 {1 个关联} other {# 个关联} } 吗?", + "delete-from-relations-title": "确定删除 { count, plural, 1 {# 个关联} other {# 个关联} } 吗?", "delete-to-relation-text": "确定删除后实体 '{{entityName}}' 将取消与当前实体的关联关系。", "delete-to-relation-title": "确定要删除实体 '{{entityName}}' 的关联吗?", "delete-to-relations-text": "确定删除所有选择的关联关系后,与当前实体对应的所有关联关系将被移除。", - "delete-to-relations-title": "确定要删除 { count, plural, 1 {1 个关联} other {# 个关联} }?", + "delete-to-relations-title": "确定要删除 { count, plural, 1 {# 个关联} other {# 个关联} }?", "direction": "方向", "direction-type": { "FROM": "从", @@ -1890,16 +1890,16 @@ "from-relations": "向外的关联", "invalid-additional-info": "无法解析附加信息JSON。", "no-relations-text": "未找到关联", - "relation-filters": "关联过滤器", + "relation-filters": "关联筛选器", "relation-type": "关联类型", "relation-type-required": "关联类型必填", "relations": "关联", - "remove-relation-filter": "移除关联过滤器", + "remove-relation-filter": "移除关联筛选器", "search-direction": { "FROM": "从", "TO": "到" }, - "selected-relations": "已选择{ count, plural, 1 {1 个关联} other {# 个关联} }", + "selected-relations": "已选择{ count, plural, 1 {# 个关联} other {# 个关联} }", "to-entity": "到实体", "to-entity-name": "到实体名称", "to-entity-type": "到实体类型", @@ -1915,11 +1915,11 @@ "delete": "删除规则链", "delete-rulechain-text": "小心,在确认规则链和所有相关数据将变得不可恢复。", "delete-rulechain-title": " 确实要删除规则链'{{ruleChainName}}'吗?", - "delete-rulechains-action-title": "删除 { count, plural, 1 {1 个规则链} other {# 个规则链} }", + "delete-rulechains-action-title": "删除 { count, plural, 1 {# 个规则链} other {# 个规则链} }", "delete-rulechains-text": "请注意:确认后,所有选定的规则链将被删除,所有相关的数据将变得不可恢复。", "delete-rulechains-title": "确实要删除{count, plural, 1 { 1 个规则链} other {# 个规则链} }吗?", "description": "说明", - "details": "细节", + "details": "详情", "events": "事件", "export": "导出规则链", "export-failed-error": "无法导出规则链:{{error}}", @@ -1938,9 +1938,9 @@ "rulechain-file": "规则链文件", "rulechain-required": "规则链必填", "rulechains": "规则链库", - "search": "搜索规则链", + "search": "查找规则链", "select-rulechain": "选择规则链", - "selected-rulechains": "已选择 { count, plural, 1 {1 个规则链} other {# 个规则链} }", + "selected-rulechains": "已选择 { count, plural, 1 {# 个规则链} other {# 个规则链} }", "set-root": "设置为根规则链", "set-root-rulechain-text": "确认之后,规则链将变为根规格链,并将处理所有传入的传输消息。", "set-root-rulechain-title": "您确定要生成规则链'{{RuleChainName}}'根吗?", @@ -1961,7 +1961,7 @@ "description": "说明", "deselect-all": "取消选择", "deselect-all-objects": "取消选择所有节点和连接", - "details": "细节", + "details": "详情", "directive-is-not-loaded": "定义的配置指令 '{{directiveName}}' 不可用。", "events": "事件", "help": "帮助", @@ -1985,7 +1985,7 @@ "output": "输出", "reset-debug-mode": "重置所有节点中的调试模式", "rulenode-details": "规则节点详情", - "search": "搜索节点", + "search": "查找节点", "select-all": "选择全部", "select-all-objects": "选择所有节点和连接", "select-message-type": "选择消息类型", @@ -1998,14 +1998,14 @@ "type-enrichment-details": "向消息元数据中添加附加信息", "type-external": "外部的", "type-external-details": "与外部系统交互", - "type-filter": "过滤器", - "type-filter-details": "使用配置条件过滤传入消息", + "type-filter": "筛选器", + "type-filter-details": "使用配置条件筛选传入消息", "type-input": "输入", "type-input-details": "规则链的逻辑输入,将传入消息转发到下一个相关规则节点", "type-rule-chain": "规则链", "type-rule-chain-details": "将传入消息转发到指定的规则链", "type-transformation": "变换", - "type-transformation-details": "更改消息有效载荷和元数据", + "type-transformation-details": "更改消息 Payload 和元数据", "type-unknown": "未知", "type-unknown-details": "未解析的规则节点", "ui-resources-load-error": "加载配置UI资源失败。" @@ -2016,20 +2016,20 @@ "create-new-tenant-profile": "创建一个新的!", "data": "纵断面数据", "default": "默认", - "default-storage-ttl-days": "默认存储TTL天(0-无限制)", + "default-storage-ttl-days": "默认存储 TTL 天数(0-无限制)", "default-storage-ttl-days-range": "不能为负存储天数", "default-storage-ttl-days-required": "默认存储 TTL 天数必填。", "delete": "删除租户配置", "delete-tenant-profile-text": "请注意:确认后,租户配置和所有相关数据将不可。", "delete-tenant-profile-title": "确实要删除租户配置 '{{tenantProfileName}}'吗?", "delete-tenant-profiles-text": "请注意:确认后,所有选定的租户配置将被删除,所有相关数据将不可恢复。", - "delete-tenant-profiles-title": "确定要删除 { count, plural, 1 {1 个租户配置} other {# 个租户配置} }?", + "delete-tenant-profiles-title": "确定要删除 { count, plural, 1 {# 个租户配置} other {# 个租户配置} }?", "description": "说明", "edit": "编辑租户配置", "idCopiedMessage": "租户配置Id已复制到剪贴板", - "max-d-p-storage-days": "最大数据点存储天数(0-无限制)", - "max-d-p-storage-days-range": "最大数据点存储天数不能为负数", - "max-d-p-storage-days-required": "最大数据点存储天数必填。", + "max-d-p-storage-days": "最大日存储数据点数(0-无限制)", + "max-d-p-storage-days-range": "最大日存储数据点数不能为负数", + "max-d-p-storage-days-required": "最大日存储数据点数必填。", "max-emails": "发送的最大电子邮件数(0-无限制)", "max-emails-range": "发送的最大电子邮件数不能为负数", "max-emails-required": "最大电子邮件发送数必填。", @@ -2075,8 +2075,8 @@ "no-tenant-profiles-matching": "找不到与 '{{entity}}' 匹配的租户配置。", "no-tenant-profiles-text": "找不到租户配置", "profile-configuration": "配置设置", - "search": "搜索租户配置", - "selected-tenant-profiles": "已选择 { count, plural, 1 {1 个租户配置} other {# 个租户配置} }", + "search": "查找租户配置", + "selected-tenant-profiles": "已选择 { count, plural, 1 {# 个租户配置} other {# 个租户配置} }", "set-default": "设置该租户配置为默认", "set-default-tenant-profile-text": "确认后,此租户配置将被标记为默认配置,并将用于未指定配置的新租户。", "set-default-tenant-profile-title": "确实要将租户配置 '{{tenantProfileName}}' 设为默认值吗?", @@ -2099,11 +2099,11 @@ "delete": "删除租户", "delete-tenant-text": "小心!确认后,租户和所有相关数据将不可恢复。", "delete-tenant-title": "您确定要删除租户'{{tenantTitle}}'吗?", - "delete-tenants-action-title": "删除 { count, plural, 1 {1 个租户} other {# 个租户} }", + "delete-tenants-action-title": "删除 { count, plural, 1 {# 个租户} other {# 个租户} }", "delete-tenants-text": "小心!确认后,所有选定的租户将被删除,所有相关数据将不可恢复。", - "delete-tenants-title": "确定要删除 {count,plural,1 {1 个租户} other {# 个租户} } 吗?", + "delete-tenants-title": "确定要删除 {count,plural,1 {# 个租户} other {# 个租户} } 吗?", "description": "说明", - "details": "细节", + "details": "详情", "events": "事件", "idCopiedMessage": "租户ID已经复制到粘贴板", "isolated-tb-core": "隔离板芯容器中的加工", @@ -2114,9 +2114,9 @@ "management": "租户管理", "no-tenants-matching": "没有找到符合 '{{entity}}' 的租户", "no-tenants-text": "没有找到租户", - "search": "搜索租户", + "search": "查找租户", "select-tenant": "选择租户", - "selected-tenants": "已选择 { count, plural, 1 {1 个租户} other {# 个租户} }", + "selected-tenants": "已选择 { count, plural, 1 {# 个租户} other {# 个租户} }", "tenant": "租户", "tenant-details": "租客详情", "tenant-required": "租户必填", @@ -2127,13 +2127,13 @@ "timeinterval": { "advanced": "高级", "days": "天", - "days-interval": "{ days, plural, 1 {1 天} other {# 天} }", + "days-interval": "{ days, plural, 1 {# 天} other {# 天} }", "hours": "小时", - "hours-interval": "{ hours, plural, 1 {1 小时} other {# 小时} }", + "hours-interval": "{ hours, plural, 1 {# 小时} other {# 小时} }", "minutes": "分钟", - "minutes-interval": "{ minutes, plural, 1 {1 分} other {# 分} }", + "minutes-interval": "{ minutes, plural, 1 {# 分} other {# 分} }", "seconds": "秒", - "seconds-interval": "{ seconds, plural, 1 {1 秒} other {# 秒} }" + "seconds-interval": "{ seconds, plural, 1 {# 秒} other {# 秒} }" }, "timeunit": { "days": "天", @@ -2143,17 +2143,17 @@ }, "timewindow": { "date-range": "日期范围", - "days": "{ days, plural, 1 { 天 } other {# 天 } }", + "days": "{ days, plural, 1 {# 天 } other {# 天 } }", "edit": "编辑时间窗口", "hide": "隐藏", "history": "历史", - "hours": "{ hours, plural, 0 { 小时 } 1 {1 小时 } other {# 小时 } }", + "hours": "{ hours, plural, 0 {- 小时 } 1 {# 小时 } other {# 小时 } }", "last": "最后", "last-prefix": "最后", - "minutes": "{ minutes, plural, 0 { 分 } 1 {1 分 } other {# 分 } }", + "minutes": "{ minutes, plural, 0 {- 分 } 1 {# 分 } other {# 分 } }", "period": "从 {{ startTime }} 到 {{ endTime }}", "realtime": "实时", - "seconds": "{ seconds, plural, 0 { 秒 } 1 {1 秒 } other {# 秒 } }", + "seconds": "{ seconds, plural, 0 {- 秒 } 1 {# 秒 } other {# 秒 } }", "time-period": "时间段" }, "timezone": { @@ -2179,11 +2179,11 @@ "delete": "删除用户", "delete-user-text": "小心!确认后,用户和所有相关数据将不可恢复。", "delete-user-title": "您确定要删除用户 '{{userEmail}}' 吗?", - "delete-users-action-title": "删除 { count, plural, 1 {1 个用户} other {# 个用户} }", + "delete-users-action-title": "删除 { count, plural, 1 {# 个用户} other {# 个用户} }", "delete-users-text": "小心!确认后,所有选定的用户将被删除,所有相关数据将不可恢复。", - "delete-users-title": "确定要删除 { count, plural, 1 {1 个用户} other {# 个用户} } 吗?", + "delete-users-title": "确定要删除 { count, plural, 1 {# 个用户} other {# 个用户} } 吗?", "description": "说明", - "details": "细节", + "details": "详情", "disable-account": "禁用用户帐户", "disable-account-message": "已成功禁用用户帐户!", "display-activation-link": "显示激活链接", @@ -2199,9 +2199,9 @@ "no-users-matching": "没有找到符合 '{{entity}}' 的用户。", "no-users-text": "找不到用户", "resend-activation": "重新发送激活", - "search": "搜索用户", + "search": "查找用户", "select-user": "选择用户", - "selected-users": "已选择 { count, plural, 1 {1 个用户} other {# 个用户} }", + "selected-users": "已选择 { count, plural, 1 {# 个用户} other {# 个用户} }", "send-activation-mail": "发送激活邮件", "sys-admin": "系统管理员", "tenant-admin": "租户管理员", @@ -2277,12 +2277,12 @@ "drop-shadow": "阴影", "edit-action": "编辑 Action", "enable-fullscreen": "启用全屏", - "general-settings": "常规设置", + "general-settings": "基本设置", "height": "高度", "icon-color": "图标颜色", "icon-size": "图标大小", "margin": "边缘", - "maximum-datasources": "最大允许 { count, plural, 1 {1 个数据源。} other {# 个数据源。} }", + "maximum-datasources": "最大允许 { count, plural, 1 {# 个数据源。} other {# 个数据源。} }", "mobile-mode-settings": "移动端设置", "no-actions-text": "找不到 Action", "order": "顺序", @@ -2344,7 +2344,7 @@ "save-widget-type-as": "部件类型另存为", "save-widget-type-as-text": "请输入新的部件标题或选择目标部件组", "saveAs": "部件另存为", - "search-data": "搜索数据", + "search-data": "查找数据", "select-widget-type": "选择窗口部件类型", "select-widgets-bundle": "选择部件组", "settings-schema": "设置模式", @@ -2373,10 +2373,10 @@ "delete": "删除部件组", "delete-widgets-bundle-text": "小心!确认后,部件组和所有相关数据将不可恢复。", "delete-widgets-bundle-title": "您确定要删除部件组 '{{widgetsBundleTitle}}'吗?", - "delete-widgets-bundles-action-title": "删除 { count, plural, 1 {1 个部件组} other {# 个部件组} }", + "delete-widgets-bundles-action-title": "删除 { count, plural, 1 {# 个部件组} other {# 个部件组} }", "delete-widgets-bundles-text": "小心!确认后,所有选定的部件组将被删除,所有相关数据将不可恢复。", - "delete-widgets-bundles-title": "确定要删除 { count, plural, 1 {1 个部件组} other {# 个部件组} } 吗?", - "details": "细节", + "delete-widgets-bundles-title": "确定要删除 { count, plural, 1 {# 个部件组} other {# 个部件组} } 吗?", + "details": "详情", "empty": "部件组是空的", "export": "导出部件组", "export-failed-error": "无法导出部件组: {{error}}", @@ -2385,8 +2385,8 @@ "no-widgets-bundles-matching": "没有找到与 '{{widgetsBundle}}' 匹配的部件组。", "no-widgets-bundles-text": "找不到部件组", "open-widgets-bundle": "打开部件组", - "search": "搜索部件组", - "selected-widgets-bundles": "已选择 { count, plural, 1 {1 个部件组} other {# 个部件组} }", + "search": "查找部件组", + "selected-widgets-bundles": "已选择 { count, plural, 1 {# 个部件组} other {# 个部件组} }", "system": "系统", "title": "标题", "title-required": "标题必填。",