From d6979e54dae1fd2350ddda9622be6d77d92d3823 Mon Sep 17 00:00:00 2001 From: nickAS21 <44275303+nickAS21@users.noreply.github.com> Date: Thu, 15 Apr 2021 11:53:45 +0300 Subject: [PATCH 1/2] lwm2m: back: Write Attributes - start (#4402) * lwm2m: back: Write Attributes - start * lwm2m: front: fis bug disable instances * lwm2m: transport: attr/discavery start * lwm2m: transport: attributeLwm2m after update profile * lwm2m: transport: attributeLwm2m after update profile (no delete attribute) * Lwm2m: back: test * Lwm2m: back: test2 * Lwm2m: back: test3 --- .../DefaultSystemDataLoaderService.java | 5 - .../secure/LwM2MBootstrapServers.java | 2 +- .../lwm2m/server/LwM2mSessionMsgListener.java | 11 +- .../lwm2m/server/LwM2mTransportHandler.java | 123 ++++++--- .../lwm2m/server/LwM2mTransportRequest.java | 47 +--- .../lwm2m/server/LwM2mTransportService.java | 4 + .../server/LwM2mTransportServiceImpl.java | 236 +++++++++++++----- .../server/client/LwM2mClientContext.java | 3 + .../server/client/LwM2mClientContextImpl.java | 23 +- .../server/client/LwM2mClientProfile.java | 8 + ui-ngx/src/app/core/http/resource.service.ts | 7 +- ui-ngx/src/app/core/services/time.service.ts | 18 +- ui-ngx/src/app/core/utils.ts | 2 +- ...ofile-transport-configuration.component.ts | 25 +- ...wm2m-observe-attr-telemetry.component.html | 3 +- ...wm2m-observe-attr-telemetry.component.scss | 8 + .../widget/lib/json-input-widget.component.ts | 6 +- .../widget/widget-components.module.ts | 3 +- .../asset/assets-table-config.resolver.ts | 2 +- .../dashboards-table-config.resolver.ts | 7 +- .../device/devices-table-config.resolver.ts | 54 ++-- .../home/pages/edge/edge-routing.module.ts | 12 +- .../entity-views-table-config.resolver.ts | 5 +- .../resource/resources-library.component.ts | 6 +- .../rulechains-table-config.resolver.ts | 72 +++--- .../shared/components/file-input.component.ts | 4 + .../components/json-object-edit.component.ts | 2 +- .../src/app/shared/models/resource.models.ts | 2 +- 28 files changed, 441 insertions(+), 259 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index 1ba2ce3470..ec0daf2d71 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -17,10 +17,8 @@ package org.thingsboard.server.service.install; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; -import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Profile; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @@ -60,11 +58,8 @@ import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.query.BooleanFilterPredicate; import org.thingsboard.server.common.data.query.DynamicValue; import org.thingsboard.server.common.data.query.DynamicValueSourceType; -import org.thingsboard.server.common.data.query.EntityKey; -import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.query.EntityKeyValueType; import org.thingsboard.server.common.data.query.FilterPredicateValue; -import org.thingsboard.server.common.data.query.KeyFilter; import org.thingsboard.server.common.data.query.NumericFilterPredicate; import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.security.Authority; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapServers.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapServers.java index e3757701cb..b08f1dbcbe 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapServers.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapServers.java @@ -23,5 +23,5 @@ public class LwM2MBootstrapServers { private Integer lifetime = 300; private Integer defaultMinPeriod = 1; private boolean notifIfDisabled = true; - private String binding = "U"; + private String binding = "UQ"; } 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 20baeba453..0bc78254a6 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 @@ -18,6 +18,7 @@ package org.thingsboard.server.transport.lwm2m.server; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.ResourceType; @@ -74,12 +75,12 @@ public class LwM2mSessionMsgListener implements GenericFutureListener resourceUpdateMsgOpt) { + @Override + public void onResourceUpdate(@NotNull Optional resourceUpdateMsgOpt) { if (ResourceType.LWM2M_MODEL.name().equals(resourceUpdateMsgOpt.get().getResourceType())) { this.service.onResourceUpdate(resourceUpdateMsgOpt); } } - public void onResourceDelete(Optional resourceDeleteMsgOpt) { + @Override + public void onResourceDelete(@NotNull Optional resourceDeleteMsgOpt) { if (ResourceType.LWM2M_MODEL.name().equals(resourceDeleteMsgOpt.get().getResourceType())) { this.service.onResourceDelete(resourceDeleteMsgOpt); } 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 0f0707ddf8..afb8add332 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 @@ -22,15 +22,14 @@ import com.google.gson.JsonSyntaxException; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.eclipse.californium.core.network.config.NetworkConfig; +import org.eclipse.leshan.core.attributes.Attribute; +import org.eclipse.leshan.core.attributes.AttributeSet; import org.eclipse.leshan.core.model.ObjectModel; 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.codec.CodecException; +import org.eclipse.leshan.core.request.DownlinkRequest; +import org.eclipse.leshan.core.request.WriteAttributesRequest; import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.server.californium.LeshanServerBuilder; import org.eclipse.leshan.server.registration.Registration; @@ -44,11 +43,19 @@ import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientProfile; import java.io.File; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.LinkedList; +import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import static org.eclipse.leshan.core.attributes.Attribute.DIMENSION; +import static org.eclipse.leshan.core.attributes.Attribute.MAXIMUM_PERIOD; +import static org.eclipse.leshan.core.attributes.Attribute.MINIMUM_PERIOD; +import static org.eclipse.leshan.core.attributes.Attribute.OBJECT_VERSION; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_KEY; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; @@ -56,8 +63,19 @@ import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPA public class LwM2mTransportHandler { public static final String BASE_DEVICE_API_TOPIC = "v1/devices/me"; + + public static final String CLIENT_LWM2M_SETTINGS = "clientLwM2mSettings"; + 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 OBSERVE_ATTRIBUTE_TELEMETRY = "observeAttr"; public static final String ATTRIBUTE = "attribute"; public static final String TELEMETRY = "telemetry"; + public static final String KEY_NAME = "keyName"; + public static final String OBSERVE = "observe"; + public static final String ATTRIBUTE_LWM2M = "attributeLwm2m"; + private static final String REQUEST = "/request"; private static final String RESPONSE = "/response"; private static final String ATTRIBUTES = "/" + ATTRIBUTE; @@ -70,14 +88,6 @@ public class LwM2mTransportHandler { 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 CLIENT_LWM2M_SETTINGS = "clientLwM2mSettings"; - public static final String KEY_NAME = "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 LOG_LW2M_TELEMETRY = "logLwm2m"; public static final String LOG_LW2M_INFO = "info"; @@ -144,19 +154,19 @@ public class LwM2mTransportHandler { throw new CodecException("Invalid value type for resource %s, type %s", resourcePath, type); } } - - public static LwM2mNode getLvM2mNodeToObject(LwM2mNode content) { - if (content instanceof LwM2mObject) { - return (LwM2mObject) content; - } else if (content instanceof LwM2mObjectInstance) { - return (LwM2mObjectInstance) content; - } else if (content instanceof LwM2mSingleResource) { - return (LwM2mSingleResource) content; - } else if (content instanceof LwM2mMultipleResource) { - return (LwM2mMultipleResource) content; - } - return null; - } +// +// public static LwM2mNode getLvM2mNodeToObject(LwM2mNode content) { +// if (content instanceof LwM2mObject) { +// return (LwM2mObject) content; +// } else if (content instanceof LwM2mObjectInstance) { +// return (LwM2mObjectInstance) content; +// } else if (content instanceof LwM2mSingleResource) { +// return (LwM2mSingleResource) content; +// } else if (content instanceof LwM2mMultipleResource) { +// return (LwM2mMultipleResource) content; +// } +// return null; +// } public static LwM2mClientProfile getNewProfileParameters(JsonObject profilesConfigData, TenantId tenantId) { LwM2mClientProfile lwM2MClientProfile = new LwM2mClientProfile(); @@ -166,6 +176,7 @@ public class LwM2mTransportHandler { lwM2MClientProfile.setPostAttributeProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(ATTRIBUTE).getAsJsonArray()); lwM2MClientProfile.setPostTelemetryProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(TELEMETRY).getAsJsonArray()); lwM2MClientProfile.setPostObserveProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(OBSERVE).getAsJsonArray()); + lwM2MClientProfile.setPostAttributeLwm2mProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(ATTRIBUTE_LWM2M).getAsJsonObject()); return lwM2MClientProfile; } @@ -184,6 +195,9 @@ public class LwM2mTransportHandler { * "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"]} + * "attributeLwm2m": {"/3_1.0": {"ver": "currentTimeTest11"}, + * "/3_1.0/0": {"gt": 17}, + * "/3_1.0/0/9": {"pmax": 45}, "/3_1.2": {ver": "3_1.2"}} */ public static LwM2mClientProfile getLwM2MClientProfileFromThingsboard(DeviceProfile deviceProfile) { if (deviceProfile != null && ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties().size() > 0) { @@ -192,7 +206,7 @@ public class LwM2mTransportHandler { ObjectMapper mapper = new ObjectMapper(); String profileStr = mapper.writeValueAsString(profile); JsonObject profileJson = (profileStr != null) ? validateJson(profileStr) : null; - return (getValidateCredentialsBodyFromThingsboard(profileJson)) ? LwM2mTransportHandler.getNewProfileParameters(profileJson, deviceProfile.getTenantId()) : null; + return getValidateCredentialsBodyFromThingsboard(profileJson) ? LwM2mTransportHandler.getNewProfileParameters(profileJson, deviceProfile.getTenantId()) : null; } catch (IOException e) { log.error("", e); } @@ -240,7 +254,10 @@ public class LwM2mTransportHandler { objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(TELEMETRY).isJsonArray() && objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().has(OBSERVE) && !objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(OBSERVE).isJsonNull() && - objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(OBSERVE).isJsonArray()); + objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(OBSERVE).isJsonArray() && + objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().has(ATTRIBUTE_LWM2M) && + !objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(ATTRIBUTE_LWM2M).isJsonNull() && + objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(ATTRIBUTE_LWM2M).isJsonObject()); } private static boolean getValidateBootstrapProfileFromThingsboard(JsonObject objectMsg) { @@ -360,4 +377,54 @@ public class LwM2mTransportHandler { return ObjectModel.DEFAULT_VERSION; } } + + /** + * As example: + * a)Write-Attributes/3/0/9?pmin=1 means the Battery Level value will be notified + * to the Server with a minimum interval of 1sec; + * this value is set at theResource level. + * b)Write-Attributes/3/0/9?pmin means the Battery Level will be notified + * to the Server with a minimum value (pmin) given by the default one + * (resource 2 of Object Server ID=1), + * or with another value if this Attribute has been set at another level + * (Object or Object Instance: see section5.1.1). + * c)Write-Attributes/3/0?pmin=10 means that all Resources of Instance 0 of the Object ‘Device (ID:3)’ + * will be notified to the Server with a minimum interval of 10 sec; + * this value is set at the Object Instance level. + * d)Write-Attributes /3/0/9?gt=45&st=10 means the Battery Level will be notified to the Server + * when: + * a.old value is 20 and new value is 35 due to step condition + * b.old value is 45 and new value is 50 due to gt condition + * c.old value is 50 and new value is 40 due to both gt and step conditions + * d.old value is 35 and new value is 20 due to step conditione) + * Write-Attributes /3/0/9?lt=20>=85&st=10 means the Battery Level will be notified to the Server + * when: + * a.old value is 17 and new value is 24 due to lt condition + * b.old value is 75 and new value is 90 due to both gt and step conditions + * String uriQueries = "pmin=10&pmax=60"; + * AttributeSet attributes = AttributeSet.parse(uriQueries); + * WriteAttributesRequest request = new WriteAttributesRequest(target, attributes); + * Attribute gt = new Attribute(GREATER_THAN, Double.valueOf("45")); + * Attribute st = new Attribute(LESSER_THAN, Double.valueOf("10")); + * Attribute pmax = new Attribute(MAXIMUM_PERIOD, "60"); + * Attribute [] attrs = {gt, st}; + */ + public static DownlinkRequest createWriteAttributeRequest(String target, Object params) { + AttributeSet attrSet = new AttributeSet(createWriteAttributes(params)); + return attrSet.getAttributes().size() > 0 ? new WriteAttributesRequest(target, attrSet) : null; + } + + private static Attribute[] createWriteAttributes(Object params) { + List attributeLists = new ArrayList(); + ObjectMapper oMapper = new ObjectMapper(); + Map map = oMapper.convertValue(params, ConcurrentHashMap.class); + map.forEach((k, v) -> { + if (!v.toString().isEmpty() || (v.toString().isEmpty() && OBJECT_VERSION.equals(k))) { + attributeLists.add(new Attribute(k, + (DIMENSION.equals(k) || MINIMUM_PERIOD.equals(k) || MAXIMUM_PERIOD.equals(k)) ? + ((Double) v).longValue() : v)); + } + }); + return (Attribute[]) attributeLists.toArray(Attribute[]::new); + } } 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 e237146c8a..2274eaf2a9 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 @@ -18,8 +18,6 @@ package org.thingsboard.server.transport.lwm2m.server; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.core.coap.CoAP; 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; @@ -33,7 +31,6 @@ 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.ReadRequest; -import org.eclipse.leshan.core.request.WriteAttributesRequest; import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.request.exception.ClientSleepingException; import org.eclipse.leshan.core.response.CancelObservationResponse; @@ -60,7 +57,6 @@ import java.util.Date; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import static org.eclipse.leshan.core.attributes.Attribute.MINIMUM_PERIOD; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.DEFAULT_TIMEOUT; 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; @@ -75,6 +71,7 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandle import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.RESPONSE_CHANNEL; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertToIdVerFromObjectId; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertToObjectIdFromIdVer; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.createWriteAttributeRequest; @Slf4j @Service @@ -174,47 +171,7 @@ public class LwM2mTransportRequest { } break; case PUT_TYPE_OPER_WRITE_ATTRIBUTES: - /** - * As example: - * a)Write-Attributes/3/0/9?pmin=1 means the Battery Level value will be notified - * to the Server with a minimum interval of 1sec; - * this value is set at theResource level. - * b)Write-Attributes/3/0/9?pmin means the Battery Level will be notified - * to the Server with a minimum value (pmin) given by the default one - * (resource 2 of Object Server ID=1), - * or with another value if this Attribute has been set at another level - * (Object or Object Instance: see section5.1.1). - * c)Write-Attributes/3/0?pmin=10 means that all Resources of Instance 0 of the Object ‘Device (ID:3)’ - * will be notified to the Server with a minimum interval of 10 sec; - * this value is set at the Object Instance level. - * d)Write-Attributes /3/0/9?gt=45&st=10 means the Battery Level will be notified to the Server - * when: - * a.old value is 20 and new value is 35 due to step condition - * b.old value is 45 and new value is 50 due to gt condition - * c.old value is 50 and new value is 40 due to both gt and step conditions - * d.old value is 35 and new value is 20 due to step conditione) - * Write-Attributes /3/0/9?lt=20>=85&st=10 means the Battery Level will be notified to the Server - * when: - * a.old value is 17 and new value is 24 due to lt condition - * b.old value is 75 and new value is 90 due to both gt and step conditions - * String uriQueries = "pmin=10&pmax=60"; - * AttributeSet attributes = AttributeSet.parse(uriQueries); - * WriteAttributesRequest request = new WriteAttributesRequest(target, attributes); - * Attribute gt = new Attribute(GREATER_THAN, Double.valueOf("45")); - * Attribute st = new Attribute(LESSER_THAN, Double.valueOf("10")); - * Attribute pmax = new Attribute(MAXIMUM_PERIOD, "60"); - * Attribute [] attrs = {gt, st}; - */ - Attribute pmin = new Attribute(MINIMUM_PERIOD, Integer.toUnsignedLong(Integer.parseInt("1"))); - Attribute[] attrs = {pmin}; - AttributeSet attrSet = new AttributeSet(attrs); - 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); - } + request = createWriteAttributeRequest (target, params); break; } 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 fb917f1370..4f3cfc44c8 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 @@ -51,6 +51,10 @@ public interface LwM2mTransportService { void onResourceDelete(Optional resourceDeleteMsgOpt); + void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg toDeviceRequest); + + void onToServerRpcResponse(TransportProtos.ToServerRpcResponseMsg toServerResponse); + void doTrigger(Registration registration, String path); void doDisconnect(TransportProtos.SessionInfoProto sessionInfo); 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 index 96da8721c8..8ab70d3af4 100644 --- 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 @@ -74,11 +74,14 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import static org.eclipse.leshan.core.attributes.Attribute.OBJECT_VERSION; +import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; 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_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.LOG_LW2M_ERROR; @@ -87,6 +90,7 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandle import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LWM2M_STRATEGY_2; 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.PUT_TYPE_OPER_WRITE_ATTRIBUTES; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.SERVICE_CHANNEL; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertToIdVerFromObjectId; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertToObjectIdFromIdVer; @@ -213,7 +217,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { * !!! Warn: if have not finishing unReg, then this operation will be finished on next Client`s connect */ public void unReg(Registration registration, Collection observations) { - executorUnRegistered.submit(() -> { + executorUnRegistered.submit(() -> { try { this.setCancelObservations(registration); this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client unRegistration", registration); @@ -365,17 +369,15 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { } /** - * * @param resourceUpdateMsgOpt - */ @Override - public void onResourceUpdate (Optional resourceUpdateMsgOpt) { + public void onResourceUpdate(Optional resourceUpdateMsgOpt) { String idVer = resourceUpdateMsgOpt.get().getResourceKey(); lwM2mClientContext.getLwM2mClients().values().stream().forEach(e -> e.updateResourceModel(idVer, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getModelProvider())); } /** - * * @param resourceDeleteMsgOpt - */ @Override @@ -384,6 +386,14 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { lwM2mClientContext.getLwM2mClients().values().stream().forEach(e -> e.deleteResources(pathIdVer, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getModelProvider())); } + public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg toDeviceRequest) { + log.info("[{}] toDeviceRpcRequest", toDeviceRequest); + } + + public void onToServerRpcResponse(TransportProtos.ToServerRpcResponseMsg toServerResponse) { + log.info("[{}] toServerRpcResponse", toServerResponse); + } + /** * Trigger Server path = "/1/0/8" *

@@ -496,15 +506,19 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { */ private void initLwM2mFromClientValue(Registration registration, LwM2mClient lwM2MClient) { LwM2mClientProfile lwM2MClientProfile = lwM2mClientContext.getProfile(registration); - Set clientObjects = this.getAllOjectsInClient(registration); - if (clientObjects != null && LWM2M_STRATEGY_2 == LwM2mTransportHandler.getClientOnlyObserveAfterConnect(lwM2MClientProfile)) { - // #2 - lwM2MClient.getPendingRequests().addAll(clientObjects); - clientObjects.forEach(path -> lwM2mTransportRequest.sendAllRequest(registration, path, GET_TYPE_OPER_READ, ContentFormat.TLV.getName(), - null, null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout())); + Set clientObjects = lwM2mClientContext.getSupportedIdVerInClient(registration); + if (clientObjects != null && clientObjects.size() > 0) { + if (LWM2M_STRATEGY_2 == LwM2mTransportHandler.getClientOnlyObserveAfterConnect(lwM2MClientProfile)) { + // #2 + lwM2MClient.getPendingRequests().addAll(clientObjects); + clientObjects.forEach(path -> lwM2mTransportRequest.sendAllRequest(registration, path, GET_TYPE_OPER_READ, ContentFormat.TLV.getName(), + null, null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout())); + } + // #1 + this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, GET_TYPE_OPER_OBSERVE, clientObjects); + this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, PUT_TYPE_OPER_WRITE_ATTRIBUTES, clientObjects); + this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, GET_TYPE_OPER_DISCOVER, clientObjects); } - // #1 - this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, GET_TYPE_OPER_OBSERVE); } /** @@ -611,41 +625,61 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { /** * Start observe/read: Attr/Telemetry - * #1 - Analyze: - * #1.1 path in resource profile == client resource + * #1 - Analyze: path in resource profile == client resource * * @param registration - */ - private void initReadAttrTelemetryObserveToClient(Registration registration, LwM2mClient lwM2MClient, String typeOper) { + private void initReadAttrTelemetryObserveToClient(Registration registration, LwM2mClient lwM2MClient, + String typeOper, Set clientObjects) { LwM2mClientProfile lwM2MClientProfile = lwM2mClientContext.getProfile(registration); - Set clientInstances = this.getAllInstancesInClient(registration); - Set result; + Set result = null; + ConcurrentHashMap params = null; if (GET_TYPE_OPER_READ.equals(typeOper)) { - result = JacksonUtil.fromString(lwM2MClientProfile.getPostAttributeProfile().toString(), new TypeReference<>() { - }); - result.addAll(JacksonUtil.convertValue(lwM2MClientProfile.getPostTelemetryProfile().toString(), new TypeReference<>() { - })); - } else { - result = JacksonUtil.fromString(lwM2MClientProfile.getPostObserveProfile().toString(), new TypeReference<>() { - }); + result = JacksonUtil.fromString(lwM2MClientProfile.getPostAttributeProfile().toString(), + new TypeReference<>() { + }); + result.addAll(JacksonUtil.fromString(lwM2MClientProfile.getPostTelemetryProfile().toString(), + new TypeReference<>() { + })); + } else if (GET_TYPE_OPER_OBSERVE.equals(typeOper)) { + result = JacksonUtil.fromString(lwM2MClientProfile.getPostObserveProfile().toString(), + new TypeReference<>() { + }); + } else if (GET_TYPE_OPER_DISCOVER.equals(typeOper)) { + result = this.getPathForWriteAttributes(lwM2MClientProfile.getPostAttributeLwm2mProfile()).keySet(); + ; + } else if (PUT_TYPE_OPER_WRITE_ATTRIBUTES.equals(typeOper)) { + params = this.getPathForWriteAttributes(lwM2MClientProfile.getPostAttributeLwm2mProfile()); + result = params.keySet(); } - Set pathSend = ConcurrentHashMap.newKeySet(); - result.forEach(target -> { - // #1.1 - String[] resPath = target.split("/"); - String instance = "/" + resPath[1] + "/" + resPath[2]; - if (clientInstances != null && clientInstances.size() > 0 && clientInstances.contains(instance)) { - pathSend.add(target); + if (!result.isEmpty()) { + // #1 + Set pathSend = result.stream().filter(target -> { + return target.split(LWM2M_SEPARATOR_PATH).length < 3 ? + clientObjects.contains("/" + target.split(LWM2M_SEPARATOR_PATH)[1]) : + clientObjects.contains("/" + target.split(LWM2M_SEPARATOR_PATH)[1] + "/" + target.split(LWM2M_SEPARATOR_PATH)[2]); + } + ) + .collect(Collectors.toUnmodifiableSet()); + if (!pathSend.isEmpty()) { + lwM2MClient.getPendingRequests().addAll(pathSend); + ConcurrentHashMap finalParams = params; + pathSend.forEach(target -> lwM2mTransportRequest.sendAllRequest(registration, target, typeOper, ContentFormat.TLV.getName(), + null, finalParams != null ? finalParams.get(target) : null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout())); + if (GET_TYPE_OPER_OBSERVE.equals(typeOper)) { + lwM2MClient.initValue(this, null); + } } - }); - lwM2MClient.getPendingRequests().addAll(pathSend); - pathSend.forEach(target -> lwM2mTransportRequest.sendAllRequest(registration, target, typeOper, ContentFormat.TLV.getName(), - null, null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout())); - if (GET_TYPE_OPER_OBSERVE.equals(typeOper)) { - lwM2MClient.initValue(this, null); } } + private ConcurrentHashMap getPathForWriteAttributes(JsonObject objectJson) { + ConcurrentHashMap pathAttributes = new Gson().fromJson(objectJson.toString(), + new TypeToken>() { + }.getType()); + return pathAttributes; + } + /** * Update parameters device in LwM2MClient * If new deviceProfile != old deviceProfile => update deviceProfile @@ -665,21 +699,6 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { lwM2MClient.setProfileId(device.getDeviceProfileId().getId()); } - /** - * @param registration - - * @return - all object in client - */ - private Set getAllOjectsInClient(Registration registration) { - Set clientObjects = ConcurrentHashMap.newKeySet(); - Arrays.stream(registration.getObjectLinks()).forEach(url -> { - LwM2mPath pathIds = new LwM2mPath(url.getUrl()); - if (pathIds.isObjectInstance()) { - clientObjects.add("/" + pathIds.getObjectId()); - } - }); - return (clientObjects.size() > 0) ? clientObjects : null; - } - /** * @param registration - * @return all instances in client @@ -724,16 +743,20 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { private void addParameters(String path, JsonObject parameters, Registration registration) { LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(registration, null); JsonObject names = lwM2mClientContext.getProfiles().get(lwM2MClient.getProfileId()).getPostKeyNameProfile(); - String resName = names.get(path).getAsString(); - if (resName != null && !resName.isEmpty()) { - try { - String resValue = this.getResourceValueToString(lwM2MClient, path); - if (resValue != null) { - parameters.addProperty(resName, resValue); + if (names != null && names.has(path)) { + String resName = names.get(path).getAsString(); + if (resName != null && !resName.isEmpty()) { + try { + String resValue = this.getResourceValueToString(lwM2MClient, path); + if (resValue != null) { + parameters.addProperty(resName, resValue); + } + } catch (Exception e) { + log.error("Failed to add parameters.", e); } - } catch (Exception e) { - log.error("Failed to add parameters.", e); } + } else { + log.error("Failed to add parameters. path: [{}], names: [{}]", path, names); } } @@ -750,7 +773,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { /** * @param lwM2MClient - - * @param path - + * @param path - * @return - return value of Resource by idPath */ private LwM2mResource returnResourceValueFromLwM2MClient(LwM2mClient lwM2MClient, String path) { @@ -779,6 +802,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { * #3.1 Attribute isChange (add&del) * #3.2 Telemetry isChange (add&del) * #3.3 KeyName isChange (add) + * #3.4 attributeLwm2m isChange (update WrightAttribute: add/update/del) * #4 update * #4.1 add If #3 isChange, then analyze and update Value in Transport form Client and send Value to thingsboard * #4.2 del @@ -789,6 +813,9 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { * -- path Attr/Telemetry includes newObserve and does not include oldObserve: send Request observe to Client * #5.3 Observe.del * -- different between newObserve and oldObserve: send Request cancel observe to client + * #6 + * #6.1 - update WriteAttribute + * #6.2 - del WriteAttribute * * @param registrationIds - * @param deviceProfile - @@ -803,6 +830,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { Set telemetrySetOld = this.convertJsonArrayToSet(telemetryOld); JsonArray observeOld = lwM2MClientProfileOld.getPostObserveProfile(); JsonObject keyNameOld = lwM2MClientProfileOld.getPostKeyNameProfile(); + JsonObject attributeLwm2mOld = lwM2MClientProfileOld.getPostAttributeLwm2mProfile(); LwM2mClientProfile lwM2MClientProfileNew = lwM2mClientContext.getProfiles().get(deviceProfile.getUuidId()); JsonArray attributeNew = lwM2MClientProfileNew.getPostAttributeProfile(); @@ -811,32 +839,41 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { Set telemetrySetNew = this.convertJsonArrayToSet(telemetryNew); JsonArray observeNew = lwM2MClientProfileNew.getPostObserveProfile(); JsonObject keyNameNew = lwM2MClientProfileNew.getPostKeyNameProfile(); + JsonObject attributeLwm2mNew = lwM2MClientProfileNew.getPostAttributeLwm2mProfile(); // #3 ResultsAnalyzerParameters sendAttrToThingsboard = new ResultsAnalyzerParameters(); // #3.1 if (!attributeOld.equals(attributeNew)) { - ResultsAnalyzerParameters postAttributeAnalyzer = this.getAnalyzerParameters(new Gson().fromJson(attributeOld, new TypeToken>() { - }.getType()), attributeSetNew); + ResultsAnalyzerParameters postAttributeAnalyzer = this.getAnalyzerParameters(new Gson().fromJson(attributeOld, + new TypeToken>() { + }.getType()), attributeSetNew); sendAttrToThingsboard.getPathPostParametersAdd().addAll(postAttributeAnalyzer.getPathPostParametersAdd()); sendAttrToThingsboard.getPathPostParametersDel().addAll(postAttributeAnalyzer.getPathPostParametersDel()); } // #3.2 if (!telemetryOld.equals(telemetryNew)) { - ResultsAnalyzerParameters postTelemetryAnalyzer = this.getAnalyzerParameters(new Gson().fromJson(telemetryOld, new TypeToken>() { - }.getType()), telemetrySetNew); + ResultsAnalyzerParameters postTelemetryAnalyzer = this.getAnalyzerParameters(new Gson().fromJson(telemetryOld, + new TypeToken>() { + }.getType()), telemetrySetNew); sendAttrToThingsboard.getPathPostParametersAdd().addAll(postTelemetryAnalyzer.getPathPostParametersAdd()); sendAttrToThingsboard.getPathPostParametersDel().addAll(postTelemetryAnalyzer.getPathPostParametersDel()); } // #3.3 if (!keyNameOld.equals(keyNameNew)) { - ResultsAnalyzerParameters keyNameChange = this.getAnalyzerKeyName(new Gson().fromJson(keyNameOld.toString(), new TypeToken>() { + ResultsAnalyzerParameters keyNameChange = this.getAnalyzerKeyName(new Gson().fromJson(keyNameOld.toString(), + new TypeToken>() { }.getType()), new Gson().fromJson(keyNameNew.toString(), new TypeToken>() { }.getType())); sendAttrToThingsboard.getPathPostParametersAdd().addAll(keyNameChange.getPathPostParametersAdd()); } + // #3.4, #6 + if (!attributeLwm2mOld.equals(attributeLwm2mNew)) { + this.getAnalyzerAttributeLwm2m(registrationIds, attributeLwm2mOld, attributeLwm2mNew); + } + // #4.1 add if (sendAttrToThingsboard.getPathPostParametersAdd().size() > 0) { // update value in Resources @@ -933,7 +970,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { }); } - private ResultsAnalyzerParameters getAnalyzerKeyName(ConcurrentMap keyNameOld, ConcurrentMap keyNameNew) { + private ResultsAnalyzerParameters getAnalyzerKeyName(ConcurrentHashMap keyNameOld, ConcurrentHashMap keyNameNew) { ResultsAnalyzerParameters analyzerParameters = new ResultsAnalyzerParameters(); Set paths = keyNameNew.entrySet() .stream() @@ -943,6 +980,71 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { return analyzerParameters; } + /** + * #3.4, #6 + * #6 + * #6.1 - send update WriteAttribute + * #6.2 - send empty WriteAttribute + * + * @param attributeLwm2mOld - + * @param attributeLwm2mNew - + * @return + */ + private void getAnalyzerAttributeLwm2m(Set registrationIds, JsonObject attributeLwm2mOld, JsonObject attributeLwm2mNew) { + ResultsAnalyzerParameters analyzerParameters = new ResultsAnalyzerParameters(); + ConcurrentHashMap lwm2mAttributesOld = new Gson().fromJson(attributeLwm2mOld.toString(), + new TypeToken>() { + }.getType()); + ConcurrentHashMap lwm2mAttributesNew = new Gson().fromJson(attributeLwm2mNew.toString(), + new TypeToken>() { + }.getType()); + Set pathOld = lwm2mAttributesOld.keySet(); + Set pathNew = lwm2mAttributesNew.keySet(); + analyzerParameters.setPathPostParametersAdd(pathNew + .stream().filter(p -> !pathOld.contains(p)).collect(Collectors.toSet())); + analyzerParameters.setPathPostParametersDel(pathOld + .stream().filter(p -> !pathNew.contains(p)).collect(Collectors.toSet())); + Set pathCommon = pathNew + .stream().filter(p -> pathOld.contains(p)).collect(Collectors.toSet()); + Set pathCommonChange = pathCommon + .stream().filter(p -> !lwm2mAttributesOld.get(p).equals(lwm2mAttributesNew.get(p))).collect(Collectors.toSet()); + analyzerParameters.getPathPostParametersAdd().addAll(pathCommonChange); + // #6 + // #6.2 + if (analyzerParameters.getPathPostParametersAdd().size() > 0) { + registrationIds.forEach(registrationId -> { + Registration registration = this.lwM2mClientContext.getRegistration(registrationId); + Set clientObjects = lwM2mClientContext.getSupportedIdVerInClient(registration); + Set pathSend = analyzerParameters.getPathPostParametersAdd().stream().filter(target -> clientObjects.contains("/" + target.split(LWM2M_SEPARATOR_PATH)[1])) + .collect(Collectors.toUnmodifiableSet()); + if (!pathSend.isEmpty()) { + ConcurrentHashMap finalParams = lwm2mAttributesNew; + pathSend.forEach(target -> lwM2mTransportRequest.sendAllRequest(registration, target, PUT_TYPE_OPER_WRITE_ATTRIBUTES, ContentFormat.TLV.getName(), + null, finalParams.get(target), this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout())); + } + }); + } + // #6.2 + if (analyzerParameters.getPathPostParametersDel().size() > 0) { + registrationIds.forEach(registrationId -> { + Registration registration = this.lwM2mClientContext.getRegistration(registrationId); + Set clientObjects = lwM2mClientContext.getSupportedIdVerInClient(registration); + Set pathSend = analyzerParameters.getPathPostParametersDel().stream().filter(target -> clientObjects.contains("/" + target.split(LWM2M_SEPARATOR_PATH)[1])) + .collect(Collectors.toUnmodifiableSet()); + if (!pathSend.isEmpty()) { + pathSend.forEach(target -> { + Map params = (Map) lwm2mAttributesOld.get(target); + params.clear(); + params.put(OBJECT_VERSION, ""); + lwM2mTransportRequest.sendAllRequest(registration, target, PUT_TYPE_OPER_WRITE_ATTRIBUTES, ContentFormat.TLV.getName(), + null, params, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout()); + }); + } + }); + } + + } + private void cancelObserveIsValue(Registration registration, Set paramAnallyzer) { LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(registration, null); paramAnallyzer.forEach(p -> { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java index 2aea3fdc3e..358f760fef 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java @@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.gen.transport.TransportProtos; import java.util.Map; +import java.util.Set; import java.util.UUID; public interface LwM2mClientContext { @@ -51,4 +52,6 @@ public interface LwM2mClientContext { Map setProfiles(Map profiles); boolean addUpdateProfileParameters(DeviceProfile deviceProfile); + + Set getSupportedIdVerInClient(Registration registration); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java index 926d12516b..1ce16b9b92 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.transport.lwm2m.server.client; +import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.security.EditableSecurityStore; import org.springframework.stereotype.Service; @@ -27,11 +28,14 @@ import org.thingsboard.server.transport.lwm2m.secure.ReadResultSecurityStore; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler; import org.thingsboard.server.transport.lwm2m.utils.TypeServer; +import java.util.Arrays; import java.util.Map; +import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.NO_SEC; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertToIdVerFromObjectId; @Service @TbLwM2mTransportComponent @@ -90,7 +94,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { @Override public LwM2mClient updateInSessionsLwM2MClient(Registration registration) { if (this.lwM2mClients.get(registration.getEndpoint()) == null) { - addLwM2mClientToSession(registration.getEndpoint()); + this.addLwM2mClientToSession(registration.getEndpoint()); } LwM2mClient lwM2MClient = lwM2mClients.get(registration.getEndpoint()); lwM2MClient.setRegistration(registration); @@ -169,4 +173,21 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { } return false; } + + /** + * if isVer - ok or default ver=DEFAULT_LWM2M_VERSION + * @param registration - + * @return - all objectIdVer in client + */ + @Override + public Set getSupportedIdVerInClient(Registration registration) { + Set clientObjects = ConcurrentHashMap.newKeySet(); + Arrays.stream(registration.getObjectLinks()).forEach(url -> { + LwM2mPath pathIds = new LwM2mPath(url.getUrl()); + if (!pathIds.isRoot()) { + clientObjects.add(convertToIdVerFromObjectId(url.getUrl(), registration)); + } + }); + return (clientObjects.size() > 0) ? clientObjects : null; + } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientProfile.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientProfile.java index 1c4042bd1a..19af453f3c 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientProfile.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientProfile.java @@ -56,6 +56,13 @@ public class LwM2mClientProfile { */ private JsonArray postObserveProfile; + /** + * "attributeLwm2m": {"/3_1.0": {"ver": "currentTimeTest11"}, + * "/3_1.0/0": {"gt": 17}, + * "/3_1.0/0/9": {"pmax": 45}, "/3_1.2": {ver": "3_1.2"}} + */ + private JsonObject postAttributeLwm2mProfile; + public LwM2mClientProfile clone() { LwM2mClientProfile lwM2mClientProfile = new LwM2mClientProfile(); lwM2mClientProfile.postClientLwM2mSettings = this.deepCopy(this.postClientLwM2mSettings, JsonObject.class); @@ -63,6 +70,7 @@ public class LwM2mClientProfile { lwM2mClientProfile.postAttributeProfile = this.deepCopy(this.postAttributeProfile, JsonArray.class); lwM2mClientProfile.postTelemetryProfile = this.deepCopy(this.postTelemetryProfile, JsonArray.class); lwM2mClientProfile.postObserveProfile = this.deepCopy(this.postObserveProfile, JsonArray.class); + lwM2mClientProfile.postAttributeLwm2mProfile = this.deepCopy(this.postAttributeLwm2mProfile, JsonObject.class); return lwM2mClientProfile; } diff --git a/ui-ngx/src/app/core/http/resource.service.ts b/ui-ngx/src/app/core/http/resource.service.ts index 6d5a367c86..2fef72fedb 100644 --- a/ui-ngx/src/app/core/http/resource.service.ts +++ b/ui-ngx/src/app/core/http/resource.service.ts @@ -43,14 +43,17 @@ export class ResourceService { } public downloadResource(resourceId: string): Observable { - return this.http.get(`/api/resource/${resourceId}/download`, { responseType: 'arraybuffer', observe: 'response' }).pipe( + return this.http.get(`/api/resource/${resourceId}/download`, { + responseType: 'arraybuffer', + observe: 'response' + }).pipe( map((response) => { const headers = response.headers; const filename = headers.get('x-filename'); const contentType = headers.get('content-type'); const linkElement = document.createElement('a'); try { - const blob = new Blob([response.body], { type: contentType }); + const blob = new Blob([response.body], {type: contentType}); const url = URL.createObjectURL(blob); linkElement.setAttribute('href', url); linkElement.setAttribute('download', filename); diff --git a/ui-ngx/src/app/core/services/time.service.ts b/ui-ngx/src/app/core/services/time.service.ts index 410b150e48..23fc215573 100644 --- a/ui-ngx/src/app/core/services/time.service.ts +++ b/ui-ngx/src/app/core/services/time.service.ts @@ -31,7 +31,7 @@ import { isDefined } from '@core/utils'; export interface TimeInterval { name: string; - translateParams: {[key: string]: any}; + translateParams: { [key: string]: any }; value: number; } @@ -56,14 +56,14 @@ export class TimeService { public loadMaxDatapointsLimit(): Observable { return this.http.get('/api/dashboard/maxDatapointsLimit', defaultHttpOptions(true)).pipe( - map( (limit) => { - this.maxDatapointsLimit = limit; - if (!this.maxDatapointsLimit || this.maxDatapointsLimit <= MIN_LIMIT) { - this.maxDatapointsLimit = MIN_LIMIT + 1; - } - return this.maxDatapointsLimit; - }) - ); + map((limit) => { + this.maxDatapointsLimit = limit; + if (!this.maxDatapointsLimit || this.maxDatapointsLimit <= MIN_LIMIT) { + this.maxDatapointsLimit = MIN_LIMIT + 1; + } + return this.maxDatapointsLimit; + }) + ); } public matchesExistingInterval(min: number, max: number, intervalMs: number): boolean { diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index 225a1398b0..b2d7b8b2a7 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -291,7 +291,7 @@ export function deepClone(target: T, ignoreFields?: string[]): T { return cp.map((n: any) => deepClone(n)) as any; } if (typeof target === 'object' && target !== {}) { - const cp = { ...(target as { [key: string]: any }) } as { [key: string]: any }; + const cp = {...(target as { [key: string]: any })} as { [key: string]: any }; Object.keys(cp).forEach(k => { if (!ignoreFields || ignoreFields.indexOf(k) === -1) { cp[k] = deepClone(cp[k]); 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 4231ab35ff..a9805c528b 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,12 +14,12 @@ /// limitations under the License. /// -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 { DeviceProfileTransportConfiguration } 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 { ATTRIBUTE, DEFAULT_BINDING, @@ -34,11 +34,11 @@ import { RESOURCES, TELEMETRY } from './lwm2m-profile-config.models'; -import {DeviceProfileService} from '@core/http/device-profile.service'; -import {deepClone, isDefinedAndNotNull, isEmpty, isUndefined} from '@core/utils'; -import {WINDOW} from '@core/services/window.service'; -import {JsonArray, JsonObject} from '@angular/compiler-cli/ngcc/src/packages/entry_point'; -import {Direction} from '@shared/models/page/sort-order'; +import { DeviceProfileService } from '@core/http/device-profile.service'; +import { deepClone, isDefinedAndNotNull, isEmpty, isUndefined } from '@core/utils'; +import { WINDOW } from '@core/services/window.service'; +import { JsonArray, JsonObject } from '@angular/compiler-cli/ngcc/src/packages/entry_point'; +import { Direction } from '@shared/models/page/sort-order'; @Component({ selector: 'tb-profile-lwm2m-device-transport-configuration', @@ -173,7 +173,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro } private updateObserveAttrTelemetryObjectFormGroup = (objectsList: ObjectLwM2M[]): void => { - this.lwm2mDeviceProfileFormGroup.patchValue({ + this.lwm2mDeviceProfileFormGroup.patchValue({ observeAttrTelemetry: deepClone(this.getObserveAttrTelemetryObjects(objectsList)) }, {emitEvent: false}); @@ -358,6 +358,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro this.configurationValue.observeAttr.observe = observeArray; this.configurationValue.observeAttr.attribute = attributeArray; this.configurationValue.observeAttr.telemetry = telemetryArray; + this.configurationValue.observeAttr.keyName = this.sortObjectKeyPathJson(KEY_NAME, keyNameNew); this.configurationValue.observeAttr.attributeLwm2m = attributeLwm2m; } } 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 2f16e8677c..759e880ffa 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 @@ -47,6 +47,7 @@

-
+
{ + this.attributeUpdateFormGroup.valueChanges.subscribe(() => { this.ctx.detectChanges(); }); } @@ -191,14 +191,14 @@ export class JsonInputWidgetComponent extends PageComponent implements OnInit { saveAttributeObservable = this.attributeService.saveEntityAttributes( entityId, this.settings.attributeScope, - [ attributeToSave ], + [attributeToSave], {} ); } else { saveAttributeObservable = this.attributeService.saveEntityTimeseries( entityId, LatestTelemetry.LATEST_TELEMETRY, - [ attributeToSave ], + [attributeToSave], {} ); } diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts index 801ec12dc7..b55c21b179 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts @@ -87,4 +87,5 @@ import { JsonInputWidgetComponent } from '@home/components/widget/lib/json-input ImportExportService ] }) -export class WidgetComponentsModule { } +export class WidgetComponentsModule { +} diff --git a/ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts index 4ea3a459cd..ae24710467 100644 --- a/ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts @@ -89,7 +89,7 @@ export class AssetsTableConfigResolver implements Resolve this.translate.instant('asset.delete-asset-title', { assetName: asset.name }); + this.config.deleteEntityTitle = asset => this.translate.instant('asset.delete-asset-title', {assetName: asset.name}); this.config.deleteEntityContent = () => this.translate.instant('asset.delete-asset-text'); this.config.deleteEntitiesTitle = count => this.translate.instant('asset.delete-assets-title', {count}); this.config.deleteEntitiesContent = () => this.translate.instant('asset.delete-assets-text'); diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts index 0a85c81375..c572fa958f 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts @@ -93,7 +93,7 @@ export class DashboardsTableConfigResolver implements Resolve - this.translate.instant('dashboard.delete-dashboard-title', { dashboardTitle: dashboard.title }); + this.translate.instant('dashboard.delete-dashboard-title', {dashboardTitle: dashboard.title}); this.config.deleteEntityContent = () => this.translate.instant('dashboard.delete-dashboard-text'); this.config.deleteEntitiesTitle = count => this.translate.instant('dashboard.delete-dashboards-title', {count}); this.config.deleteEntitiesContent = () => this.translate.instant('dashboard.delete-dashboards-text'); @@ -363,8 +363,7 @@ export class DashboardsTableConfigResolver implements Resolve { - this.config.table.updateData(); + this.config.table.updateData(); }); } ); diff --git a/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts index 36b93fa697..0a6c4e9610 100644 --- a/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts @@ -97,7 +97,7 @@ export class DevicesTableConfigResolver implements Resolve this.translate.instant('device.delete-device-title', { deviceName: device.name }); + this.config.deleteEntityTitle = device => this.translate.instant('device.delete-device-title', {deviceName: device.name}); this.config.deleteEntityContent = () => this.translate.instant('device.delete-device-text'); this.config.deleteEntitiesTitle = count => this.translate.instant('device.delete-devices-title', {count}); this.config.deleteEntitiesContent = () => this.translate.instant('device.delete-devices-text'); @@ -109,7 +109,7 @@ export class DevicesTableConfigResolver implements Resolve this.deviceService.getDeviceInfo(savedDevice.id.id) - )); + )); }; this.config.onEntityAction = action => this.onDeviceAction(action); this.config.detailsReadonly = () => (this.config.componentsData.deviceScope === 'customer_user' || this.config.componentsData.deviceScope === 'edge_customer_user'); @@ -208,7 +208,7 @@ export class DevicesTableConfigResolver implements Resolve this.deviceService.getCustomerDeviceInfosByDeviceProfileId(this.customerId, pageLink, this.config.componentsData.deviceProfileId !== null ? - this.config.componentsData.deviceProfileId.id : ''); + this.config.componentsData.deviceProfileId.id : ''); this.config.deleteEntity = id => this.deviceService.unassignDeviceFromCustomer(id.id); } } @@ -250,26 +250,26 @@ export class DevicesTableConfigResolver implements Resolve (entity.customerId && entity.customerId.id !== NULL_UUID && !entity.customerIsPublic), - onAction: ($event, entity) => this.unassignFromCustomer($event, entity) - }, - { - name: this.translate.instant('device.make-private'), - icon: 'reply', - isEnabled: (entity) => (entity.customerId && entity.customerId.id !== NULL_UUID && entity.customerIsPublic), - onAction: ($event, entity) => this.unassignFromCustomer($event, entity) - }, - { - name: this.translate.instant('device.manage-credentials'), - icon: 'security', - isEnabled: () => true, - onAction: ($event, entity) => this.manageCredentials($event, entity) - } - ); + actions.push( + { + name: this.translate.instant('device.unassign-from-customer'), + icon: 'assignment_return', + isEnabled: (entity) => (entity.customerId && entity.customerId.id !== NULL_UUID && !entity.customerIsPublic), + onAction: ($event, entity) => this.unassignFromCustomer($event, entity) + }, + { + name: this.translate.instant('device.make-private'), + icon: 'reply', + isEnabled: (entity) => (entity.customerId && entity.customerId.id !== NULL_UUID && entity.customerIsPublic), + onAction: ($event, entity) => this.unassignFromCustomer($event, entity) + }, + { + name: this.translate.instant('device.manage-credentials'), + icon: 'security', + isEnabled: () => true, + onAction: ($event, entity) => this.manageCredentials($event, entity) + } + ); } if (deviceScope === 'customer_user' || deviceScope === 'edge_customer_user') { actions.push( @@ -452,10 +452,10 @@ export class DevicesTableConfigResolver implements Resolve { - if (res) { - this.config.table.updateData(); - } - }); + if (res) { + this.config.table.updateData(); + } + }); } unassignFromCustomer($event: Event, device: DeviceInfo) { diff --git a/ui-ngx/src/app/modules/home/pages/edge/edge-routing.module.ts b/ui-ngx/src/app/modules/home/pages/edge/edge-routing.module.ts index c2eff33a11..29f5ab3579 100644 --- a/ui-ngx/src/app/modules/home/pages/edge/edge-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/edge/edge-routing.module.ts @@ -33,8 +33,11 @@ import { RuleChainType } from '@shared/models/rule-chain.models'; import { importRuleChainBreadcumbLabelFunction, ResolvedRuleChainMetaDataResolver, - ruleChainBreadcumbLabelFunction, RuleChainImportGuard, - RuleChainResolver, RuleNodeComponentsResolver, TooltipsterResolver + ruleChainBreadcumbLabelFunction, + RuleChainImportGuard, + RuleChainResolver, + RuleNodeComponentsResolver, + TooltipsterResolver } from '@home/pages/rulechain/rulechain-routing.module'; const routes: Routes = [ @@ -249,7 +252,7 @@ const routes: Routes = [ } ] } - ] + ] }]; @NgModule({ @@ -259,4 +262,5 @@ const routes: Routes = [ EdgesTableConfigResolver ] }) -export class EdgeRoutingModule { } +export class EdgeRoutingModule { +} diff --git a/ui-ngx/src/app/modules/home/pages/entity-view/entity-views-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/entity-view/entity-views-table-config.resolver.ts index 04c7469f71..730ac2603a 100644 --- a/ui-ngx/src/app/modules/home/pages/entity-view/entity-views-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/entity-view/entity-views-table-config.resolver.ts @@ -89,7 +89,7 @@ export class EntityViewsTableConfigResolver implements Resolve - this.translate.instant('entity-view.delete-entity-view-title', { entityViewName: entityView.name }); + this.translate.instant('entity-view.delete-entity-view-title', {entityViewName: entityView.name}); this.config.deleteEntityContent = () => this.translate.instant('entity-view.delete-entity-view-text'); this.config.deleteEntitiesTitle = count => this.translate.instant('entity-view.delete-entity-views-title', {count}); this.config.deleteEntitiesContent = () => this.translate.instant('entity-view.delete-entity-views-text'); @@ -143,8 +143,7 @@ export class EntityViewsTableConfigResolver implements Resolve this.config.tableTitle = edge.name + ': ' + this.translate.instant('entity-view.entity-views') ); - } - else { + } else { this.config.tableTitle = this.translate.instant('entity-view.entity-views'); } this.config.columns = this.configureColumns(this.config.componentsData.entityViewScope); diff --git a/ui-ngx/src/app/modules/home/pages/resource/resources-library.component.ts b/ui-ngx/src/app/modules/home/pages/resource/resources-library.component.ts index 5f7a35c5b9..ce26499102 100644 --- a/ui-ngx/src/app/modules/home/pages/resource/resources-library.component.ts +++ b/ui-ngx/src/app/modules/home/pages/resource/resources-library.component.ts @@ -90,8 +90,10 @@ export class ResourcesLibraryComponent extends EntityComponent impleme buildForm(entity: Resource): FormGroup { return this.fb.group( { - resourceType: [{value: entity?.resourceType ? entity.resourceType : ResourceType.LWM2M_MODEL, - disabled: this.isEdit }, [Validators.required]], + resourceType: [{ + value: entity?.resourceType ? entity.resourceType : ResourceType.LWM2M_MODEL, + disabled: this.isEdit + }, [Validators.required]], data: [entity ? entity.data : null, [Validators.required]], fileName: [entity ? entity.fileName : null, [Validators.required]], title: [entity ? entity.title : '', []] diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts index b27fc2e997..37a21cab2e 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts @@ -73,7 +73,7 @@ export class RuleChainsTableConfigResolver implements Resolve this.translate.instant('rulechain.delete-rulechain-title', - { ruleChainName: ruleChain.name }); + {ruleChainName: ruleChain.name}); this.config.deleteEntityContent = () => this.translate.instant('rulechain.delete-rulechain-text'); this.config.deleteEntitiesTitle = count => this.translate.instant('rulechain.delete-rulechains-title', {count}); this.config.deleteEntitiesContent = () => this.translate.instant('rulechain.delete-rulechains-text'); @@ -121,11 +121,11 @@ export class RuleChainsTableConfigResolver implements Resolve('root', 'rulechain.root', '60px', entity => { - if (ruleChainScope === 'edge') { - return checkBoxCell((this.config.componentsData.edge.rootRuleChainId.id === entity.id.id)); - } else { - return checkBoxCell(entity.root); - } + if (ruleChainScope === 'edge') { + return checkBoxCell((this.config.componentsData.edge.rootRuleChainId.id === entity.id.id)); + } else { + return checkBoxCell(entity.root); + } }) ); } else if (ruleChainScope === 'edges') { @@ -421,32 +421,32 @@ export class RuleChainsTableConfigResolver implements Resolve { - if (res) { - this.edgeService.findMissingToRelatedRuleChains(this.config.componentsData.edgeId).subscribe( - (missingRuleChains) => { - if (missingRuleChains && Object.keys(missingRuleChains).length > 0) { - const formattedMissingRuleChains: Array = new Array(); - for (const missingRuleChain of Object.keys(missingRuleChains)) { - const arrayOfMissingRuleChains = missingRuleChains[missingRuleChain]; - const tmp = '- \'' + missingRuleChain + '\': \'' + arrayOfMissingRuleChains.join('\', ') + '\''; - formattedMissingRuleChains.push(tmp); - } - const message = this.translate.instant('edge.missing-related-rule-chains-text', - {missingRuleChains: formattedMissingRuleChains.join('
')}); - this.dialogService.alert(this.translate.instant('edge.missing-related-rule-chains-title'), - message, this.translate.instant('action.close'), true).subscribe( - () => { - this.config.table.updateData(); + if (res) { + this.edgeService.findMissingToRelatedRuleChains(this.config.componentsData.edgeId).subscribe( + (missingRuleChains) => { + if (missingRuleChains && Object.keys(missingRuleChains).length > 0) { + const formattedMissingRuleChains: Array = new Array(); + for (const missingRuleChain of Object.keys(missingRuleChains)) { + const arrayOfMissingRuleChains = missingRuleChains[missingRuleChain]; + const tmp = '- \'' + missingRuleChain + '\': \'' + arrayOfMissingRuleChains.join('\', ') + '\''; + formattedMissingRuleChains.push(tmp); } - ); - } else { - this.config.table.updateData(); + const message = this.translate.instant('edge.missing-related-rule-chains-text', + {missingRuleChains: formattedMissingRuleChains.join('
')}); + this.dialogService.alert(this.translate.instant('edge.missing-related-rule-chains-title'), + message, this.translate.instant('action.close'), true).subscribe( + () => { + this.config.table.updateData(); + } + ); + } else { + this.config.table.updateData(); + } } - } - ); + ); + } } - } - ); + ); } unassignFromEdge($event: Event, ruleChain: RuleChain) { @@ -510,13 +510,13 @@ export class RuleChainsTableConfigResolver implements Resolve { - if (res) { - this.ruleChainService.setAutoAssignToEdgeRuleChain(ruleChain.id.id).subscribe( - () => { - this.config.table.updateData(); - } - ); - } + if (res) { + this.ruleChainService.setAutoAssignToEdgeRuleChain(ruleChain.id.id).subscribe( + () => { + this.config.table.updateData(); + } + ); + } } ); } diff --git a/ui-ngx/src/app/shared/components/file-input.component.ts b/ui-ngx/src/app/shared/components/file-input.component.ts index 0fcaf8b790..09f9c1b17c 100644 --- a/ui-ngx/src/app/shared/components/file-input.component.ts +++ b/ui-ngx/src/app/shared/components/file-input.component.ts @@ -73,9 +73,11 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, contentConvertFunction: (content: string) => any; private requiredValue: boolean; + get required(): boolean { return this.requiredValue; } + @Input() set required(value: boolean) { const newVal = coerceBooleanProperty(value); @@ -85,9 +87,11 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, } private requiredAsErrorValue: boolean; + get requiredAsError(): boolean { return this.requiredAsErrorValue; } + @Input() set requiredAsError(value: boolean) { const newVal = coerceBooleanProperty(value); diff --git a/ui-ngx/src/app/shared/components/json-object-edit.component.ts b/ui-ngx/src/app/shared/components/json-object-edit.component.ts index d82b66e7b6..78652faa18 100644 --- a/ui-ngx/src/app/shared/components/json-object-edit.component.ts +++ b/ui-ngx/src/app/shared/components/json-object-edit.component.ts @@ -22,7 +22,7 @@ import { ActionNotificationHide, ActionNotificationShow } from '@core/notificati import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { CancelAnimationFrame, RafService } from '@core/services/raf.service'; -import { guid, isUndefined, isDefinedAndNotNull, isLiteralObject } from '@core/utils'; +import { guid, isDefinedAndNotNull, isLiteralObject, isUndefined } from '@core/utils'; import { ResizeObserver } from '@juggle/resize-observer'; import { getAce } from '@shared/models/ace/ace.models'; diff --git a/ui-ngx/src/app/shared/models/resource.models.ts b/ui-ngx/src/app/shared/models/resource.models.ts index 154f3c65f9..082d09ac24 100644 --- a/ui-ngx/src/app/shared/models/resource.models.ts +++ b/ui-ngx/src/app/shared/models/resource.models.ts @@ -45,7 +45,7 @@ export const ResourceTypeTranslationMap = new Map( [ResourceType.LWM2M_MODEL, 'LWM2M model'], [ResourceType.PKCS_12, 'PKCS #12'], [ResourceType.JKS, 'JKS'] - ] + ] ); export interface ResourceInfo extends BaseData { From e08d1b2c0734596fa6e01f5dac3f70daf1c43112 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Thu, 15 Apr 2021 12:05:04 +0300 Subject: [PATCH 2/2] Feature/event filters * Event filter implementation * Removed redundant imports * convert createdTime to column name * UI: Added ui to filter events parameters * Case sensitivity fixed, boolean Is Error added * UI: Added support event filter for error * Improvement for implementation of Event Filter * UI: Refactoring event filter * UI: Add trim * Filter added for all event types * UI: Updated filter events for new API * Refactoring * Code cleaning. Debug Rule Chain added * Removed redundant casts in JpaBaseEventDao, correct created_time check * Removed redundant abstract method in DebugEvent * refactoring of the filter queries * Filters for debug events * UI: Refactoring event table panel Co-authored-by: AndrewVolosytnykhThingsboard Co-authored-by: Vladyslav_Prykhodko Co-authored-by: AndrewVolosytnykhThingsboard <77969531+AndrewVolosytnykhThingsboard@users.noreply.github.com> --- .../server/controller/EventController.java | 37 ++++ .../src/main/resources/thingsboard.yml | 4 +- .../server/dao/event/EventService.java | 3 + .../server/common/data/event/DebugEvent.java | 45 +++++ .../data/event/DebugRuleChainEventFilter.java | 23 +++ .../data/event/DebugRuleNodeEventFilter.java | 23 +++ .../common/data/event/ErrorEventFilter.java | 36 ++++ .../server/common/data/event/EventFilter.java | 40 ++++ .../server/common/data/event/EventType.java | 20 ++ .../data/event/LifeCycleEventFilter.java | 37 ++++ .../data/event/StatisticsEventFilter.java | 36 ++++ .../server/dao/event/BaseEventService.java | 7 +- .../server/dao/event/EventDao.java | 3 + .../server/dao/sql/event/EventRepository.java | 171 +++++++++++++++++- .../server/dao/sql/event/JpaBaseEventDao.java | 106 +++++++++++ ui-ngx/src/app/core/http/event.service.ts | 8 +- .../event/event-filter-panel.component.html | 64 +++++++ .../event/event-filter-panel.component.scss | 35 ++++ .../event/event-filter-panel.component.ts | 101 +++++++++++ .../components/event/event-table-config.ts | 122 ++++++++++++- .../components/event/event-table.component.ts | 13 +- .../home/components/home-components.module.ts | 2 + ui-ngx/src/app/shared/models/event.models.ts | 34 ++++ .../assets/locale/locale.constant-cs_CZ.json | 5 +- .../assets/locale/locale.constant-de_DE.json | 5 +- .../assets/locale/locale.constant-el_GR.json | 5 +- .../assets/locale/locale.constant-en_US.json | 8 +- .../assets/locale/locale.constant-es_ES.json | 5 +- .../assets/locale/locale.constant-fa_IR.json | 5 +- .../assets/locale/locale.constant-fr_FR.json | 5 +- .../assets/locale/locale.constant-it_IT.json | 5 +- .../assets/locale/locale.constant-ja_JA.json | 5 +- .../assets/locale/locale.constant-ka_GE.json | 5 +- .../assets/locale/locale.constant-ko_KR.json | 5 +- .../assets/locale/locale.constant-lv_LV.json | 5 +- .../assets/locale/locale.constant-pt_BR.json | 5 +- .../assets/locale/locale.constant-ro_RO.json | 9 +- .../assets/locale/locale.constant-ru_RU.json | 5 +- .../assets/locale/locale.constant-sl_SI.json | 5 +- .../assets/locale/locale.constant-tr_TR.json | 5 +- .../assets/locale/locale.constant-uk_UA.json | 5 +- .../assets/locale/locale.constant-zh_CN.json | 5 +- .../assets/locale/locale.constant-zh_TW.json | 5 +- 43 files changed, 1018 insertions(+), 59 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/event/DebugEvent.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/event/DebugRuleChainEventFilter.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/event/DebugRuleNodeEventFilter.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/event/ErrorEventFilter.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/event/EventFilter.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/event/EventType.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/event/LifeCycleEventFilter.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/event/StatisticsEventFilter.java create mode 100644 ui-ngx/src/app/modules/home/components/event/event-filter-panel.component.html create mode 100644 ui-ngx/src/app/modules/home/components/event/event-filter-panel.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/event/event-filter-panel.component.ts diff --git a/application/src/main/java/org/thingsboard/server/controller/EventController.java b/application/src/main/java/org/thingsboard/server/controller/EventController.java index ac9e5a3ca2..7e49ba55d4 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EventController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EventController.java @@ -18,12 +18,14 @@ package org.thingsboard.server.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; 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.server.common.data.Event; +import org.thingsboard.server.common.data.event.EventFilter; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; @@ -31,6 +33,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.dao.event.EventService; +import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.permission.Operation; @@ -101,4 +104,38 @@ public class EventController extends BaseController { } } + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/events/{entityType}/{entityId}", method = RequestMethod.POST) + @ResponseBody + public PageData getEvents( + @PathVariable("entityType") String strEntityType, + @PathVariable("entityId") String strEntityId, + @RequestParam("tenantId") String strTenantId, + @RequestParam int pageSize, + @RequestParam int page, + @RequestBody EventFilter eventFilter, + @RequestParam(required = false) String textSearch, + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder, + @RequestParam(required = false) Long startTime, + @RequestParam(required = false) Long endTime) throws ThingsboardException { + checkParameter("EntityId", strEntityId); + checkParameter("EntityType", strEntityType); + try { + TenantId tenantId = new TenantId(toUUID(strTenantId)); + + EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId); + checkEntityId(entityId, Operation.READ); + + if(sortProperty != null && sortProperty.equals("createdTime") && eventFilter.hasFilterForJsonBody()) { + sortProperty = ModelConstants.CREATED_TIME_PROPERTY; + } + + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); + return checkNotNull(eventService.findEventsByFilter(tenantId, entityId, eventFilter, pageLink)); + } catch (Exception e) { + throw handleException(e); + } + } + } diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index e53dd65c28..c5e6c69ff5 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -322,7 +322,7 @@ actors: # Enable/disable actor statistics enabled: "${ACTORS_STATISTICS_ENABLED:true}" js_print_interval_ms: "${ACTORS_JS_STATISTICS_PRINT_INTERVAL_MS:10000}" - persist_frequency: "${ACTORS_STATISTICS_PERSIST_FREQUENCY:3600000}" + persist_frequency: "${ACTORS_STATISTICS_PERSIST_FREQUENCY:10000}" cache: # caffeine or redis @@ -516,7 +516,7 @@ js: # Built-in JVM JavaScript environment properties local: # Use Sandboxed (secured) JVM JavaScript environment - use_js_sandbox: "${USE_LOCAL_JS_SANDBOX:false}" + use_js_sandbox: "${USE_LOCAL_JS_SANDBOX:true}" # Specify thread pool size for JavaScript sandbox resource monitor monitor_thread_pool_size: "${LOCAL_JS_SANDBOX_MONITOR_THREAD_POOL_SIZE:4}" # Maximum CPU time in milliseconds allowed for script execution diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java index 2bcb0d3df0..ea25568375 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.event; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.Event; +import org.thingsboard.server.common.data.event.EventFilter; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; @@ -41,6 +42,8 @@ public interface EventService { List findLatestEvents(TenantId tenantId, EntityId entityId, String eventType, int limit); + PageData findEventsByFilter(TenantId tenantId, EntityId entityId, EventFilter eventFilter, TimePageLink pageLink); + void removeEvents(TenantId tenantId, EntityId entityId); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/event/DebugEvent.java b/common/data/src/main/java/org/thingsboard/server/common/data/event/DebugEvent.java new file mode 100644 index 0000000000..0e5bcbd015 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/event/DebugEvent.java @@ -0,0 +1,45 @@ +/** + * 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.common.data.event; + +import lombok.Data; +import org.eclipse.leshan.core.util.StringUtils; + +@Data +public abstract class DebugEvent implements EventFilter { + + private String msgDirectionType; + private String server; + private String dataSearch; + private String metadataSearch; + private String entityName; + private String relationType; + private String entityId; + private String msgType; + private boolean isError; + private String error; + + public void setIsError(boolean isError) { + this.isError = isError; + } + + @Override + public boolean hasFilterForJsonBody() { + return !StringUtils.isEmpty(msgDirectionType) || !StringUtils.isEmpty(server) || !StringUtils.isEmpty(dataSearch) || !StringUtils.isEmpty(metadataSearch) + || !StringUtils.isEmpty(entityName) || !StringUtils.isEmpty(relationType) || !StringUtils.isEmpty(entityId) || !StringUtils.isEmpty(msgType) || !StringUtils.isEmpty(error) || isError; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/event/DebugRuleChainEventFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/event/DebugRuleChainEventFilter.java new file mode 100644 index 0000000000..cc16d780f0 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/event/DebugRuleChainEventFilter.java @@ -0,0 +1,23 @@ +/** + * 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.common.data.event; + +public class DebugRuleChainEventFilter extends DebugEvent { + @Override + public EventType getEventType() { + return EventType.DEBUG_RULE_CHAIN; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/event/DebugRuleNodeEventFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/event/DebugRuleNodeEventFilter.java new file mode 100644 index 0000000000..abe73e6e85 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/event/DebugRuleNodeEventFilter.java @@ -0,0 +1,23 @@ +/** + * 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.common.data.event; + +public class DebugRuleNodeEventFilter extends DebugEvent { + @Override + public EventType getEventType() { + return EventType.DEBUG_RULE_NODE; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/event/ErrorEventFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/event/ErrorEventFilter.java new file mode 100644 index 0000000000..59e6297a47 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/event/ErrorEventFilter.java @@ -0,0 +1,36 @@ +/** + * 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.common.data.event; + +import lombok.Data; +import org.eclipse.leshan.core.util.StringUtils; + +@Data +public class ErrorEventFilter implements EventFilter { + private String server; + private String method; + private String error; + + @Override + public EventType getEventType() { + return EventType.ERROR; + } + + @Override + public boolean hasFilterForJsonBody() { + return !StringUtils.isEmpty(server) || !StringUtils.isEmpty(method) || !StringUtils.isEmpty(error); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/event/EventFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/event/EventFilter.java new file mode 100644 index 0000000000..eebab7e7d4 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/event/EventFilter.java @@ -0,0 +1,40 @@ +/** + * 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.common.data.event; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.PROPERTY, + property = "eventType") +@JsonSubTypes({ + @JsonSubTypes.Type(value = DebugRuleNodeEventFilter.class, name = "DEBUG_RULE_NODE"), + @JsonSubTypes.Type(value = DebugRuleChainEventFilter.class, name = "DEBUG_RULE_CHAIN"), + @JsonSubTypes.Type(value = ErrorEventFilter.class, name = "ERROR"), + @JsonSubTypes.Type(value = LifeCycleEventFilter.class, name = "LC_EVENT"), + @JsonSubTypes.Type(value = StatisticsEventFilter.class, name = "STATS") +}) +public interface EventFilter { + @JsonIgnore + EventType getEventType(); + + boolean hasFilterForJsonBody(); + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/event/EventType.java b/common/data/src/main/java/org/thingsboard/server/common/data/event/EventType.java new file mode 100644 index 0000000000..a0700fdfbc --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/event/EventType.java @@ -0,0 +1,20 @@ +/** + * 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.common.data.event; + +public enum EventType { + ERROR, LC_EVENT, STATS, DEBUG_RULE_NODE, DEBUG_RULE_CHAIN +} \ No newline at end of file diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/event/LifeCycleEventFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/event/LifeCycleEventFilter.java new file mode 100644 index 0000000000..4936a83e3c --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/event/LifeCycleEventFilter.java @@ -0,0 +1,37 @@ +/** + * 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.common.data.event; + +import lombok.Data; +import org.eclipse.leshan.core.util.StringUtils; + +@Data +public class LifeCycleEventFilter implements EventFilter { + private String server; + private String event; + private String status; + private String error; + + @Override + public EventType getEventType() { + return EventType.LC_EVENT; + } + + @Override + public boolean hasFilterForJsonBody() { + return !StringUtils.isEmpty(server) || !StringUtils.isEmpty(event) || !StringUtils.isEmpty(status) || !StringUtils.isEmpty(error); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/event/StatisticsEventFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/event/StatisticsEventFilter.java new file mode 100644 index 0000000000..0d28ea27e9 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/event/StatisticsEventFilter.java @@ -0,0 +1,36 @@ +/** + * 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.common.data.event; + +import lombok.Data; +import org.eclipse.leshan.core.util.StringUtils; + +@Data +public class StatisticsEventFilter implements EventFilter { + private String server; + private Integer messagesProcessed; + private Integer errorsOccurred; + + @Override + public EventType getEventType() { + return EventType.STATS; + } + + @Override + public boolean hasFilterForJsonBody() { + return !StringUtils.isEmpty(server) || (messagesProcessed != null && messagesProcessed > 0) || (errorsOccurred != null && errorsOccurred > 0); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java b/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java index 4cc853b2b9..91bfa954fd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java @@ -23,6 +23,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.Event; +import org.thingsboard.server.common.data.event.EventFilter; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; @@ -30,7 +31,6 @@ import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; -import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Optional; @@ -111,6 +111,11 @@ public class BaseEventService implements EventService { return eventDao.findLatestEvents(tenantId.getId(), entityId, eventType, limit); } + @Override + public PageData findEventsByFilter(TenantId tenantId, EntityId entityId, EventFilter eventFilter, TimePageLink pageLink) { + return eventDao.findEventByFilter(tenantId.getId(), entityId, eventFilter, pageLink); + } + @Override public void removeEvents(TenantId tenantId, EntityId entityId) { PageData eventPageData; diff --git a/dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java b/dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java index 0cb4fccfb3..ba4e86c95e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.event; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.Event; +import org.thingsboard.server.common.data.event.EventFilter; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; @@ -88,6 +89,8 @@ public interface EventDao extends Dao { */ PageData findEvents(UUID tenantId, EntityId entityId, String eventType, TimePageLink pageLink); + PageData findEventByFilter(UUID tenantId, EntityId entityId, EventFilter eventFilter, TimePageLink pageLink); + /** * Find latest events by tenantId, entityId and eventType. * diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/event/EventRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/event/EventRepository.java index c68d6f1a7e..270ab26329 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/event/EventRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/event/EventRepository.java @@ -44,11 +44,11 @@ public interface EventRepository extends PagingAndSortingRepository findLatestByTenantIdAndEntityTypeAndEntityIdAndEventType( - @Param("tenantId") UUID tenantId, - @Param("entityType") EntityType entityType, - @Param("entityId") UUID entityId, - @Param("eventType") String eventType, - Pageable pageable); + @Param("tenantId") UUID tenantId, + @Param("entityType") EntityType entityType, + @Param("entityId") UUID entityId, + @Param("eventType") String eventType, + Pageable pageable); @Query("SELECT e FROM EventEntity e WHERE " + "e.tenantId = :tenantId " + @@ -80,4 +80,165 @@ public interface EventRepository extends PagingAndSortingRepository= :startTime AND (:endTime = 0 OR e.created_time <= :endTime) " + + ") AS e WHERE " + + "(:type IS NULL OR lower(json_body->>'type') LIKE concat('%', lower(:type\\:\\:varchar), '%')) " + + "AND (:server IS NULL OR lower(json_body->>'server') LIKE concat('%', lower(:server\\:\\:varchar), '%')) " + + "AND (:entityName IS NULL OR lower(json_body->>'entityName') LIKE concat('%', lower(:entityName\\:\\:varchar), '%')) " + + "AND (:relationType IS NULL OR lower(json_body->>'relationType') LIKE concat('%', lower(:relationType\\:\\:varchar), '%')) " + + "AND (:bodyEntityId IS NULL OR lower(json_body->>'entityId') LIKE concat('%', lower(:bodyEntityId\\:\\:varchar), '%')) " + + "AND (:msgType IS NULL OR lower(json_body->>'msgType') LIKE concat('%', lower(:msgType\\:\\:varchar), '%')) " + + "AND ((:isError = FALSE) OR (json_body->>'error') IS NOT NULL) " + + "AND (:error IS NULL OR lower(json_body->>'error') LIKE concat('%', lower(:error\\:\\:varchar), '%')) " + + "AND (:data IS NULL OR lower(json_body->>'data') LIKE concat('%', lower(:data\\:\\:varchar), '%')) " + + "AND (:metadata IS NULL OR lower(json_body->>'metadata') LIKE concat('%', lower(:metadata\\:\\:varchar), '%')) ", + countQuery = "SELECT count(*) FROM " + + "(SELECT *, e.body\\:\\:jsonb as json_body FROM event e WHERE " + + "e.tenant_id = :tenantId " + + "AND e.entity_type = :entityType " + + "AND e.entity_id = :entityId " + + "AND e.event_type = :eventType " + + "AND e.created_time >= :startTime AND (:endTime = 0 OR e.created_time <= :endTime) " + + ") AS e WHERE " + + "(:type IS NULL OR lower(json_body->>'type') LIKE concat('%', lower(:type\\:\\:varchar), '%')) " + + "AND (:server IS NULL OR lower(json_body->>'server') LIKE concat('%', lower(:server\\:\\:varchar), '%')) " + + "AND (:entityName IS NULL OR lower(json_body->>'entityName') LIKE concat('%', lower(:entityName\\:\\:varchar), '%')) " + + "AND (:relationType IS NULL OR lower(json_body->>'relationType') LIKE concat('%', lower(:relationType\\:\\:varchar), '%')) " + + "AND (:bodyEntityId IS NULL OR lower(json_body->>'entityId') LIKE concat('%', lower(:bodyEntityId\\:\\:varchar), '%')) " + + "AND (:msgType IS NULL OR lower(json_body->>'msgType') LIKE concat('%', lower(:msgType\\:\\:varchar), '%')) " + + "AND ((:isError = FALSE) OR (json_body->>'error') IS NOT NULL) " + + "AND (:error IS NULL OR lower(json_body->>'error') LIKE concat('%', lower(:error\\:\\:varchar), '%')) " + + "AND (:data IS NULL OR lower(json_body->>'data') LIKE concat('%', lower(:data\\:\\:varchar), '%')) " + + "AND (:metadata IS NULL OR lower(json_body->>'metadata') LIKE concat('%', lower(:metadata\\:\\:varchar), '%'))" + ) + Page findDebugRuleNodeEvents(@Param("tenantId") UUID tenantId, + @Param("entityId") UUID entityId, + @Param("entityType") String entityType, + @Param("eventType") String eventType, + @Param("startTime") Long startTime, + @Param("endTime") Long endTime, + @Param("type") String type, + @Param("server") String server, + @Param("entityName") String entityName, + @Param("relationType") String relationType, + @Param("bodyEntityId") String bodyEntityId, + @Param("msgType") String msgType, + @Param("isError") boolean isError, + @Param("error") String error, + @Param("data") String data, + @Param("metadata") String metadata, + Pageable pageable); + + @Query(nativeQuery = true, + value = "SELECT e.id, e.created_time, e.body, e.entity_id, e.entity_type, e.event_type, e.event_uid, e.tenant_id, ts FROM " + + "(SELECT *, e.body\\:\\:jsonb as json_body FROM event e WHERE " + + "e.tenant_id = :tenantId " + + "AND e.entity_type = :entityType " + + "AND e.entity_id = :entityId " + + "AND e.event_type = 'ERROR' " + + "AND e.created_time >= :startTime AND (:endTime = 0 OR e.created_time <= :endTime) " + + ") AS e WHERE " + + "(:server IS NULL OR lower(json_body->>'server') LIKE concat('%', lower(:server\\:\\:varchar), '%')) " + + "AND (:method IS NULL OR lower(json_body->>'method') LIKE concat('%', lower(:method\\:\\:varchar), '%')) " + + "AND (:error IS NULL OR lower(json_body->>'error') LIKE concat('%', lower(:error\\:\\:varchar), '%'))", + countQuery = "SELECT count(*) FROM " + + "(SELECT *, e.body\\:\\:jsonb as json_body FROM event e WHERE " + + "e.tenant_id = :tenantId " + + "AND e.entity_type = :entityType " + + "AND e.entity_id = :entityId " + + "AND e.event_type = 'ERROR' " + + "AND e.created_time >= :startTime AND (:endTime = 0 OR e.created_time <= :endTime) " + + ") AS e WHERE " + + "(:server IS NULL OR lower(json_body->>'server') LIKE concat('%', lower(:server\\:\\:varchar), '%')) " + + "AND (:method IS NULL OR lower(json_body->>'method') LIKE concat('%', lower(:method\\:\\:varchar), '%')) " + + "AND (:error IS NULL OR lower(json_body->>'error') LIKE concat('%', lower(:error\\:\\:varchar), '%'))") + Page findErrorEvents(@Param("tenantId") UUID tenantId, + @Param("entityId") UUID entityId, + @Param("entityType") String entityType, + @Param("startTime") Long startTime, + @Param("endTime") Long endTIme, + @Param("server") String server, + @Param("method") String method, + @Param("error") String error, + Pageable pageable); + + @Query(nativeQuery = true, + value = "SELECT e.id, e.created_time, e.body, e.entity_id, e.entity_type, e.event_type, e.event_uid, e.tenant_id, ts FROM " + + "(SELECT *, e.body\\:\\:jsonb as json_body FROM event e WHERE " + + "e.tenant_id = :tenantId " + + "AND e.entity_type = :entityType " + + "AND e.entity_id = :entityId " + + "AND e.event_type = 'LC_EVENT' " + + "AND e.created_time >= :startTime AND (:endTime = 0 OR e.created_time <= :endTime) " + + ") AS e WHERE " + + "(:server IS NULL OR lower(json_body->>'server') LIKE concat('%', lower(:server\\:\\:varchar), '%')) " + + "AND (:event IS NULL OR lower(json_body->>'event') LIKE concat('%', lower(:event\\:\\:varchar), '%')) " + + "AND ((:statusFilterEnabled = FALSE) OR lower(json_body->>'success')\\:\\:boolean = :statusFilter) " + + "AND (:error IS NULL OR lower(json_body->>'error') LIKE concat('%', lower(:error\\:\\:varchar), '%'))" + , + countQuery = "SELECT count(*) FROM " + + "(SELECT *, e.body\\:\\:jsonb as json_body FROM event e WHERE " + + "e.tenant_id = :tenantId " + + "AND e.entity_type = :entityType " + + "AND e.entity_id = :entityId " + + "AND e.event_type = 'LC_EVENT' " + + "AND e.created_time >= :startTime AND (:endTime = 0 OR e.created_time <= :endTime) " + + ") AS e WHERE " + + "(:server IS NULL OR lower(json_body->>'server') LIKE concat('%', lower(:server\\:\\:varchar), '%')) " + + "AND (:event IS NULL OR lower(json_body->>'event') LIKE concat('%', lower(:event\\:\\:varchar), '%')) " + + "AND ((:statusFilterEnabled = FALSE) OR lower(json_body->>'success')\\:\\:boolean = :statusFilter) " + + "AND (:error IS NULL OR lower(json_body->>'error') LIKE concat('%', lower(:error\\:\\:varchar), '%'))" + ) + Page findLifeCycleEvents(@Param("tenantId") UUID tenantId, + @Param("entityId") UUID entityId, + @Param("entityType") String entityType, + @Param("startTime") Long startTime, + @Param("endTime") Long endTIme, + @Param("server") String server, + @Param("event") String event, + @Param("statusFilterEnabled") boolean statusFilterEnabled, + @Param("statusFilter") boolean statusFilter, + @Param("error") String error, + Pageable pageable); + + @Query(nativeQuery = true, + value = "SELECT e.id, e.created_time, e.body, e.entity_id, e.entity_type, e.event_type, e.event_uid, e.tenant_id, ts FROM " + + "(SELECT *, e.body\\:\\:jsonb as json_body FROM event e WHERE " + + "e.tenant_id = :tenantId " + + "AND e.entity_type = :entityType " + + "AND e.entity_id = :entityId " + + "AND e.event_type = 'STATS' " + + "AND e.created_time >= :startTime AND (:endTime = 0 OR e.created_time <= :endTime) " + + ") AS e WHERE " + + "(:server IS NULL OR lower(e.body\\:\\:json->>'server') LIKE concat('%', lower(:server\\:\\:varchar), '%')) " + + "AND (:messagesProcessed = 0 OR (json_body->>'messagesProcessed')\\:\\:integer >= :messagesProcessed) " + + "AND (:errorsOccurred = 0 OR (json_body->>'errorsOccurred')\\:\\:integer >= :errorsOccurred) ", + countQuery = "SELECT count(*) FROM " + + "(SELECT *, e.body\\:\\:jsonb as json_body FROM event e WHERE " + + "e.tenant_id = :tenantId " + + "AND e.entity_type = :entityType " + + "AND e.entity_id = :entityId " + + "AND e.event_type = 'LC_EVENT' " + + "AND e.created_time >= :startTime AND (:endTime = 0 OR e.created_time <= :endTime) " + + ") AS e WHERE " + + "(:server IS NULL OR lower(e.body\\:\\:json->>'server') LIKE concat('%', lower(:server\\:\\:varchar), '%')) " + + "AND (:messagesProcessed = 0 OR (json_body->>'messagesProcessed')\\:\\:integer >= :messagesProcessed) " + + "AND (:errorsOccurred = 0 OR (json_body->>'errorsOccurred')\\:\\:integer >= :errorsOccurred) ") + Page findStatisticsEvents(@Param("tenantId") UUID tenantId, + @Param("entityId") UUID entityId, + @Param("entityType") String entityType, + @Param("startTime") Long startTime, + @Param("endTime") Long endTIme, + @Param("server") String server, + @Param("messagesProcessed") Integer messagesProcessed, + @Param("errorsOccurred") Integer errorsOccurred, + Pageable pageable); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java index 06f54042d7..44848ec515 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java @@ -24,6 +24,12 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.Event; +import org.thingsboard.server.common.data.event.DebugEvent; +import org.thingsboard.server.common.data.event.ErrorEventFilter; +import org.thingsboard.server.common.data.event.EventFilter; +import org.thingsboard.server.common.data.event.EventType; +import org.thingsboard.server.common.data.event.LifeCycleEventFilter; +import org.thingsboard.server.common.data.event.StatisticsEventFilter; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EventId; import org.thingsboard.server.common.data.id.TenantId; @@ -147,6 +153,98 @@ public class JpaBaseEventDao extends JpaAbstractDao implemen DaoUtil.toPageable(pageLink))); } + @Override + public PageData findEventByFilter(UUID tenantId, EntityId entityId, EventFilter eventFilter, TimePageLink pageLink) { + if (eventFilter.hasFilterForJsonBody()) { + switch (eventFilter.getEventType()) { + case DEBUG_RULE_NODE: + case DEBUG_RULE_CHAIN: + return findEventByFilter(tenantId, entityId, (DebugEvent) eventFilter, pageLink); + case LC_EVENT: + return findEventByFilter(tenantId, entityId, (LifeCycleEventFilter) eventFilter, pageLink); + case ERROR: + return findEventByFilter(tenantId, entityId, (ErrorEventFilter) eventFilter, pageLink); + case STATS: + return findEventByFilter(tenantId, entityId, (StatisticsEventFilter) eventFilter, pageLink); + default: + throw new RuntimeException("Not supported event type: " + eventFilter.getEventType()); + } + } else { + return findEvents(tenantId, entityId, eventFilter.getEventType().name(), pageLink); + } + } + + private PageData findEventByFilter(UUID tenantId, EntityId entityId, DebugEvent eventFilter, TimePageLink pageLink) { + return DaoUtil.toPageData( + eventRepository.findDebugRuleNodeEvents( + tenantId, + entityId.getId(), + entityId.getEntityType().name(), + eventFilter.getEventType().name(), + notNull(pageLink.getStartTime()), + notNull(pageLink.getEndTime()), + eventFilter.getMsgDirectionType(), + eventFilter.getServer(), + eventFilter.getEntityName(), + eventFilter.getRelationType(), + eventFilter.getEntityId(), + eventFilter.getMsgType(), + eventFilter.isError(), + eventFilter.getError(), + eventFilter.getDataSearch(), + eventFilter.getMetadataSearch(), + DaoUtil.toPageable(pageLink))); + } + + private PageData findEventByFilter(UUID tenantId, EntityId entityId, ErrorEventFilter eventFilter, TimePageLink pageLink) { + return DaoUtil.toPageData( + eventRepository.findErrorEvents( + tenantId, + entityId.getId(), + entityId.getEntityType().name(), + notNull(pageLink.getStartTime()), + notNull(pageLink.getEndTime()), + eventFilter.getServer(), + eventFilter.getMethod(), + eventFilter.getError(), + DaoUtil.toPageable(pageLink)) + ); + } + + private PageData findEventByFilter(UUID tenantId, EntityId entityId, LifeCycleEventFilter eventFilter, TimePageLink pageLink) { + boolean statusFilterEnabled = !StringUtils.isEmpty(eventFilter.getStatus()); + boolean statusFilter = statusFilterEnabled && eventFilter.getStatus().equalsIgnoreCase("Success"); + return DaoUtil.toPageData( + eventRepository.findLifeCycleEvents( + tenantId, + entityId.getId(), + entityId.getEntityType().name(), + notNull(pageLink.getStartTime()), + notNull(pageLink.getEndTime()), + eventFilter.getServer(), + eventFilter.getEvent(), + statusFilterEnabled, + statusFilter, + eventFilter.getError(), + DaoUtil.toPageable(pageLink)) + ); + } + + private PageData findEventByFilter(UUID tenantId, EntityId entityId, StatisticsEventFilter eventFilter, TimePageLink pageLink) { + return DaoUtil.toPageData( + eventRepository.findStatisticsEvents( + tenantId, + entityId.getId(), + entityId.getEntityType().name(), + notNull(pageLink.getStartTime()), + notNull(pageLink.getEndTime()), + eventFilter.getServer(), + notNull(eventFilter.getMessagesProcessed()), + notNull(eventFilter.getErrorsOccurred()), + DaoUtil.toPageable(pageLink)) + ); + } + @Override public List findLatestEvents(UUID tenantId, EntityId entityId, String eventType, int limit) { List latest = eventRepository.findLatestByTenantIdAndEntityTypeAndEntityIdAndEventType( @@ -177,4 +275,12 @@ public class JpaBaseEventDao extends JpaAbstractDao implemen return Optional.of(DaoUtil.getData(eventInsertRepository.saveOrUpdate(entity))); } + private long notNull(Long value) { + return value != null ? value : 0; + } + + private int notNull(Integer value) { + return value != null ? value : 0; + } + } diff --git a/ui-ngx/src/app/core/http/event.service.ts b/ui-ngx/src/app/core/http/event.service.ts index 9bf1b4e86f..fd740d73a7 100644 --- a/ui-ngx/src/app/core/http/event.service.ts +++ b/ui-ngx/src/app/core/http/event.service.ts @@ -21,7 +21,7 @@ import { HttpClient } from '@angular/common/http'; import { TimePageLink } from '@shared/models/page/page-link'; import { PageData } from '@shared/models/page/page-data'; import { EntityId } from '@shared/models/id/entity-id'; -import { DebugEventType, Event, EventType } from '@shared/models/event.models'; +import { DebugEventType, Event, EventType, FilterEventBody } from '@shared/models/event.models'; @Injectable({ providedIn: 'root' @@ -39,4 +39,10 @@ export class EventService { defaultHttpOptionsFromConfig(config)); } + public getFilterEvents(entityId: EntityId, eventType: EventType | DebugEventType, tenantId: string, + filters: FilterEventBody, pageLink: TimePageLink, config?: RequestConfig): Observable> { + return this.http.post>(`/api/events/${entityId.entityType}/${entityId.id}` + + `${pageLink.toQuery()}&tenantId=${tenantId}`, {...filters, eventType}, defaultHttpOptionsFromConfig(config)); + } + } diff --git a/ui-ngx/src/app/modules/home/components/event/event-filter-panel.component.html b/ui-ngx/src/app/modules/home/components/event/event-filter-panel.component.html new file mode 100644 index 0000000000..0568a2d572 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/event/event-filter-panel.component.html @@ -0,0 +1,64 @@ + +
+ + + + + {{ column.title | translate}} + + {{ 'event.all-events' | translate}} + + {{ value }} + + + + + + + {{ 'event.has-error' | translate }} + + + + + {{ column.title | translate}} + + + + + + {{ column.title | translate}} + + + + + +
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/event/event-filter-panel.component.scss b/ui-ngx/src/app/modules/home/components/event/event-filter-panel.component.scss new file mode 100644 index 0000000000..971b81689f --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/event/event-filter-panel.component.scss @@ -0,0 +1,35 @@ +/** + * 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 { + width: 100%; + min-width: 300px; + overflow: auto; + background: #fff; + border-radius: 4px; + box-shadow: + 0 7px 8px -4px rgba(0, 0, 0, .2), + 0 13px 19px 2px rgba(0, 0, 0, .14), + 0 5px 24px 4px rgba(0, 0, 0, .12); + + .mat-content { + overflow: hidden; + background-color: #fff; + } + + .mat-padding { + padding: 16px; + } +} diff --git a/ui-ngx/src/app/modules/home/components/event/event-filter-panel.component.ts b/ui-ngx/src/app/modules/home/components/event/event-filter-panel.component.ts new file mode 100644 index 0000000000..3d016e361e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/event/event-filter-panel.component.ts @@ -0,0 +1,101 @@ +/// +/// 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, Inject, InjectionToken } from '@angular/core'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { OverlayRef } from '@angular/cdk/overlay'; +import { EntityType } from '@shared/models/entity-type.models'; +import { FilterEventBody } from '@shared/models/event.models'; +import { deepTrim } from '@core/utils'; + +export const EVENT_FILTER_PANEL_DATA = new InjectionToken('AlarmFilterPanelData'); + +export interface EventFilterPanelData { + filterParams: FilterEventBody; + columns: Array; +} + +export interface FilterEntityColumn { + key: string; + title: string; +} + + +@Component({ + selector: 'tb-event-filter-panel', + templateUrl: './event-filter-panel.component.html', + styleUrls: ['./event-filter-panel.component.scss'] +}) +export class EventFilterPanelComponent { + + eventFilterFormGroup: FormGroup; + result: EventFilterPanelData; + + private conditionError = false; + + private msgDirectionTypes = ['IN', 'OUT']; + private statusTypes = ['Success', 'Failure']; + private entityTypes = Object.keys(EntityType); + + showColumns: FilterEntityColumn[] = []; + + constructor(@Inject(EVENT_FILTER_PANEL_DATA) + public data: EventFilterPanelData, + public overlayRef: OverlayRef, + private fb: FormBuilder) { + this.eventFilterFormGroup = this.fb.group({}); + this.data.columns.forEach((column) => { + this.showColumns.push(column); + this.eventFilterFormGroup.addControl(column.key, this.fb.control(this.data.filterParams[column.key] || '')); + if (column.key === 'isError') { + this.conditionError = true; + } + }); + } + + isSelector(key: string): string { + return ['msgDirectionType', 'status', 'entityName'].includes(key) ? key : ''; + } + + selectorValues(key: string): string[] { + switch (key) { + case 'msgDirectionType': + return this.msgDirectionTypes; + case 'status': + return this.statusTypes; + case 'entityName': + return this.entityTypes; + } + } + + update() { + const filter = deepTrim(Object.fromEntries(Object.entries(this.eventFilterFormGroup.value).filter(([_, v]) => v !== ''))); + this.result = { + filterParams: filter, + columns: this.data.columns + }; + this.overlayRef.dispose(); + } + + showErrorMsgFields() { + return !this.conditionError || this.eventFilterFormGroup.get('isError').value !== ''; + } + + cancel() { + this.overlayRef.dispose(); + } +} + diff --git a/ui-ngx/src/app/modules/home/components/event/event-table-config.ts b/ui-ngx/src/app/modules/home/components/event/event-table-config.ts index f18df5cd02..3066de554b 100644 --- a/ui-ngx/src/app/modules/home/components/event/event-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/event/event-table-config.ts @@ -20,7 +20,7 @@ import { EntityTableColumn, EntityTableConfig } from '@home/models/entity/entities-table-config.models'; -import { DebugEventType, Event, EventType } from '@shared/models/event.models'; +import { DebugEventType, Event, EventType, FilterEventBody } from '@shared/models/event.models'; import { TimePageLink } from '@shared/models/page/page-link'; import { TranslateService } from '@ngx-translate/core'; import { DatePipe } from '@angular/common'; @@ -38,16 +38,29 @@ import { EventContentDialogComponent, EventContentDialogData } from '@home/components/event/event-content-dialog.component'; -import { sortObjectKeys } from '@core/utils'; +import { isEqual, sortObjectKeys } from '@core/utils'; +import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; +import { ChangeDetectorRef, Injector, StaticProvider, ViewContainerRef } from '@angular/core'; +import { ComponentPortal } from '@angular/cdk/portal'; +import { + EVENT_FILTER_PANEL_DATA, + EventFilterPanelComponent, + EventFilterPanelData, + FilterEntityColumn +} from '@home/components/event/event-filter-panel.component'; export class EventTableConfig extends EntityTableConfig { eventTypeValue: EventType | DebugEventType; + private filterParams: FilterEventBody = {}; + private filterColumns: FilterEntityColumn[] = []; + set eventType(eventType: EventType | DebugEventType) { if (this.eventTypeValue !== eventType) { this.eventTypeValue = eventType; this.updateColumns(true); + this.updateFilterColumns(); } } @@ -66,7 +79,10 @@ export class EventTableConfig extends EntityTableConfig { public tenantId: string, private defaultEventType: EventType | DebugEventType, private disabledEventTypes: Array = null, - private debugEventTypes: Array = null) { + private debugEventTypes: Array = null, + private overlay: Overlay, + private viewContainerRef: ViewContainerRef, + private cd: ChangeDetectorRef) { super(); this.loadDataOnInit = false; this.tableTitle = ''; @@ -101,10 +117,20 @@ export class EventTableConfig extends EntityTableConfig { this.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; this.updateColumns(); + this.updateFilterColumns(); + + this.headerActionDescriptors.push({ + name: this.translate.instant('event.events-filter'), + icon: 'filter_list', + isEnabled: () => true, + onAction: ($event) => { + this.editEventFilter($event); + } + }); } fetchEvents(pageLink: TimePageLink): Observable> { - return this.eventService.getEvents(this.entityId, this.eventType, this.tenantId, pageLink); + return this.eventService.getFilterEvents(this.entityId, this.eventType, this.tenantId, this.filterParams, pageLink); } updateColumns(updateTableColumns: boolean = false): void { @@ -169,7 +195,7 @@ export class EventTableConfig extends EntityTableConfig { }), false, key => ({ padding: '0 12px 0 0' })), - new EntityTableColumn('entity', 'event.entity', '100px', + new EntityTableColumn('entityName', 'event.entity-type', '100px', (entity) => entity.body.entityName, entity => ({ padding: '0 12px 0 0', }), false, key => ({ @@ -249,5 +275,91 @@ export class EventTableConfig extends EntityTableConfig { } }); } + + private updateFilterColumns() { + this.filterParams = {}; + this.filterColumns = [{key: 'server', title: 'event.server'}]; + switch (this.eventType) { + case EventType.ERROR: + this.filterColumns.push( + {key: 'method', title: 'event.method'}, + {key: 'error', title: 'event.error'} + ); + break; + case EventType.LC_EVENT: + this.filterColumns.push( + {key: 'method', title: 'event.event'}, + {key: 'status', title: 'event.status'}, + {key: 'error', title: 'event.error'} + ); + break; + case EventType.STATS: + this.filterColumns.push( + {key: 'messagesProcessed', title: 'event.messages-processed'}, + {key: 'errorsOccurred', title: 'event.errors-occurred'} + ); + break; + case DebugEventType.DEBUG_RULE_NODE: + case DebugEventType.DEBUG_RULE_CHAIN: + this.filterColumns.push( + {key: 'msgDirectionType', title: 'event.type'}, + {key: 'entityId', title: 'event.entity-id'}, + {key: 'entityName', title: 'event.entity-type'}, + {key: 'msgType', title: 'event.message-type'}, + {key: 'relationType', title: 'event.relation-type'}, + {key: 'dataSearch', title: 'event.data'}, + {key: 'metadataSearch', title: 'event.metadata'}, + {key: 'isError', title: 'event.error'}, + {key: 'error', title: 'event.error'} + ); + break; + } + } + + private editEventFilter($event: MouseEvent) { + if ($event) { + $event.stopPropagation(); + } + const target = $event.target || $event.srcElement || $event.currentTarget; + const config = new OverlayConfig(); + config.backdropClass = 'cdk-overlay-transparent-backdrop'; + config.hasBackdrop = true; + const connectedPosition: ConnectedPosition = { + originX: 'end', + originY: 'bottom', + overlayX: 'end', + overlayY: 'top' + }; + config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement) + .withPositions([connectedPosition]); + + const overlayRef = this.overlay.create(config); + overlayRef.backdropClick().subscribe(() => { + overlayRef.dispose(); + }); + const providers: StaticProvider[] = [ + { + provide: EVENT_FILTER_PANEL_DATA, + useValue: { + columns: this.filterColumns, + filterParams: this.filterParams + } as EventFilterPanelData + }, + { + provide: OverlayRef, + useValue: overlayRef + } + ]; + const injector = Injector.create({parent: this.viewContainerRef.injector, providers}); + const componentRef = overlayRef.attach(new ComponentPortal(EventFilterPanelComponent, + this.viewContainerRef, injector)); + componentRef.onDestroy(() => { + if (componentRef.instance.result && !isEqual(this.filterParams, componentRef.instance.result.filterParams)) { + this.filterParams = componentRef.instance.result.filterParams; + this.table.updateData(); + } + }); + this.cd.detectChanges(); + } } diff --git a/ui-ngx/src/app/modules/home/components/event/event-table.component.ts b/ui-ngx/src/app/modules/home/components/event/event-table.component.ts index 6aaa256ab8..00aeb4f50c 100644 --- a/ui-ngx/src/app/modules/home/components/event/event-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/event/event-table.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, Input, OnInit, ViewChild } from '@angular/core'; +import { ChangeDetectorRef, Component, Input, OnInit, ViewChild, ViewContainerRef } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { DatePipe } from '@angular/common'; import { MatDialog } from '@angular/material/dialog'; @@ -24,6 +24,7 @@ import { EventTableConfig } from './event-table-config'; import { EventService } from '@core/http/event.service'; import { DialogService } from '@core/services/dialog.service'; import { DebugEventType, EventType } from '@shared/models/event.models'; +import { Overlay } from '@angular/cdk/overlay'; @Component({ selector: 'tb-event-table', @@ -80,7 +81,10 @@ export class EventTableComponent implements OnInit { private dialogService: DialogService, private translate: TranslateService, private datePipe: DatePipe, - private dialog: MatDialog) { + private dialog: MatDialog, + private overlay: Overlay, + private viewContainerRef: ViewContainerRef, + private cd: ChangeDetectorRef) { } ngOnInit() { @@ -95,7 +99,10 @@ export class EventTableComponent implements OnInit { this.tenantId, this.defaultEventType, this.disabledEventTypes, - this.debugEventTypes + this.debugEventTypes, + this.overlay, + this.viewContainerRef, + this.cd ); } diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index 2c011a0214..14ec1fdc4e 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -25,6 +25,7 @@ import { AuditLogDetailsDialogComponent } from '@home/components/audit-log/audit import { AuditLogTableComponent } from '@home/components/audit-log/audit-log-table.component'; import { EventTableHeaderComponent } from '@home/components/event/event-table-header.component'; import { EventTableComponent } from '@home/components/event/event-table.component'; +import { EventFilterPanelComponent } from '@home/components/event/event-filter-panel.component'; import { RelationTableComponent } from '@home/components/relation/relation-table.component'; import { RelationDialogComponent } from '@home/components/relation/relation-dialog.component'; import { AlarmTableHeaderComponent } from '@home/components/alarm/alarm-table-header.component'; @@ -149,6 +150,7 @@ import { DisplayWidgetTypesPanelComponent } from '@home/components/dashboard-pag EventContentDialogComponent, EventTableHeaderComponent, EventTableComponent, + EventFilterPanelComponent, EdgeDownlinkTableHeaderComponent, EdgeDownlinkTableComponent, RelationTableComponent, diff --git a/ui-ngx/src/app/shared/models/event.models.ts b/ui-ngx/src/app/shared/models/event.models.ts index 00fabd4526..b801bab750 100644 --- a/ui-ngx/src/app/shared/models/event.models.ts +++ b/ui-ngx/src/app/shared/models/event.models.ts @@ -19,6 +19,7 @@ import { TenantId } from '@shared/models/id/tenant-id'; import { EntityId } from '@shared/models/id/entity-id'; import { EventId } from './id/event-id'; import { ContentType } from '@shared/models/constants'; +import { EntityType } from '@shared/models/entity-type.models'; export enum EventType { ERROR = 'ERROR', @@ -83,3 +84,36 @@ export interface Event extends BaseData { uid: string; body: EventBody; } + +export interface BaseFilterEventBody { + server?: string; +} + +export interface ErrorFilterEventBody extends BaseFilterEventBody { + method?: string; +} + +export interface LcFilterEventEventBody extends BaseFilterEventBody { + method?: string; + status?: string; + isError?: boolean; +} + +export interface StatsFilterEventBody extends BaseFilterEventBody { + messagesProcessed?: string; + errorsOccurred?: string; +} + +export interface DebugFilterRuleNodeEventBody extends BaseFilterEventBody { + msgDirectionType?: string; + entityId?: string; + entityName?: EntityType; + msgId?: string; + msgType?: string; + relationType?: string; + dataSearch?: string; + metadataSearch?: string; + isError?: boolean; +} + +export type FilterEventBody = ErrorFilterEventBody & LcFilterEventEventBody & StatsFilterEventBody & DebugFilterRuleNodeEventBody; diff --git a/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json b/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json index 888154e3bf..92941c9404 100644 --- a/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json +++ b/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json @@ -1332,7 +1332,6 @@ "body": "Tělo", "method": "Metoda", "type": "Typ", - "entity": "Entita", "message-id": "Id zprávy", "message-type": "Typ zprávy", "data-type": "Typ dat", @@ -1344,7 +1343,9 @@ "success": "Úspěch", "failed": "Neúspěch", "messages-processed": "Zpracované zprávy", - "errors-occurred": "Vyskytly se chyby" + "errors-occurred": "Vyskytly se chyby", + "all-events": "Vše", + "entity-type": "Typ entity" }, "extension": { "extensions": "Rozšíření", diff --git a/ui-ngx/src/assets/locale/locale.constant-de_DE.json b/ui-ngx/src/assets/locale/locale.constant-de_DE.json index 712ad0fa2d..3dcf03e1b0 100644 --- a/ui-ngx/src/assets/locale/locale.constant-de_DE.json +++ b/ui-ngx/src/assets/locale/locale.constant-de_DE.json @@ -1094,7 +1094,6 @@ "body": "Inhalt", "method": "Methode", "type": "Typ", - "entity": "Entität", "message-id": "Nachrichten-Id", "message-type": "Nachrichten-Typ", "data-type": "Datentyp", @@ -1106,7 +1105,9 @@ "success": "Erfolg", "failed": "Fehlgeschlagen", "messages-processed": "Nachrichten verarbeitet", - "errors-occurred": "Fehler aufgetreten" + "errors-occurred": "Fehler aufgetreten", + "all-events": "Alle", + "entity-type": "Entitätstyp" }, "extension": { "extensions": "Erweiterungen", diff --git a/ui-ngx/src/assets/locale/locale.constant-el_GR.json b/ui-ngx/src/assets/locale/locale.constant-el_GR.json index 2d2ab96de5..d92b6e7884 100644 --- a/ui-ngx/src/assets/locale/locale.constant-el_GR.json +++ b/ui-ngx/src/assets/locale/locale.constant-el_GR.json @@ -1247,7 +1247,6 @@ "out": "Έξοδος", "metadata": "Μεταδεδομένα", "message": "Μήνυμα", - "entity": "Οντότητα", "message-id": "ID Μηνύματος", "message-type": "Τύπος Μηνύματος", "data-type": "Τύπος Δεδομένων", @@ -1258,7 +1257,9 @@ "success": "Επιτυχία", "failed": "Απέτυχε", "messages-processed": "Επεξεργασμένα μηνύματα", - "errors-occurred": "Παρουσιάστηκαν σφάλματα" + "errors-occurred": "Παρουσιάστηκαν σφάλματα", + "all-events": "Όλοι", + "entity-type": "Τύπος οντοτήτων" }, "extension": { "extensions": "Επεκτάσεις", 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 c16f0c62ef..d028bf5158 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1616,6 +1616,7 @@ }, "event": { "event-type": "Event type", + "events-filter": "Events Filter", "type-error": "Error", "type-lc-event": "Lifecycle event", "type-stats": "Statistics", @@ -1629,7 +1630,6 @@ "body": "Body", "method": "Method", "type": "Type", - "entity": "Entity", "message-id": "Message Id", "message-type": "Message Type", "data-type": "Data Type", @@ -1641,7 +1641,11 @@ "success": "Success", "failed": "Failed", "messages-processed": "Messages processed", - "errors-occurred": "Errors occurred" + "errors-occurred": "Errors occurred", + "all-events": "All", + "has-error": "Has error", + "entity-id": "Entity Id", + "entity-type": "Entity type" }, "extension": { "extensions": "Extensions", diff --git a/ui-ngx/src/assets/locale/locale.constant-es_ES.json b/ui-ngx/src/assets/locale/locale.constant-es_ES.json index ba278e796e..9d18102a43 100644 --- a/ui-ngx/src/assets/locale/locale.constant-es_ES.json +++ b/ui-ngx/src/assets/locale/locale.constant-es_ES.json @@ -1521,7 +1521,6 @@ "body": "Cuerpo", "method": "Método", "type": "Tipo", - "entity": "Entidad", "message-id": "Id Mensaje", "message-type": "Tipo Mensaje", "data-type": "Tipo de Datos", @@ -1533,7 +1532,9 @@ "success": "Éxito", "failed": "Fallo", "messages-processed": "Mensajes procesados", - "errors-occurred": "Ocurrieron errores" + "errors-occurred": "Ocurrieron errores", + "all-events": "Todos", + "entity-type": "Tipo de entidad" }, "extension": { "extensions": "Extensiones", diff --git a/ui-ngx/src/assets/locale/locale.constant-fa_IR.json b/ui-ngx/src/assets/locale/locale.constant-fa_IR.json index 5327f0b33b..90a10f765c 100644 --- a/ui-ngx/src/assets/locale/locale.constant-fa_IR.json +++ b/ui-ngx/src/assets/locale/locale.constant-fa_IR.json @@ -881,7 +881,6 @@ "body": "بدنه", "method": "روش", "type": "نوع", - "entity": "موجودي", "message-id": "پيام ID", "message-type": "نوع پيام", "data-type": "نوع داده", @@ -893,7 +892,9 @@ "success": "موفقيت", "failed": "عدم موفقيت", "messages-processed": "پيام پردازش شد", - "errors-occurred": "خطاها رخ دادند" + "errors-occurred": "خطاها رخ دادند", + "all-events": "همه", + "entity-type": "نوع موجودي" }, "extension": { "extensions": "دنباله ها", diff --git a/ui-ngx/src/assets/locale/locale.constant-fr_FR.json b/ui-ngx/src/assets/locale/locale.constant-fr_FR.json index a5c25909b0..d3a6cf51a5 100644 --- a/ui-ngx/src/assets/locale/locale.constant-fr_FR.json +++ b/ui-ngx/src/assets/locale/locale.constant-fr_FR.json @@ -1115,7 +1115,6 @@ "body": "Corps", "data": "Données", "data-type": "Type de données", - "entity": "Entité", "error": "erreur", "type-edge-event": "Downlink", "errors-occurred": "Des erreurs sont survenues", @@ -1138,7 +1137,9 @@ "type-debug-rule-node": "Debug", "type-error": "Erreur", "type-lc-event": "Evénement du cycle de vie", - "type-stats": "Statistiques" + "type-stats": "Statistiques", + "all-events": "Tout", + "entity-type": "Type d'entité" }, "extension": { "add": "Ajouter une extension", diff --git a/ui-ngx/src/assets/locale/locale.constant-it_IT.json b/ui-ngx/src/assets/locale/locale.constant-it_IT.json index 363801ebc3..4050e48ad7 100644 --- a/ui-ngx/src/assets/locale/locale.constant-it_IT.json +++ b/ui-ngx/src/assets/locale/locale.constant-it_IT.json @@ -915,7 +915,6 @@ "body": "Body", "method": "Metodo", "type": "Tipo", - "entity": "Entità", "message-id": "Id Messaggio", "message-type": "Tipo Messaggio", "data-type": "Tipo di dato", @@ -927,7 +926,9 @@ "success": "Success", "failed": "Failed", "messages-processed": "Messaggi elaborati", - "errors-occurred": "Si sono verificati degli errori" + "errors-occurred": "Si sono verificati degli errori", + "all-events": "Tutte", + "entity-type": "Tipo entità" }, "extension": { "extensions": "Estensioni", diff --git a/ui-ngx/src/assets/locale/locale.constant-ja_JA.json b/ui-ngx/src/assets/locale/locale.constant-ja_JA.json index 88d666423c..f3696e9a23 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ja_JA.json +++ b/ui-ngx/src/assets/locale/locale.constant-ja_JA.json @@ -768,7 +768,6 @@ "body": "体", "method": "方法", "type": "タイプ", - "entity": "エンティティ", "message-id": "メッセージID", "message-type": "メッセージタイプ", "data-type": "データ・タイプ", @@ -780,7 +779,9 @@ "success": "成功", "failed": "失敗", "messages-processed": "処理されたメッセージ", - "errors-occurred": "エラーが発生しました" + "errors-occurred": "エラーが発生しました", + "all-events": "すべて", + "entity-type": "エンティティタイプ" }, "extension": { "extensions": "拡張機能", diff --git a/ui-ngx/src/assets/locale/locale.constant-ka_GE.json b/ui-ngx/src/assets/locale/locale.constant-ka_GE.json index 06bde1bb02..6f5ba73674 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ka_GE.json +++ b/ui-ngx/src/assets/locale/locale.constant-ka_GE.json @@ -959,7 +959,6 @@ "body": "სხეული", "method": "მეთოდი", "type": "ტიპი", - "entity": "ობიექტი", "message-id": "მესიჯის-ID", "message-type": "მესიჯის ტიპი", "data-type": "მონაცემთა ტიპი", @@ -971,7 +970,9 @@ "success": "წარმატება", "failed": "ვერ მოხერხდა", "messages-processed": "შეტყობინებების დამუშავება", - "errors-occurred": "შეცდომები მოხდა" + "errors-occurred": "შეცდომები მოხდა", + "all-events": "ყველა", + "entity-type": "ობიექტის ტიპი" }, "extension": { "extensions": "დამატებითი აპი", diff --git a/ui-ngx/src/assets/locale/locale.constant-ko_KR.json b/ui-ngx/src/assets/locale/locale.constant-ko_KR.json index f52c94a0f0..cbe962bc2a 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ko_KR.json +++ b/ui-ngx/src/assets/locale/locale.constant-ko_KR.json @@ -1329,7 +1329,6 @@ "body": "Body", "method": "방법", "type": "유형", - "entity": "개체", "message-id": "메시지 ID", "message-type": "메시지 유형", "data-type": "데이터 유형", @@ -1341,7 +1340,9 @@ "success": "성공", "failed": "실패", "messages-processed": "처리된 메시지", - "errors-occurred": "오류가 발생했습니다" + "errors-occurred": "오류가 발생했습니다", + "all-events": "모두", + "entity-type": "개체 유형" }, "extension": { "extensions": "확장", diff --git a/ui-ngx/src/assets/locale/locale.constant-lv_LV.json b/ui-ngx/src/assets/locale/locale.constant-lv_LV.json index 76282308e7..c2b7ce6e79 100644 --- a/ui-ngx/src/assets/locale/locale.constant-lv_LV.json +++ b/ui-ngx/src/assets/locale/locale.constant-lv_LV.json @@ -894,7 +894,6 @@ "body": "Galvenā daļa", "method": "Metode", "type": "Tips", - "entity": "Vienība", "message-id": "Ziņojuma Id", "message-type": "Ziņojuma tips", "data-type": "Datu tips", @@ -906,7 +905,9 @@ "success": "Sekmīgi", "failed": "Kļūda", "messages-processed": "Ziņojumi apstrādāti", - "errors-occurred": "Kļūdas konstatētas" + "errors-occurred": "Kļūdas konstatētas", + "all-events": "Visi", + "entity-type": "Vienības tips" }, "extension": { "extensions": "Paplašinājumi", diff --git a/ui-ngx/src/assets/locale/locale.constant-pt_BR.json b/ui-ngx/src/assets/locale/locale.constant-pt_BR.json index 325229b47b..7f5c7ce239 100644 --- a/ui-ngx/src/assets/locale/locale.constant-pt_BR.json +++ b/ui-ngx/src/assets/locale/locale.constant-pt_BR.json @@ -997,7 +997,6 @@ "body": "Corpo", "method": "Método", "type": "Tipo", - "entity": "Entidade", "message-id": "ID de mensagem", "message-type": "Tipo de Mensagem", "data-type": "Tipo de Dados", @@ -1009,7 +1008,9 @@ "success": "Êxito", "failed": "Falhou", "messages-processed": "Mensagens processadas", - "errors-occurred": "Erros" + "errors-occurred": "Erros", + "all-events": "Tudo", + "entity-type": "Tipo de entidade" }, "extension": { "extensions": "Extensões", diff --git a/ui-ngx/src/assets/locale/locale.constant-ro_RO.json b/ui-ngx/src/assets/locale/locale.constant-ro_RO.json index 971ad02d00..80c9941f2a 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ro_RO.json +++ b/ui-ngx/src/assets/locale/locale.constant-ro_RO.json @@ -945,7 +945,6 @@ "body": "Corp", "method": "Metodă", "type": "Tip", - "entity": "Entitate", "message-id": "ID Mesaj", "message-type": "Tip Mesaj", "data-type": "Tip Date", @@ -957,7 +956,9 @@ "success": "Succes", "failed": "Eşuat", "messages-processed": "Mesaje procesate", - "errors-occurred": "Au apărut erori" + "errors-occurred": "Au apărut erori", + "all-events": "Toate", + "entity-type": "Tip Entitate" }, "extension": { "extensions": "Extensii", @@ -1174,7 +1175,7 @@ "entity-field": "Câmp Entitate", "access-token": "Token De Acces" }, - "stepper-text":{ + "stepper-text": { "select-file": "Selectează un fişier", "configuration": "Importă configurație", "column-type": "Selectează tipul de coloane", @@ -1796,4 +1797,4 @@ "language": { "language": "Limba" } -} +} \ No newline at end of file diff --git a/ui-ngx/src/assets/locale/locale.constant-ru_RU.json b/ui-ngx/src/assets/locale/locale.constant-ru_RU.json index a78416a201..f015d01b18 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ru_RU.json +++ b/ui-ngx/src/assets/locale/locale.constant-ru_RU.json @@ -948,7 +948,6 @@ "body": "Тело", "method": "Метод", "type": "Тип", - "entity": "Объект", "message-id": "ИД сообщения", "message-type": "Тип сообщения", "data-type": "Тип данных", @@ -960,7 +959,9 @@ "success": "Успех", "failed": "Неудача", "messages-processed": "Сообщения обработаны", - "errors-occurred": "Возникли ошибки" + "errors-occurred": "Возникли ошибки", + "all-events": "Все", + "entity-type": "Тип объекта" }, "extension": { "extensions": "Расширение", diff --git a/ui-ngx/src/assets/locale/locale.constant-sl_SI.json b/ui-ngx/src/assets/locale/locale.constant-sl_SI.json index 4dffbc6be2..7dd60aaa2f 100644 --- a/ui-ngx/src/assets/locale/locale.constant-sl_SI.json +++ b/ui-ngx/src/assets/locale/locale.constant-sl_SI.json @@ -1329,7 +1329,6 @@ "body": "Vsebina", "method": "Metoda", "type": "Vrsta", - "entity": "Entiteta", "message-id": "ID sporočila", "message-type": "Vrsta sporočila", "data-type": "Vrsta podatkov", @@ -1341,7 +1340,9 @@ "success": "Uspeh", "failed": "Ni uspelo", "messages-processed": "Obdelana sporočila", - "errors-occurred": "Prišlo je do napak" + "errors-occurred": "Prišlo je do napak", + "all-events": "Vse", + "entity-type": "Vrsta entitete" }, "extension": { "extensions": "Razširitve", diff --git a/ui-ngx/src/assets/locale/locale.constant-tr_TR.json b/ui-ngx/src/assets/locale/locale.constant-tr_TR.json index 06d259dace..dfe3dbbaef 100644 --- a/ui-ngx/src/assets/locale/locale.constant-tr_TR.json +++ b/ui-ngx/src/assets/locale/locale.constant-tr_TR.json @@ -915,7 +915,6 @@ "body": "İçerik //(Body)", "method": "Yöntem", "type": "Tür", - "entity": "Varlık", "message-id": "Mesaj Kimliği", "message-type": "Mesaj tipi", "data-type": "Veri tipi", @@ -927,7 +926,9 @@ "success": "Başarı", "failed": "Başarısız oldu", "messages-processed": "Mesajlar işlendi", - "errors-occurred": "Hatalar oluştu" + "errors-occurred": "Hatalar oluştu", + "all-events": "Tümü", + "entity-type": "Öğe türü" }, "extension": { "extensions": "Uzantılar", diff --git a/ui-ngx/src/assets/locale/locale.constant-uk_UA.json b/ui-ngx/src/assets/locale/locale.constant-uk_UA.json index f9f33d48b9..c45b19ad5f 100644 --- a/ui-ngx/src/assets/locale/locale.constant-uk_UA.json +++ b/ui-ngx/src/assets/locale/locale.constant-uk_UA.json @@ -1201,7 +1201,6 @@ "out": "Out", "metadata": "Метадані", "message": "Повідомлення", - "entity": "Сутність", "message-id": "Id повідомлення", "message-type": "Тип повідомлення", "data-type": "Тип даних", @@ -1212,7 +1211,9 @@ "success": "Успіх", "failed": "Невдача", "messages-processed": "Повідомлення опрацьовані", - "errors-occurred": "Виникли помилки" + "errors-occurred": "Виникли помилки", + "all-events": "Всі", + "entity-type": "Тип сутності" }, "extension": { "extensions": "Розширення", 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 c720981fc6..b7a3078c3b 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json @@ -1334,7 +1334,6 @@ "body": "整体", "data": "数据", "data-type": "数据类型", - "entity": "实体", "error": "错误", "errors-occurred": "错误发生", "event": "事件", @@ -1356,7 +1355,9 @@ "type-debug-rule-node": "调试", "type-error": "错误", "type-lc-event": "生命周期事件", - "type-stats": "类型统计" + "type-stats": "类型统计", + "all-events": "全部", + "entity-type": "实体类型" }, "extension": { "add": "添加扩展", diff --git a/ui-ngx/src/assets/locale/locale.constant-zh_TW.json b/ui-ngx/src/assets/locale/locale.constant-zh_TW.json index 14f4268724..bc8374a1c1 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_TW.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_TW.json @@ -865,7 +865,6 @@ "body": "整體", "method": "方法", "type": "類型", - "entity": "實體", "message-id": "消息ID", "message-type": "消息類型", "data-type": "資料類型", @@ -877,7 +876,9 @@ "success": "成功", "failed": "失敗", "messages-processed": "消息處理", - "errors-occurred": "錯誤發生" + "errors-occurred": "錯誤發生", + "all-events": "所有", + "entity-type": "實體類型" }, "extension": { "extensions": "擴展",