Commit cc1b42f93d1842252b915e52d7dcd0abed329727

Authored by Volodymyr Babak
2 parents f2eea6d8 eb16527e

Merge branch 'master' of github.com:thingsboard/thingsboard into edge/refactoring

Showing 71 changed files with 1459 additions and 318 deletions
... ... @@ -18,12 +18,14 @@ package org.thingsboard.server.controller;
18 18 import org.springframework.beans.factory.annotation.Autowired;
19 19 import org.springframework.security.access.prepost.PreAuthorize;
20 20 import org.springframework.web.bind.annotation.PathVariable;
  21 +import org.springframework.web.bind.annotation.RequestBody;
21 22 import org.springframework.web.bind.annotation.RequestMapping;
22 23 import org.springframework.web.bind.annotation.RequestMethod;
23 24 import org.springframework.web.bind.annotation.RequestParam;
24 25 import org.springframework.web.bind.annotation.ResponseBody;
25 26 import org.springframework.web.bind.annotation.RestController;
26 27 import org.thingsboard.server.common.data.Event;
  28 +import org.thingsboard.server.common.data.event.EventFilter;
27 29 import org.thingsboard.server.common.data.exception.ThingsboardException;
28 30 import org.thingsboard.server.common.data.id.EntityId;
29 31 import org.thingsboard.server.common.data.id.EntityIdFactory;
... ... @@ -31,6 +33,7 @@ import org.thingsboard.server.common.data.id.TenantId;
31 33 import org.thingsboard.server.common.data.page.PageData;
32 34 import org.thingsboard.server.common.data.page.TimePageLink;
33 35 import org.thingsboard.server.dao.event.EventService;
  36 +import org.thingsboard.server.dao.model.ModelConstants;
34 37 import org.thingsboard.server.queue.util.TbCoreComponent;
35 38 import org.thingsboard.server.service.security.permission.Operation;
36 39
... ... @@ -101,4 +104,38 @@ public class EventController extends BaseController {
101 104 }
102 105 }
103 106
  107 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
  108 + @RequestMapping(value = "/events/{entityType}/{entityId}", method = RequestMethod.POST)
  109 + @ResponseBody
  110 + public PageData<Event> getEvents(
  111 + @PathVariable("entityType") String strEntityType,
  112 + @PathVariable("entityId") String strEntityId,
  113 + @RequestParam("tenantId") String strTenantId,
  114 + @RequestParam int pageSize,
  115 + @RequestParam int page,
  116 + @RequestBody EventFilter eventFilter,
  117 + @RequestParam(required = false) String textSearch,
  118 + @RequestParam(required = false) String sortProperty,
  119 + @RequestParam(required = false) String sortOrder,
  120 + @RequestParam(required = false) Long startTime,
  121 + @RequestParam(required = false) Long endTime) throws ThingsboardException {
  122 + checkParameter("EntityId", strEntityId);
  123 + checkParameter("EntityType", strEntityType);
  124 + try {
  125 + TenantId tenantId = new TenantId(toUUID(strTenantId));
  126 +
  127 + EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId);
  128 + checkEntityId(entityId, Operation.READ);
  129 +
  130 + if(sortProperty != null && sortProperty.equals("createdTime") && eventFilter.hasFilterForJsonBody()) {
  131 + sortProperty = ModelConstants.CREATED_TIME_PROPERTY;
  132 + }
  133 +
  134 + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
  135 + return checkNotNull(eventService.findEventsByFilter(tenantId, entityId, eventFilter, pageLink));
  136 + } catch (Exception e) {
  137 + throw handleException(e);
  138 + }
  139 + }
  140 +
104 141 }
... ...
... ... @@ -17,10 +17,8 @@ package org.thingsboard.server.service.install;
17 17
18 18 import com.fasterxml.jackson.databind.ObjectMapper;
19 19 import com.fasterxml.jackson.databind.node.ObjectNode;
20   -import lombok.Getter;
21 20 import lombok.extern.slf4j.Slf4j;
22 21 import org.springframework.beans.factory.annotation.Autowired;
23   -import org.springframework.beans.factory.annotation.Value;
24 22 import org.springframework.context.annotation.Bean;
25 23 import org.springframework.context.annotation.Profile;
26 24 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
... ... @@ -60,11 +58,8 @@ import org.thingsboard.server.common.data.page.PageLink;
60 58 import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
61 59 import org.thingsboard.server.common.data.query.DynamicValue;
62 60 import org.thingsboard.server.common.data.query.DynamicValueSourceType;
63   -import org.thingsboard.server.common.data.query.EntityKey;
64   -import org.thingsboard.server.common.data.query.EntityKeyType;
65 61 import org.thingsboard.server.common.data.query.EntityKeyValueType;
66 62 import org.thingsboard.server.common.data.query.FilterPredicateValue;
67   -import org.thingsboard.server.common.data.query.KeyFilter;
68 63 import org.thingsboard.server.common.data.query.NumericFilterPredicate;
69 64 import org.thingsboard.server.common.data.rule.RuleChainType;
70 65 import org.thingsboard.server.common.data.security.Authority;
... ...
... ... @@ -322,7 +322,7 @@ actors:
322 322 # Enable/disable actor statistics
323 323 enabled: "${ACTORS_STATISTICS_ENABLED:true}"
324 324 js_print_interval_ms: "${ACTORS_JS_STATISTICS_PRINT_INTERVAL_MS:10000}"
325   - persist_frequency: "${ACTORS_STATISTICS_PERSIST_FREQUENCY:3600000}"
  325 + persist_frequency: "${ACTORS_STATISTICS_PERSIST_FREQUENCY:10000}"
326 326
327 327 cache:
328 328 # caffeine or redis
... ... @@ -516,7 +516,7 @@ js:
516 516 # Built-in JVM JavaScript environment properties
517 517 local:
518 518 # Use Sandboxed (secured) JVM JavaScript environment
519   - use_js_sandbox: "${USE_LOCAL_JS_SANDBOX:false}"
  519 + use_js_sandbox: "${USE_LOCAL_JS_SANDBOX:true}"
520 520 # Specify thread pool size for JavaScript sandbox resource monitor
521 521 monitor_thread_pool_size: "${LOCAL_JS_SANDBOX_MONITOR_THREAD_POOL_SIZE:4}"
522 522 # Maximum CPU time in milliseconds allowed for script execution
... ...
... ... @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.event;
17 17
18 18 import com.google.common.util.concurrent.ListenableFuture;
19 19 import org.thingsboard.server.common.data.Event;
  20 +import org.thingsboard.server.common.data.event.EventFilter;
20 21 import org.thingsboard.server.common.data.id.EntityId;
21 22 import org.thingsboard.server.common.data.id.TenantId;
22 23 import org.thingsboard.server.common.data.page.PageData;
... ... @@ -41,6 +42,8 @@ public interface EventService {
41 42
42 43 List<Event> findLatestEvents(TenantId tenantId, EntityId entityId, String eventType, int limit);
43 44
  45 + PageData<Event> findEventsByFilter(TenantId tenantId, EntityId entityId, EventFilter eventFilter, TimePageLink pageLink);
  46 +
44 47 void removeEvents(TenantId tenantId, EntityId entityId);
45 48
46 49 }
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.event;
  17 +
  18 +import lombok.Data;
  19 +import org.eclipse.leshan.core.util.StringUtils;
  20 +
  21 +@Data
  22 +public abstract class DebugEvent implements EventFilter {
  23 +
  24 + private String msgDirectionType;
  25 + private String server;
  26 + private String dataSearch;
  27 + private String metadataSearch;
  28 + private String entityName;
  29 + private String relationType;
  30 + private String entityId;
  31 + private String msgType;
  32 + private boolean isError;
  33 + private String error;
  34 +
  35 + public void setIsError(boolean isError) {
  36 + this.isError = isError;
  37 + }
  38 +
  39 + @Override
  40 + public boolean hasFilterForJsonBody() {
  41 + return !StringUtils.isEmpty(msgDirectionType) || !StringUtils.isEmpty(server) || !StringUtils.isEmpty(dataSearch) || !StringUtils.isEmpty(metadataSearch)
  42 + || !StringUtils.isEmpty(entityName) || !StringUtils.isEmpty(relationType) || !StringUtils.isEmpty(entityId) || !StringUtils.isEmpty(msgType) || !StringUtils.isEmpty(error) || isError;
  43 + }
  44 +
  45 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.event;
  17 +
  18 +public class DebugRuleChainEventFilter extends DebugEvent {
  19 + @Override
  20 + public EventType getEventType() {
  21 + return EventType.DEBUG_RULE_CHAIN;
  22 + }
  23 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.event;
  17 +
  18 +public class DebugRuleNodeEventFilter extends DebugEvent {
  19 + @Override
  20 + public EventType getEventType() {
  21 + return EventType.DEBUG_RULE_NODE;
  22 + }
  23 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.event;
  17 +
  18 +import lombok.Data;
  19 +import org.eclipse.leshan.core.util.StringUtils;
  20 +
  21 +@Data
  22 +public class ErrorEventFilter implements EventFilter {
  23 + private String server;
  24 + private String method;
  25 + private String error;
  26 +
  27 + @Override
  28 + public EventType getEventType() {
  29 + return EventType.ERROR;
  30 + }
  31 +
  32 + @Override
  33 + public boolean hasFilterForJsonBody() {
  34 + return !StringUtils.isEmpty(server) || !StringUtils.isEmpty(method) || !StringUtils.isEmpty(error);
  35 + }
  36 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.event;
  17 +
  18 +import com.fasterxml.jackson.annotation.JsonIgnore;
  19 +import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
  20 +import com.fasterxml.jackson.annotation.JsonSubTypes;
  21 +import com.fasterxml.jackson.annotation.JsonTypeInfo;
  22 +
  23 +@JsonTypeInfo(
  24 + use = JsonTypeInfo.Id.NAME,
  25 + include = JsonTypeInfo.As.PROPERTY,
  26 + property = "eventType")
  27 +@JsonSubTypes({
  28 + @JsonSubTypes.Type(value = DebugRuleNodeEventFilter.class, name = "DEBUG_RULE_NODE"),
  29 + @JsonSubTypes.Type(value = DebugRuleChainEventFilter.class, name = "DEBUG_RULE_CHAIN"),
  30 + @JsonSubTypes.Type(value = ErrorEventFilter.class, name = "ERROR"),
  31 + @JsonSubTypes.Type(value = LifeCycleEventFilter.class, name = "LC_EVENT"),
  32 + @JsonSubTypes.Type(value = StatisticsEventFilter.class, name = "STATS")
  33 +})
  34 +public interface EventFilter {
  35 + @JsonIgnore
  36 + EventType getEventType();
  37 +
  38 + boolean hasFilterForJsonBody();
  39 +
  40 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.event;
  17 +
  18 +public enum EventType {
  19 + ERROR, LC_EVENT, STATS, DEBUG_RULE_NODE, DEBUG_RULE_CHAIN
  20 +}
\ No newline at end of file
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.event;
  17 +
  18 +import lombok.Data;
  19 +import org.eclipse.leshan.core.util.StringUtils;
  20 +
  21 +@Data
  22 +public class LifeCycleEventFilter implements EventFilter {
  23 + private String server;
  24 + private String event;
  25 + private String status;
  26 + private String error;
  27 +
  28 + @Override
  29 + public EventType getEventType() {
  30 + return EventType.LC_EVENT;
  31 + }
  32 +
  33 + @Override
  34 + public boolean hasFilterForJsonBody() {
  35 + return !StringUtils.isEmpty(server) || !StringUtils.isEmpty(event) || !StringUtils.isEmpty(status) || !StringUtils.isEmpty(error);
  36 + }
  37 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.event;
  17 +
  18 +import lombok.Data;
  19 +import org.eclipse.leshan.core.util.StringUtils;
  20 +
  21 +@Data
  22 +public class StatisticsEventFilter implements EventFilter {
  23 + private String server;
  24 + private Integer messagesProcessed;
  25 + private Integer errorsOccurred;
  26 +
  27 + @Override
  28 + public EventType getEventType() {
  29 + return EventType.STATS;
  30 + }
  31 +
  32 + @Override
  33 + public boolean hasFilterForJsonBody() {
  34 + return !StringUtils.isEmpty(server) || (messagesProcessed != null && messagesProcessed > 0) || (errorsOccurred != null && errorsOccurred > 0);
  35 + }
  36 +}
... ...
... ... @@ -23,5 +23,5 @@ public class LwM2MBootstrapServers {
23 23 private Integer lifetime = 300;
24 24 private Integer defaultMinPeriod = 1;
25 25 private boolean notifIfDisabled = true;
26   - private String binding = "U";
  26 + private String binding = "UQ";
27 27 }
... ...
... ... @@ -18,6 +18,7 @@ package org.thingsboard.server.transport.lwm2m.server;
18 18 import io.netty.util.concurrent.Future;
19 19 import io.netty.util.concurrent.GenericFutureListener;
20 20 import lombok.extern.slf4j.Slf4j;
  21 +import org.jetbrains.annotations.NotNull;
21 22 import org.thingsboard.server.common.data.Device;
22 23 import org.thingsboard.server.common.data.DeviceProfile;
23 24 import org.thingsboard.server.common.data.ResourceType;
... ... @@ -74,12 +75,12 @@ public class LwM2mSessionMsgListener implements GenericFutureListener<Future<? s
74 75
75 76 @Override
76 77 public void onToDeviceRpcRequest(ToDeviceRpcRequestMsg toDeviceRequest) {
77   - log.info("[{}] toDeviceRpcRequest", toDeviceRequest);
  78 + this.service.onToDeviceRpcRequest(toDeviceRequest);
78 79 }
79 80
80 81 @Override
81 82 public void onToServerRpcResponse(ToServerRpcResponseMsg toServerResponse) {
82   - log.info("[{}] toServerRpcResponse", toServerResponse);
  83 + this.service.onToServerRpcResponse(toServerResponse);
83 84 }
84 85
85 86 @Override
... ... @@ -87,13 +88,15 @@ public class LwM2mSessionMsgListener implements GenericFutureListener<Future<? s
87 88 log.info("[{}] operationComplete", future);
88 89 }
89 90
90   - public void onResourceUpdate(Optional<TransportProtos.ResourceUpdateMsg> resourceUpdateMsgOpt) {
  91 + @Override
  92 + public void onResourceUpdate(@NotNull Optional<TransportProtos.ResourceUpdateMsg> resourceUpdateMsgOpt) {
91 93 if (ResourceType.LWM2M_MODEL.name().equals(resourceUpdateMsgOpt.get().getResourceType())) {
92 94 this.service.onResourceUpdate(resourceUpdateMsgOpt);
93 95 }
94 96 }
95 97
96   - public void onResourceDelete(Optional<TransportProtos.ResourceDeleteMsg> resourceDeleteMsgOpt) {
  98 + @Override
  99 + public void onResourceDelete(@NotNull Optional<TransportProtos.ResourceDeleteMsg> resourceDeleteMsgOpt) {
97 100 if (ResourceType.LWM2M_MODEL.name().equals(resourceDeleteMsgOpt.get().getResourceType())) {
98 101 this.service.onResourceDelete(resourceDeleteMsgOpt);
99 102 }
... ...
... ... @@ -22,15 +22,14 @@ import com.google.gson.JsonSyntaxException;
22 22 import lombok.extern.slf4j.Slf4j;
23 23 import org.apache.commons.lang3.StringUtils;
24 24 import org.eclipse.californium.core.network.config.NetworkConfig;
  25 +import org.eclipse.leshan.core.attributes.Attribute;
  26 +import org.eclipse.leshan.core.attributes.AttributeSet;
25 27 import org.eclipse.leshan.core.model.ObjectModel;
26 28 import org.eclipse.leshan.core.model.ResourceModel;
27   -import org.eclipse.leshan.core.node.LwM2mMultipleResource;
28   -import org.eclipse.leshan.core.node.LwM2mNode;
29   -import org.eclipse.leshan.core.node.LwM2mObject;
30   -import org.eclipse.leshan.core.node.LwM2mObjectInstance;
31 29 import org.eclipse.leshan.core.node.LwM2mPath;
32   -import org.eclipse.leshan.core.node.LwM2mSingleResource;
33 30 import org.eclipse.leshan.core.node.codec.CodecException;
  31 +import org.eclipse.leshan.core.request.DownlinkRequest;
  32 +import org.eclipse.leshan.core.request.WriteAttributesRequest;
34 33 import org.eclipse.leshan.core.util.Hex;
35 34 import org.eclipse.leshan.server.californium.LeshanServerBuilder;
36 35 import org.eclipse.leshan.server.registration.Registration;
... ... @@ -44,11 +43,19 @@ import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientProfile;
44 43
45 44 import java.io.File;
46 45 import java.io.IOException;
  46 +import java.util.ArrayList;
47 47 import java.util.Arrays;
48 48 import java.util.Date;
49 49 import java.util.LinkedList;
  50 +import java.util.List;
  51 +import java.util.Map;
50 52 import java.util.Optional;
  53 +import java.util.concurrent.ConcurrentHashMap;
51 54
  55 +import static org.eclipse.leshan.core.attributes.Attribute.DIMENSION;
  56 +import static org.eclipse.leshan.core.attributes.Attribute.MAXIMUM_PERIOD;
  57 +import static org.eclipse.leshan.core.attributes.Attribute.MINIMUM_PERIOD;
  58 +import static org.eclipse.leshan.core.attributes.Attribute.OBJECT_VERSION;
52 59 import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_KEY;
53 60 import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH;
54 61
... ... @@ -56,8 +63,19 @@ import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPA
56 63 public class LwM2mTransportHandler {
57 64
58 65 public static final String BASE_DEVICE_API_TOPIC = "v1/devices/me";
  66 +
  67 + public static final String CLIENT_LWM2M_SETTINGS = "clientLwM2mSettings";
  68 + public static final String BOOTSTRAP = "bootstrap";
  69 + public static final String SERVERS = "servers";
  70 + public static final String LWM2M_SERVER = "lwm2mServer";
  71 + public static final String BOOTSTRAP_SERVER = "bootstrapServer";
  72 + public static final String OBSERVE_ATTRIBUTE_TELEMETRY = "observeAttr";
59 73 public static final String ATTRIBUTE = "attribute";
60 74 public static final String TELEMETRY = "telemetry";
  75 + public static final String KEY_NAME = "keyName";
  76 + public static final String OBSERVE = "observe";
  77 + public static final String ATTRIBUTE_LWM2M = "attributeLwm2m";
  78 +
61 79 private static final String REQUEST = "/request";
62 80 private static final String RESPONSE = "/response";
63 81 private static final String ATTRIBUTES = "/" + ATTRIBUTE;
... ... @@ -70,14 +88,6 @@ public class LwM2mTransportHandler {
70 88 public static final String DEVICE_TELEMETRY_TOPIC = BASE_DEVICE_API_TOPIC + TELEMETRIES;
71 89
72 90 public static final long DEFAULT_TIMEOUT = 2 * 60 * 1000L; // 2min in ms
73   - public static final String OBSERVE_ATTRIBUTE_TELEMETRY = "observeAttr";
74   - public static final String CLIENT_LWM2M_SETTINGS = "clientLwM2mSettings";
75   - public static final String KEY_NAME = "keyName";
76   - public static final String OBSERVE = "observe";
77   - public static final String BOOTSTRAP = "bootstrap";
78   - public static final String SERVERS = "servers";
79   - public static final String LWM2M_SERVER = "lwm2mServer";
80   - public static final String BOOTSTRAP_SERVER = "bootstrapServer";
81 91
82 92 public static final String LOG_LW2M_TELEMETRY = "logLwm2m";
83 93 public static final String LOG_LW2M_INFO = "info";
... ... @@ -144,19 +154,19 @@ public class LwM2mTransportHandler {
144 154 throw new CodecException("Invalid value type for resource %s, type %s", resourcePath, type);
145 155 }
146 156 }
147   -
148   - public static LwM2mNode getLvM2mNodeToObject(LwM2mNode content) {
149   - if (content instanceof LwM2mObject) {
150   - return (LwM2mObject) content;
151   - } else if (content instanceof LwM2mObjectInstance) {
152   - return (LwM2mObjectInstance) content;
153   - } else if (content instanceof LwM2mSingleResource) {
154   - return (LwM2mSingleResource) content;
155   - } else if (content instanceof LwM2mMultipleResource) {
156   - return (LwM2mMultipleResource) content;
157   - }
158   - return null;
159   - }
  157 +//
  158 +// public static LwM2mNode getLvM2mNodeToObject(LwM2mNode content) {
  159 +// if (content instanceof LwM2mObject) {
  160 +// return (LwM2mObject) content;
  161 +// } else if (content instanceof LwM2mObjectInstance) {
  162 +// return (LwM2mObjectInstance) content;
  163 +// } else if (content instanceof LwM2mSingleResource) {
  164 +// return (LwM2mSingleResource) content;
  165 +// } else if (content instanceof LwM2mMultipleResource) {
  166 +// return (LwM2mMultipleResource) content;
  167 +// }
  168 +// return null;
  169 +// }
160 170
161 171 public static LwM2mClientProfile getNewProfileParameters(JsonObject profilesConfigData, TenantId tenantId) {
162 172 LwM2mClientProfile lwM2MClientProfile = new LwM2mClientProfile();
... ... @@ -166,6 +176,7 @@ public class LwM2mTransportHandler {
166 176 lwM2MClientProfile.setPostAttributeProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(ATTRIBUTE).getAsJsonArray());
167 177 lwM2MClientProfile.setPostTelemetryProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(TELEMETRY).getAsJsonArray());
168 178 lwM2MClientProfile.setPostObserveProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(OBSERVE).getAsJsonArray());
  179 + lwM2MClientProfile.setPostAttributeLwm2mProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(ATTRIBUTE_LWM2M).getAsJsonObject());
169 180 return lwM2MClientProfile;
170 181 }
171 182
... ... @@ -184,6 +195,9 @@ public class LwM2mTransportHandler {
184 195 * "attribute":["/2/0/1","/3/0/9"],
185 196 * "telemetry":["/1/0/1","/2/0/1","/6/0/1"],
186 197 * "observe":["/2/0","/2/0/0","/4/0/2"]}
  198 + * "attributeLwm2m": {"/3_1.0": {"ver": "currentTimeTest11"},
  199 + * "/3_1.0/0": {"gt": 17},
  200 + * "/3_1.0/0/9": {"pmax": 45}, "/3_1.2": {ver": "3_1.2"}}
187 201 */
188 202 public static LwM2mClientProfile getLwM2MClientProfileFromThingsboard(DeviceProfile deviceProfile) {
189 203 if (deviceProfile != null && ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties().size() > 0) {
... ... @@ -192,7 +206,7 @@ public class LwM2mTransportHandler {
192 206 ObjectMapper mapper = new ObjectMapper();
193 207 String profileStr = mapper.writeValueAsString(profile);
194 208 JsonObject profileJson = (profileStr != null) ? validateJson(profileStr) : null;
195   - return (getValidateCredentialsBodyFromThingsboard(profileJson)) ? LwM2mTransportHandler.getNewProfileParameters(profileJson, deviceProfile.getTenantId()) : null;
  209 + return getValidateCredentialsBodyFromThingsboard(profileJson) ? LwM2mTransportHandler.getNewProfileParameters(profileJson, deviceProfile.getTenantId()) : null;
196 210 } catch (IOException e) {
197 211 log.error("", e);
198 212 }
... ... @@ -240,7 +254,10 @@ public class LwM2mTransportHandler {
240 254 objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(TELEMETRY).isJsonArray() &&
241 255 objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().has(OBSERVE) &&
242 256 !objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(OBSERVE).isJsonNull() &&
243   - objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(OBSERVE).isJsonArray());
  257 + objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(OBSERVE).isJsonArray() &&
  258 + objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().has(ATTRIBUTE_LWM2M) &&
  259 + !objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(ATTRIBUTE_LWM2M).isJsonNull() &&
  260 + objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(ATTRIBUTE_LWM2M).isJsonObject());
244 261 }
245 262
246 263 private static boolean getValidateBootstrapProfileFromThingsboard(JsonObject objectMsg) {
... ... @@ -360,4 +377,54 @@ public class LwM2mTransportHandler {
360 377 return ObjectModel.DEFAULT_VERSION;
361 378 }
362 379 }
  380 +
  381 + /**
  382 + * As example:
  383 + * a)Write-Attributes/3/0/9?pmin=1 means the Battery Level value will be notified
  384 + * to the Server with a minimum interval of 1sec;
  385 + * this value is set at theResource level.
  386 + * b)Write-Attributes/3/0/9?pmin means the Battery Level will be notified
  387 + * to the Server with a minimum value (pmin) given by the default one
  388 + * (resource 2 of Object Server ID=1),
  389 + * or with another value if this Attribute has been set at another level
  390 + * (Object or Object Instance: see section5.1.1).
  391 + * c)Write-Attributes/3/0?pmin=10 means that all Resources of Instance 0 of the Object ‘Device (ID:3)’
  392 + * will be notified to the Server with a minimum interval of 10 sec;
  393 + * this value is set at the Object Instance level.
  394 + * d)Write-Attributes /3/0/9?gt=45&st=10 means the Battery Level will be notified to the Server
  395 + * when:
  396 + * a.old value is 20 and new value is 35 due to step condition
  397 + * b.old value is 45 and new value is 50 due to gt condition
  398 + * c.old value is 50 and new value is 40 due to both gt and step conditions
  399 + * d.old value is 35 and new value is 20 due to step conditione)
  400 + * Write-Attributes /3/0/9?lt=20&gt=85&st=10 means the Battery Level will be notified to the Server
  401 + * when:
  402 + * a.old value is 17 and new value is 24 due to lt condition
  403 + * b.old value is 75 and new value is 90 due to both gt and step conditions
  404 + * String uriQueries = "pmin=10&pmax=60";
  405 + * AttributeSet attributes = AttributeSet.parse(uriQueries);
  406 + * WriteAttributesRequest request = new WriteAttributesRequest(target, attributes);
  407 + * Attribute gt = new Attribute(GREATER_THAN, Double.valueOf("45"));
  408 + * Attribute st = new Attribute(LESSER_THAN, Double.valueOf("10"));
  409 + * Attribute pmax = new Attribute(MAXIMUM_PERIOD, "60");
  410 + * Attribute [] attrs = {gt, st};
  411 + */
  412 + public static DownlinkRequest createWriteAttributeRequest(String target, Object params) {
  413 + AttributeSet attrSet = new AttributeSet(createWriteAttributes(params));
  414 + return attrSet.getAttributes().size() > 0 ? new WriteAttributesRequest(target, attrSet) : null;
  415 + }
  416 +
  417 + private static Attribute[] createWriteAttributes(Object params) {
  418 + List attributeLists = new ArrayList<Attribute>();
  419 + ObjectMapper oMapper = new ObjectMapper();
  420 + Map<String, Object> map = oMapper.convertValue(params, ConcurrentHashMap.class);
  421 + map.forEach((k, v) -> {
  422 + if (!v.toString().isEmpty() || (v.toString().isEmpty() && OBJECT_VERSION.equals(k))) {
  423 + attributeLists.add(new Attribute(k,
  424 + (DIMENSION.equals(k) || MINIMUM_PERIOD.equals(k) || MAXIMUM_PERIOD.equals(k)) ?
  425 + ((Double) v).longValue() : v));
  426 + }
  427 + });
  428 + return (Attribute[]) attributeLists.toArray(Attribute[]::new);
  429 + }
363 430 }
... ...
... ... @@ -18,8 +18,6 @@ package org.thingsboard.server.transport.lwm2m.server;
18 18 import lombok.extern.slf4j.Slf4j;
19 19 import org.eclipse.californium.core.coap.CoAP;
20 20 import org.eclipse.californium.core.coap.Response;
21   -import org.eclipse.leshan.core.attributes.Attribute;
22   -import org.eclipse.leshan.core.attributes.AttributeSet;
23 21 import org.eclipse.leshan.core.model.ResourceModel;
24 22 import org.eclipse.leshan.core.node.LwM2mNode;
25 23 import org.eclipse.leshan.core.node.LwM2mPath;
... ... @@ -33,7 +31,6 @@ import org.eclipse.leshan.core.request.DownlinkRequest;
33 31 import org.eclipse.leshan.core.request.ExecuteRequest;
34 32 import org.eclipse.leshan.core.request.ObserveRequest;
35 33 import org.eclipse.leshan.core.request.ReadRequest;
36   -import org.eclipse.leshan.core.request.WriteAttributesRequest;
37 34 import org.eclipse.leshan.core.request.WriteRequest;
38 35 import org.eclipse.leshan.core.request.exception.ClientSleepingException;
39 36 import org.eclipse.leshan.core.response.CancelObservationResponse;
... ... @@ -60,7 +57,6 @@ import java.util.Date;
60 57 import java.util.concurrent.ExecutorService;
61 58 import java.util.concurrent.Executors;
62 59
63   -import static org.eclipse.leshan.core.attributes.Attribute.MINIMUM_PERIOD;
64 60 import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.DEFAULT_TIMEOUT;
65 61 import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.GET_TYPE_OPER_DISCOVER;
66 62 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
75 71 import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.RESPONSE_CHANNEL;
76 72 import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertToIdVerFromObjectId;
77 73 import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertToObjectIdFromIdVer;
  74 +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.createWriteAttributeRequest;
78 75
79 76 @Slf4j
80 77 @Service
... ... @@ -174,47 +171,7 @@ public class LwM2mTransportRequest {
174 171 }
175 172 break;
176 173 case PUT_TYPE_OPER_WRITE_ATTRIBUTES:
177   - /**
178   - * As example:
179   - * a)Write-Attributes/3/0/9?pmin=1 means the Battery Level value will be notified
180   - * to the Server with a minimum interval of 1sec;
181   - * this value is set at theResource level.
182   - * b)Write-Attributes/3/0/9?pmin means the Battery Level will be notified
183   - * to the Server with a minimum value (pmin) given by the default one
184   - * (resource 2 of Object Server ID=1),
185   - * or with another value if this Attribute has been set at another level
186   - * (Object or Object Instance: see section5.1.1).
187   - * c)Write-Attributes/3/0?pmin=10 means that all Resources of Instance 0 of the Object ‘Device (ID:3)’
188   - * will be notified to the Server with a minimum interval of 10 sec;
189   - * this value is set at the Object Instance level.
190   - * d)Write-Attributes /3/0/9?gt=45&st=10 means the Battery Level will be notified to the Server
191   - * when:
192   - * a.old value is 20 and new value is 35 due to step condition
193   - * b.old value is 45 and new value is 50 due to gt condition
194   - * c.old value is 50 and new value is 40 due to both gt and step conditions
195   - * d.old value is 35 and new value is 20 due to step conditione)
196   - * Write-Attributes /3/0/9?lt=20&gt=85&st=10 means the Battery Level will be notified to the Server
197   - * when:
198   - * a.old value is 17 and new value is 24 due to lt condition
199   - * b.old value is 75 and new value is 90 due to both gt and step conditions
200   - * String uriQueries = "pmin=10&pmax=60";
201   - * AttributeSet attributes = AttributeSet.parse(uriQueries);
202   - * WriteAttributesRequest request = new WriteAttributesRequest(target, attributes);
203   - * Attribute gt = new Attribute(GREATER_THAN, Double.valueOf("45"));
204   - * Attribute st = new Attribute(LESSER_THAN, Double.valueOf("10"));
205   - * Attribute pmax = new Attribute(MAXIMUM_PERIOD, "60");
206   - * Attribute [] attrs = {gt, st};
207   - */
208   - Attribute pmin = new Attribute(MINIMUM_PERIOD, Integer.toUnsignedLong(Integer.parseInt("1")));
209   - Attribute[] attrs = {pmin};
210   - AttributeSet attrSet = new AttributeSet(attrs);
211   - if (resultIds.isResource()) {
212   - request = new WriteAttributesRequest(resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId(), attrSet);
213   - } else if (resultIds.isObjectInstance()) {
214   - request = new WriteAttributesRequest(resultIds.getObjectId(), resultIds.getObjectInstanceId(), attrSet);
215   - } else if (resultIds.getObjectId() >= 0) {
216   - request = new WriteAttributesRequest(resultIds.getObjectId(), attrSet);
217   - }
  174 + request = createWriteAttributeRequest (target, params);
218 175 break;
219 176 }
220 177
... ...
... ... @@ -51,6 +51,10 @@ public interface LwM2mTransportService {
51 51
52 52 void onResourceDelete(Optional<TransportProtos.ResourceDeleteMsg> resourceDeleteMsgOpt);
53 53
  54 + void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg toDeviceRequest);
  55 +
  56 + void onToServerRpcResponse(TransportProtos.ToServerRpcResponseMsg toServerResponse);
  57 +
54 58 void doTrigger(Registration registration, String path);
55 59
56 60 void doDisconnect(TransportProtos.SessionInfoProto sessionInfo);
... ...
... ... @@ -74,11 +74,14 @@ import java.util.concurrent.Executors;
74 74 import java.util.concurrent.TimeUnit;
75 75 import java.util.stream.Collectors;
76 76
  77 +import static org.eclipse.leshan.core.attributes.Attribute.OBJECT_VERSION;
  78 +import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH;
77 79 import static org.thingsboard.server.common.transport.util.JsonUtils.getJsonObject;
78 80 import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.CLIENT_NOT_AUTHORIZED;
79 81 import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.DEVICE_ATTRIBUTES_REQUEST;
80 82 import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.DEVICE_ATTRIBUTES_TOPIC;
81 83 import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.DEVICE_TELEMETRY_TOPIC;
  84 +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.GET_TYPE_OPER_DISCOVER;
82 85 import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.GET_TYPE_OPER_OBSERVE;
83 86 import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.GET_TYPE_OPER_READ;
84 87 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
87 90 import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LWM2M_STRATEGY_2;
88 91 import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.POST_TYPE_OPER_EXECUTE;
89 92 import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.POST_TYPE_OPER_WRITE_REPLACE;
  93 +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.PUT_TYPE_OPER_WRITE_ATTRIBUTES;
90 94 import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.SERVICE_CHANNEL;
91 95 import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertToIdVerFromObjectId;
92 96 import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertToObjectIdFromIdVer;
... ... @@ -213,7 +217,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
213 217 * !!! Warn: if have not finishing unReg, then this operation will be finished on next Client`s connect
214 218 */
215 219 public void unReg(Registration registration, Collection<Observation> observations) {
216   - executorUnRegistered.submit(() -> {
  220 + executorUnRegistered.submit(() -> {
217 221 try {
218 222 this.setCancelObservations(registration);
219 223 this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client unRegistration", registration);
... ... @@ -365,17 +369,15 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
365 369 }
366 370
367 371 /**
368   - *
369 372 * @param resourceUpdateMsgOpt -
370 373 */
371 374 @Override
372   - public void onResourceUpdate (Optional<TransportProtos.ResourceUpdateMsg> resourceUpdateMsgOpt) {
  375 + public void onResourceUpdate(Optional<TransportProtos.ResourceUpdateMsg> resourceUpdateMsgOpt) {
373 376 String idVer = resourceUpdateMsgOpt.get().getResourceKey();
374 377 lwM2mClientContext.getLwM2mClients().values().stream().forEach(e -> e.updateResourceModel(idVer, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getModelProvider()));
375 378 }
376 379
377 380 /**
378   - *
379 381 * @param resourceDeleteMsgOpt -
380 382 */
381 383 @Override
... ... @@ -384,6 +386,14 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
384 386 lwM2mClientContext.getLwM2mClients().values().stream().forEach(e -> e.deleteResources(pathIdVer, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getModelProvider()));
385 387 }
386 388
  389 + public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg toDeviceRequest) {
  390 + log.info("[{}] toDeviceRpcRequest", toDeviceRequest);
  391 + }
  392 +
  393 + public void onToServerRpcResponse(TransportProtos.ToServerRpcResponseMsg toServerResponse) {
  394 + log.info("[{}] toServerRpcResponse", toServerResponse);
  395 + }
  396 +
387 397 /**
388 398 * Trigger Server path = "/1/0/8"
389 399 * <p>
... ... @@ -496,15 +506,19 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
496 506 */
497 507 private void initLwM2mFromClientValue(Registration registration, LwM2mClient lwM2MClient) {
498 508 LwM2mClientProfile lwM2MClientProfile = lwM2mClientContext.getProfile(registration);
499   - Set<String> clientObjects = this.getAllOjectsInClient(registration);
500   - if (clientObjects != null && LWM2M_STRATEGY_2 == LwM2mTransportHandler.getClientOnlyObserveAfterConnect(lwM2MClientProfile)) {
501   - // #2
502   - lwM2MClient.getPendingRequests().addAll(clientObjects);
503   - clientObjects.forEach(path -> lwM2mTransportRequest.sendAllRequest(registration, path, GET_TYPE_OPER_READ, ContentFormat.TLV.getName(),
504   - null, null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout()));
  509 + Set<String> clientObjects = lwM2mClientContext.getSupportedIdVerInClient(registration);
  510 + if (clientObjects != null && clientObjects.size() > 0) {
  511 + if (LWM2M_STRATEGY_2 == LwM2mTransportHandler.getClientOnlyObserveAfterConnect(lwM2MClientProfile)) {
  512 + // #2
  513 + lwM2MClient.getPendingRequests().addAll(clientObjects);
  514 + clientObjects.forEach(path -> lwM2mTransportRequest.sendAllRequest(registration, path, GET_TYPE_OPER_READ, ContentFormat.TLV.getName(),
  515 + null, null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout()));
  516 + }
  517 + // #1
  518 + this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, GET_TYPE_OPER_OBSERVE, clientObjects);
  519 + this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, PUT_TYPE_OPER_WRITE_ATTRIBUTES, clientObjects);
  520 + this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, GET_TYPE_OPER_DISCOVER, clientObjects);
505 521 }
506   - // #1
507   - this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, GET_TYPE_OPER_OBSERVE);
508 522 }
509 523
510 524 /**
... ... @@ -611,41 +625,61 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
611 625
612 626 /**
613 627 * Start observe/read: Attr/Telemetry
614   - * #1 - Analyze:
615   - * #1.1 path in resource profile == client resource
  628 + * #1 - Analyze: path in resource profile == client resource
616 629 *
617 630 * @param registration -
618 631 */
619   - private void initReadAttrTelemetryObserveToClient(Registration registration, LwM2mClient lwM2MClient, String typeOper) {
  632 + private void initReadAttrTelemetryObserveToClient(Registration registration, LwM2mClient lwM2MClient,
  633 + String typeOper, Set<String> clientObjects) {
620 634 LwM2mClientProfile lwM2MClientProfile = lwM2mClientContext.getProfile(registration);
621   - Set<String> clientInstances = this.getAllInstancesInClient(registration);
622   - Set<String> result;
  635 + Set<String> result = null;
  636 + ConcurrentHashMap<String, Object> params = null;
623 637 if (GET_TYPE_OPER_READ.equals(typeOper)) {
624   - result = JacksonUtil.fromString(lwM2MClientProfile.getPostAttributeProfile().toString(), new TypeReference<>() {
625   - });
626   - result.addAll(JacksonUtil.convertValue(lwM2MClientProfile.getPostTelemetryProfile().toString(), new TypeReference<>() {
627   - }));
628   - } else {
629   - result = JacksonUtil.fromString(lwM2MClientProfile.getPostObserveProfile().toString(), new TypeReference<>() {
630   - });
  638 + result = JacksonUtil.fromString(lwM2MClientProfile.getPostAttributeProfile().toString(),
  639 + new TypeReference<>() {
  640 + });
  641 + result.addAll(JacksonUtil.fromString(lwM2MClientProfile.getPostTelemetryProfile().toString(),
  642 + new TypeReference<>() {
  643 + }));
  644 + } else if (GET_TYPE_OPER_OBSERVE.equals(typeOper)) {
  645 + result = JacksonUtil.fromString(lwM2MClientProfile.getPostObserveProfile().toString(),
  646 + new TypeReference<>() {
  647 + });
  648 + } else if (GET_TYPE_OPER_DISCOVER.equals(typeOper)) {
  649 + result = this.getPathForWriteAttributes(lwM2MClientProfile.getPostAttributeLwm2mProfile()).keySet();
  650 + ;
  651 + } else if (PUT_TYPE_OPER_WRITE_ATTRIBUTES.equals(typeOper)) {
  652 + params = this.getPathForWriteAttributes(lwM2MClientProfile.getPostAttributeLwm2mProfile());
  653 + result = params.keySet();
631 654 }
632   - Set<String> pathSend = ConcurrentHashMap.newKeySet();
633   - result.forEach(target -> {
634   - // #1.1
635   - String[] resPath = target.split("/");
636   - String instance = "/" + resPath[1] + "/" + resPath[2];
637   - if (clientInstances != null && clientInstances.size() > 0 && clientInstances.contains(instance)) {
638   - pathSend.add(target);
  655 + if (!result.isEmpty()) {
  656 + // #1
  657 + Set<String> pathSend = result.stream().filter(target -> {
  658 + return target.split(LWM2M_SEPARATOR_PATH).length < 3 ?
  659 + clientObjects.contains("/" + target.split(LWM2M_SEPARATOR_PATH)[1]) :
  660 + clientObjects.contains("/" + target.split(LWM2M_SEPARATOR_PATH)[1] + "/" + target.split(LWM2M_SEPARATOR_PATH)[2]);
  661 + }
  662 + )
  663 + .collect(Collectors.toUnmodifiableSet());
  664 + if (!pathSend.isEmpty()) {
  665 + lwM2MClient.getPendingRequests().addAll(pathSend);
  666 + ConcurrentHashMap<String, Object> finalParams = params;
  667 + pathSend.forEach(target -> lwM2mTransportRequest.sendAllRequest(registration, target, typeOper, ContentFormat.TLV.getName(),
  668 + null, finalParams != null ? finalParams.get(target) : null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout()));
  669 + if (GET_TYPE_OPER_OBSERVE.equals(typeOper)) {
  670 + lwM2MClient.initValue(this, null);
  671 + }
639 672 }
640   - });
641   - lwM2MClient.getPendingRequests().addAll(pathSend);
642   - pathSend.forEach(target -> lwM2mTransportRequest.sendAllRequest(registration, target, typeOper, ContentFormat.TLV.getName(),
643   - null, null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout()));
644   - if (GET_TYPE_OPER_OBSERVE.equals(typeOper)) {
645   - lwM2MClient.initValue(this, null);
646 673 }
647 674 }
648 675
  676 + private ConcurrentHashMap<String, Object> getPathForWriteAttributes(JsonObject objectJson) {
  677 + ConcurrentHashMap<String, Object> pathAttributes = new Gson().fromJson(objectJson.toString(),
  678 + new TypeToken<ConcurrentHashMap<String, Object>>() {
  679 + }.getType());
  680 + return pathAttributes;
  681 + }
  682 +
649 683 /**
650 684 * Update parameters device in LwM2MClient
651 685 * If new deviceProfile != old deviceProfile => update deviceProfile
... ... @@ -667,21 +701,6 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
667 701
668 702 /**
669 703 * @param registration -
670   - * @return - all object in client
671   - */
672   - private Set<String> getAllOjectsInClient(Registration registration) {
673   - Set<String> clientObjects = ConcurrentHashMap.newKeySet();
674   - Arrays.stream(registration.getObjectLinks()).forEach(url -> {
675   - LwM2mPath pathIds = new LwM2mPath(url.getUrl());
676   - if (pathIds.isObjectInstance()) {
677   - clientObjects.add("/" + pathIds.getObjectId());
678   - }
679   - });
680   - return (clientObjects.size() > 0) ? clientObjects : null;
681   - }
682   -
683   - /**
684   - * @param registration -
685 704 * @return all instances in client
686 705 */
687 706 private Set<String> getAllInstancesInClient(Registration registration) {
... ... @@ -724,16 +743,20 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
724 743 private void addParameters(String path, JsonObject parameters, Registration registration) {
725 744 LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(registration, null);
726 745 JsonObject names = lwM2mClientContext.getProfiles().get(lwM2MClient.getProfileId()).getPostKeyNameProfile();
727   - String resName = names.get(path).getAsString();
728   - if (resName != null && !resName.isEmpty()) {
729   - try {
730   - String resValue = this.getResourceValueToString(lwM2MClient, path);
731   - if (resValue != null) {
732   - parameters.addProperty(resName, resValue);
  746 + if (names != null && names.has(path)) {
  747 + String resName = names.get(path).getAsString();
  748 + if (resName != null && !resName.isEmpty()) {
  749 + try {
  750 + String resValue = this.getResourceValueToString(lwM2MClient, path);
  751 + if (resValue != null) {
  752 + parameters.addProperty(resName, resValue);
  753 + }
  754 + } catch (Exception e) {
  755 + log.error("Failed to add parameters.", e);
733 756 }
734   - } catch (Exception e) {
735   - log.error("Failed to add parameters.", e);
736 757 }
  758 + } else {
  759 + log.error("Failed to add parameters. path: [{}], names: [{}]", path, names);
737 760 }
738 761 }
739 762
... ... @@ -750,7 +773,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
750 773
751 774 /**
752 775 * @param lwM2MClient -
753   - * @param path -
  776 + * @param path -
754 777 * @return - return value of Resource by idPath
755 778 */
756 779 private LwM2mResource returnResourceValueFromLwM2MClient(LwM2mClient lwM2MClient, String path) {
... ... @@ -779,6 +802,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
779 802 * #3.1 Attribute isChange (add&del)
780 803 * #3.2 Telemetry isChange (add&del)
781 804 * #3.3 KeyName isChange (add)
  805 + * #3.4 attributeLwm2m isChange (update WrightAttribute: add/update/del)
782 806 * #4 update
783 807 * #4.1 add If #3 isChange, then analyze and update Value in Transport form Client and send Value to thingsboard
784 808 * #4.2 del
... ... @@ -789,6 +813,9 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
789 813 * -- path Attr/Telemetry includes newObserve and does not include oldObserve: send Request observe to Client
790 814 * #5.3 Observe.del
791 815 * -- different between newObserve and oldObserve: send Request cancel observe to client
  816 + * #6
  817 + * #6.1 - update WriteAttribute
  818 + * #6.2 - del WriteAttribute
792 819 *
793 820 * @param registrationIds -
794 821 * @param deviceProfile -
... ... @@ -803,6 +830,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
803 830 Set<String> telemetrySetOld = this.convertJsonArrayToSet(telemetryOld);
804 831 JsonArray observeOld = lwM2MClientProfileOld.getPostObserveProfile();
805 832 JsonObject keyNameOld = lwM2MClientProfileOld.getPostKeyNameProfile();
  833 + JsonObject attributeLwm2mOld = lwM2MClientProfileOld.getPostAttributeLwm2mProfile();
806 834
807 835 LwM2mClientProfile lwM2MClientProfileNew = lwM2mClientContext.getProfiles().get(deviceProfile.getUuidId());
808 836 JsonArray attributeNew = lwM2MClientProfileNew.getPostAttributeProfile();
... ... @@ -811,32 +839,41 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
811 839 Set<String> telemetrySetNew = this.convertJsonArrayToSet(telemetryNew);
812 840 JsonArray observeNew = lwM2MClientProfileNew.getPostObserveProfile();
813 841 JsonObject keyNameNew = lwM2MClientProfileNew.getPostKeyNameProfile();
  842 + JsonObject attributeLwm2mNew = lwM2MClientProfileNew.getPostAttributeLwm2mProfile();
814 843
815 844 // #3
816 845 ResultsAnalyzerParameters sendAttrToThingsboard = new ResultsAnalyzerParameters();
817 846 // #3.1
818 847 if (!attributeOld.equals(attributeNew)) {
819   - ResultsAnalyzerParameters postAttributeAnalyzer = this.getAnalyzerParameters(new Gson().fromJson(attributeOld, new TypeToken<Set<String>>() {
820   - }.getType()), attributeSetNew);
  848 + ResultsAnalyzerParameters postAttributeAnalyzer = this.getAnalyzerParameters(new Gson().fromJson(attributeOld,
  849 + new TypeToken<Set<String>>() {
  850 + }.getType()), attributeSetNew);
821 851 sendAttrToThingsboard.getPathPostParametersAdd().addAll(postAttributeAnalyzer.getPathPostParametersAdd());
822 852 sendAttrToThingsboard.getPathPostParametersDel().addAll(postAttributeAnalyzer.getPathPostParametersDel());
823 853 }
824 854 // #3.2
825 855 if (!telemetryOld.equals(telemetryNew)) {
826   - ResultsAnalyzerParameters postTelemetryAnalyzer = this.getAnalyzerParameters(new Gson().fromJson(telemetryOld, new TypeToken<Set<String>>() {
827   - }.getType()), telemetrySetNew);
  856 + ResultsAnalyzerParameters postTelemetryAnalyzer = this.getAnalyzerParameters(new Gson().fromJson(telemetryOld,
  857 + new TypeToken<Set<String>>() {
  858 + }.getType()), telemetrySetNew);
828 859 sendAttrToThingsboard.getPathPostParametersAdd().addAll(postTelemetryAnalyzer.getPathPostParametersAdd());
829 860 sendAttrToThingsboard.getPathPostParametersDel().addAll(postTelemetryAnalyzer.getPathPostParametersDel());
830 861 }
831 862 // #3.3
832 863 if (!keyNameOld.equals(keyNameNew)) {
833   - ResultsAnalyzerParameters keyNameChange = this.getAnalyzerKeyName(new Gson().fromJson(keyNameOld.toString(), new TypeToken<ConcurrentHashMap<String, String>>() {
  864 + ResultsAnalyzerParameters keyNameChange = this.getAnalyzerKeyName(new Gson().fromJson(keyNameOld.toString(),
  865 + new TypeToken<ConcurrentHashMap<String, String>>() {
834 866 }.getType()),
835 867 new Gson().fromJson(keyNameNew.toString(), new TypeToken<ConcurrentHashMap<String, String>>() {
836 868 }.getType()));
837 869 sendAttrToThingsboard.getPathPostParametersAdd().addAll(keyNameChange.getPathPostParametersAdd());
838 870 }
839 871
  872 + // #3.4, #6
  873 + if (!attributeLwm2mOld.equals(attributeLwm2mNew)) {
  874 + this.getAnalyzerAttributeLwm2m(registrationIds, attributeLwm2mOld, attributeLwm2mNew);
  875 + }
  876 +
840 877 // #4.1 add
841 878 if (sendAttrToThingsboard.getPathPostParametersAdd().size() > 0) {
842 879 // update value in Resources
... ... @@ -933,7 +970,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
933 970 });
934 971 }
935 972
936   - private ResultsAnalyzerParameters getAnalyzerKeyName(ConcurrentMap<String, String> keyNameOld, ConcurrentMap<String, String> keyNameNew) {
  973 + private ResultsAnalyzerParameters getAnalyzerKeyName(ConcurrentHashMap<String, String> keyNameOld, ConcurrentHashMap<String, String> keyNameNew) {
937 974 ResultsAnalyzerParameters analyzerParameters = new ResultsAnalyzerParameters();
938 975 Set<String> paths = keyNameNew.entrySet()
939 976 .stream()
... ... @@ -943,6 +980,71 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
943 980 return analyzerParameters;
944 981 }
945 982
  983 + /**
  984 + * #3.4, #6
  985 + * #6
  986 + * #6.1 - send update WriteAttribute
  987 + * #6.2 - send empty WriteAttribute
  988 + *
  989 + * @param attributeLwm2mOld -
  990 + * @param attributeLwm2mNew -
  991 + * @return
  992 + */
  993 + private void getAnalyzerAttributeLwm2m(Set<String> registrationIds, JsonObject attributeLwm2mOld, JsonObject attributeLwm2mNew) {
  994 + ResultsAnalyzerParameters analyzerParameters = new ResultsAnalyzerParameters();
  995 + ConcurrentHashMap<String, Object> lwm2mAttributesOld = new Gson().fromJson(attributeLwm2mOld.toString(),
  996 + new TypeToken<ConcurrentHashMap<String, Object>>() {
  997 + }.getType());
  998 + ConcurrentHashMap<String, Object> lwm2mAttributesNew = new Gson().fromJson(attributeLwm2mNew.toString(),
  999 + new TypeToken<ConcurrentHashMap<String, Object>>() {
  1000 + }.getType());
  1001 + Set<String> pathOld = lwm2mAttributesOld.keySet();
  1002 + Set<String> pathNew = lwm2mAttributesNew.keySet();
  1003 + analyzerParameters.setPathPostParametersAdd(pathNew
  1004 + .stream().filter(p -> !pathOld.contains(p)).collect(Collectors.toSet()));
  1005 + analyzerParameters.setPathPostParametersDel(pathOld
  1006 + .stream().filter(p -> !pathNew.contains(p)).collect(Collectors.toSet()));
  1007 + Set<String> pathCommon = pathNew
  1008 + .stream().filter(p -> pathOld.contains(p)).collect(Collectors.toSet());
  1009 + Set<String> pathCommonChange = pathCommon
  1010 + .stream().filter(p -> !lwm2mAttributesOld.get(p).equals(lwm2mAttributesNew.get(p))).collect(Collectors.toSet());
  1011 + analyzerParameters.getPathPostParametersAdd().addAll(pathCommonChange);
  1012 + // #6
  1013 + // #6.2
  1014 + if (analyzerParameters.getPathPostParametersAdd().size() > 0) {
  1015 + registrationIds.forEach(registrationId -> {
  1016 + Registration registration = this.lwM2mClientContext.getRegistration(registrationId);
  1017 + Set<String> clientObjects = lwM2mClientContext.getSupportedIdVerInClient(registration);
  1018 + Set<String> pathSend = analyzerParameters.getPathPostParametersAdd().stream().filter(target -> clientObjects.contains("/" + target.split(LWM2M_SEPARATOR_PATH)[1]))
  1019 + .collect(Collectors.toUnmodifiableSet());
  1020 + if (!pathSend.isEmpty()) {
  1021 + ConcurrentHashMap<String, Object> finalParams = lwm2mAttributesNew;
  1022 + pathSend.forEach(target -> lwM2mTransportRequest.sendAllRequest(registration, target, PUT_TYPE_OPER_WRITE_ATTRIBUTES, ContentFormat.TLV.getName(),
  1023 + null, finalParams.get(target), this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout()));
  1024 + }
  1025 + });
  1026 + }
  1027 + // #6.2
  1028 + if (analyzerParameters.getPathPostParametersDel().size() > 0) {
  1029 + registrationIds.forEach(registrationId -> {
  1030 + Registration registration = this.lwM2mClientContext.getRegistration(registrationId);
  1031 + Set<String> clientObjects = lwM2mClientContext.getSupportedIdVerInClient(registration);
  1032 + Set<String> pathSend = analyzerParameters.getPathPostParametersDel().stream().filter(target -> clientObjects.contains("/" + target.split(LWM2M_SEPARATOR_PATH)[1]))
  1033 + .collect(Collectors.toUnmodifiableSet());
  1034 + if (!pathSend.isEmpty()) {
  1035 + pathSend.forEach(target -> {
  1036 + Map<String, Object> params = (Map<String, Object>) lwm2mAttributesOld.get(target);
  1037 + params.clear();
  1038 + params.put(OBJECT_VERSION, "");
  1039 + lwM2mTransportRequest.sendAllRequest(registration, target, PUT_TYPE_OPER_WRITE_ATTRIBUTES, ContentFormat.TLV.getName(),
  1040 + null, params, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout());
  1041 + });
  1042 + }
  1043 + });
  1044 + }
  1045 +
  1046 + }
  1047 +
946 1048 private void cancelObserveIsValue(Registration registration, Set<String> paramAnallyzer) {
947 1049 LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(registration, null);
948 1050 paramAnallyzer.forEach(p -> {
... ...
... ... @@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.DeviceProfile;
20 20 import org.thingsboard.server.gen.transport.TransportProtos;
21 21
22 22 import java.util.Map;
  23 +import java.util.Set;
23 24 import java.util.UUID;
24 25
25 26 public interface LwM2mClientContext {
... ... @@ -51,4 +52,6 @@ public interface LwM2mClientContext {
51 52 Map<UUID, LwM2mClientProfile> setProfiles(Map<UUID, LwM2mClientProfile> profiles);
52 53
53 54 boolean addUpdateProfileParameters(DeviceProfile deviceProfile);
  55 +
  56 + Set<String> getSupportedIdVerInClient(Registration registration);
54 57 }
... ...
... ... @@ -15,6 +15,7 @@
15 15 */
16 16 package org.thingsboard.server.transport.lwm2m.server.client;
17 17
  18 +import org.eclipse.leshan.core.node.LwM2mPath;
18 19 import org.eclipse.leshan.server.registration.Registration;
19 20 import org.eclipse.leshan.server.security.EditableSecurityStore;
20 21 import org.springframework.stereotype.Service;
... ... @@ -27,11 +28,14 @@ import org.thingsboard.server.transport.lwm2m.secure.ReadResultSecurityStore;
27 28 import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler;
28 29 import org.thingsboard.server.transport.lwm2m.utils.TypeServer;
29 30
  31 +import java.util.Arrays;
30 32 import java.util.Map;
  33 +import java.util.Set;
31 34 import java.util.UUID;
32 35 import java.util.concurrent.ConcurrentHashMap;
33 36
34 37 import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.NO_SEC;
  38 +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertToIdVerFromObjectId;
35 39
36 40 @Service
37 41 @TbLwM2mTransportComponent
... ... @@ -90,7 +94,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
90 94 @Override
91 95 public LwM2mClient updateInSessionsLwM2MClient(Registration registration) {
92 96 if (this.lwM2mClients.get(registration.getEndpoint()) == null) {
93   - addLwM2mClientToSession(registration.getEndpoint());
  97 + this.addLwM2mClientToSession(registration.getEndpoint());
94 98 }
95 99 LwM2mClient lwM2MClient = lwM2mClients.get(registration.getEndpoint());
96 100 lwM2MClient.setRegistration(registration);
... ... @@ -169,4 +173,21 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
169 173 }
170 174 return false;
171 175 }
  176 +
  177 + /**
  178 + * if isVer - ok or default ver=DEFAULT_LWM2M_VERSION
  179 + * @param registration -
  180 + * @return - all objectIdVer in client
  181 + */
  182 + @Override
  183 + public Set<String> getSupportedIdVerInClient(Registration registration) {
  184 + Set<String> clientObjects = ConcurrentHashMap.newKeySet();
  185 + Arrays.stream(registration.getObjectLinks()).forEach(url -> {
  186 + LwM2mPath pathIds = new LwM2mPath(url.getUrl());
  187 + if (!pathIds.isRoot()) {
  188 + clientObjects.add(convertToIdVerFromObjectId(url.getUrl(), registration));
  189 + }
  190 + });
  191 + return (clientObjects.size() > 0) ? clientObjects : null;
  192 + }
172 193 }
... ...
... ... @@ -56,6 +56,13 @@ public class LwM2mClientProfile {
56 56 */
57 57 private JsonArray postObserveProfile;
58 58
  59 + /**
  60 + * "attributeLwm2m": {"/3_1.0": {"ver": "currentTimeTest11"},
  61 + * "/3_1.0/0": {"gt": 17},
  62 + * "/3_1.0/0/9": {"pmax": 45}, "/3_1.2": {ver": "3_1.2"}}
  63 + */
  64 + private JsonObject postAttributeLwm2mProfile;
  65 +
59 66 public LwM2mClientProfile clone() {
60 67 LwM2mClientProfile lwM2mClientProfile = new LwM2mClientProfile();
61 68 lwM2mClientProfile.postClientLwM2mSettings = this.deepCopy(this.postClientLwM2mSettings, JsonObject.class);
... ... @@ -63,6 +70,7 @@ public class LwM2mClientProfile {
63 70 lwM2mClientProfile.postAttributeProfile = this.deepCopy(this.postAttributeProfile, JsonArray.class);
64 71 lwM2mClientProfile.postTelemetryProfile = this.deepCopy(this.postTelemetryProfile, JsonArray.class);
65 72 lwM2mClientProfile.postObserveProfile = this.deepCopy(this.postObserveProfile, JsonArray.class);
  73 + lwM2mClientProfile.postAttributeLwm2mProfile = this.deepCopy(this.postAttributeLwm2mProfile, JsonObject.class);
66 74 return lwM2mClientProfile;
67 75 }
68 76
... ...
... ... @@ -23,6 +23,7 @@ import org.springframework.beans.factory.annotation.Autowired;
23 23 import org.springframework.beans.factory.annotation.Value;
24 24 import org.springframework.stereotype.Service;
25 25 import org.thingsboard.server.common.data.Event;
  26 +import org.thingsboard.server.common.data.event.EventFilter;
26 27 import org.thingsboard.server.common.data.id.EntityId;
27 28 import org.thingsboard.server.common.data.id.TenantId;
28 29 import org.thingsboard.server.common.data.page.PageData;
... ... @@ -30,7 +31,6 @@ import org.thingsboard.server.common.data.page.TimePageLink;
30 31 import org.thingsboard.server.dao.exception.DataValidationException;
31 32 import org.thingsboard.server.dao.service.DataValidator;
32 33
33   -import java.nio.charset.StandardCharsets;
34 34 import java.util.List;
35 35 import java.util.Optional;
36 36
... ... @@ -112,6 +112,11 @@ public class BaseEventService implements EventService {
112 112 }
113 113
114 114 @Override
  115 + public PageData<Event> findEventsByFilter(TenantId tenantId, EntityId entityId, EventFilter eventFilter, TimePageLink pageLink) {
  116 + return eventDao.findEventByFilter(tenantId.getId(), entityId, eventFilter, pageLink);
  117 + }
  118 +
  119 + @Override
115 120 public void removeEvents(TenantId tenantId, EntityId entityId) {
116 121 PageData<Event> eventPageData;
117 122 TimePageLink eventPageLink = new TimePageLink(1000);
... ...
... ... @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.event;
17 17
18 18 import com.google.common.util.concurrent.ListenableFuture;
19 19 import org.thingsboard.server.common.data.Event;
  20 +import org.thingsboard.server.common.data.event.EventFilter;
20 21 import org.thingsboard.server.common.data.id.EntityId;
21 22 import org.thingsboard.server.common.data.id.TenantId;
22 23 import org.thingsboard.server.common.data.page.PageData;
... ... @@ -88,6 +89,8 @@ public interface EventDao extends Dao<Event> {
88 89 */
89 90 PageData<Event> findEvents(UUID tenantId, EntityId entityId, String eventType, TimePageLink pageLink);
90 91
  92 + PageData<Event> findEventByFilter(UUID tenantId, EntityId entityId, EventFilter eventFilter, TimePageLink pageLink);
  93 +
91 94 /**
92 95 * Find latest events by tenantId, entityId and eventType.
93 96 *
... ...
... ... @@ -44,11 +44,11 @@ public interface EventRepository extends PagingAndSortingRepository<EventEntity,
44 44 @Query("SELECT e FROM EventEntity e WHERE e.tenantId = :tenantId AND e.entityType = :entityType " +
45 45 "AND e.entityId = :entityId AND e.eventType = :eventType ORDER BY e.eventType DESC, e.id DESC")
46 46 List<EventEntity> findLatestByTenantIdAndEntityTypeAndEntityIdAndEventType(
47   - @Param("tenantId") UUID tenantId,
48   - @Param("entityType") EntityType entityType,
49   - @Param("entityId") UUID entityId,
50   - @Param("eventType") String eventType,
51   - Pageable pageable);
  47 + @Param("tenantId") UUID tenantId,
  48 + @Param("entityType") EntityType entityType,
  49 + @Param("entityId") UUID entityId,
  50 + @Param("eventType") String eventType,
  51 + Pageable pageable);
52 52
53 53 @Query("SELECT e FROM EventEntity e WHERE " +
54 54 "e.tenantId = :tenantId " +
... ... @@ -80,4 +80,165 @@ public interface EventRepository extends PagingAndSortingRepository<EventEntity,
80 80 @Param("endTime") Long endTime,
81 81 Pageable pageable);
82 82
  83 + @Query(nativeQuery = true,
  84 + 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 " +
  85 + "(SELECT *, e.body\\:\\:jsonb as json_body FROM event e WHERE " +
  86 + "e.tenant_id = :tenantId " +
  87 + "AND e.entity_type = :entityType " +
  88 + "AND e.entity_id = :entityId " +
  89 + "AND e.event_type = :eventType " +
  90 + "AND e.created_time >= :startTime AND (:endTime = 0 OR e.created_time <= :endTime) " +
  91 + ") AS e WHERE " +
  92 + "(:type IS NULL OR lower(json_body->>'type') LIKE concat('%', lower(:type\\:\\:varchar), '%')) " +
  93 + "AND (:server IS NULL OR lower(json_body->>'server') LIKE concat('%', lower(:server\\:\\:varchar), '%')) " +
  94 + "AND (:entityName IS NULL OR lower(json_body->>'entityName') LIKE concat('%', lower(:entityName\\:\\:varchar), '%')) " +
  95 + "AND (:relationType IS NULL OR lower(json_body->>'relationType') LIKE concat('%', lower(:relationType\\:\\:varchar), '%')) " +
  96 + "AND (:bodyEntityId IS NULL OR lower(json_body->>'entityId') LIKE concat('%', lower(:bodyEntityId\\:\\:varchar), '%')) " +
  97 + "AND (:msgType IS NULL OR lower(json_body->>'msgType') LIKE concat('%', lower(:msgType\\:\\:varchar), '%')) " +
  98 + "AND ((:isError = FALSE) OR (json_body->>'error') IS NOT NULL) " +
  99 + "AND (:error IS NULL OR lower(json_body->>'error') LIKE concat('%', lower(:error\\:\\:varchar), '%')) " +
  100 + "AND (:data IS NULL OR lower(json_body->>'data') LIKE concat('%', lower(:data\\:\\:varchar), '%')) " +
  101 + "AND (:metadata IS NULL OR lower(json_body->>'metadata') LIKE concat('%', lower(:metadata\\:\\:varchar), '%')) ",
  102 + countQuery = "SELECT count(*) FROM " +
  103 + "(SELECT *, e.body\\:\\:jsonb as json_body FROM event e WHERE " +
  104 + "e.tenant_id = :tenantId " +
  105 + "AND e.entity_type = :entityType " +
  106 + "AND e.entity_id = :entityId " +
  107 + "AND e.event_type = :eventType " +
  108 + "AND e.created_time >= :startTime AND (:endTime = 0 OR e.created_time <= :endTime) " +
  109 + ") AS e WHERE " +
  110 + "(:type IS NULL OR lower(json_body->>'type') LIKE concat('%', lower(:type\\:\\:varchar), '%')) " +
  111 + "AND (:server IS NULL OR lower(json_body->>'server') LIKE concat('%', lower(:server\\:\\:varchar), '%')) " +
  112 + "AND (:entityName IS NULL OR lower(json_body->>'entityName') LIKE concat('%', lower(:entityName\\:\\:varchar), '%')) " +
  113 + "AND (:relationType IS NULL OR lower(json_body->>'relationType') LIKE concat('%', lower(:relationType\\:\\:varchar), '%')) " +
  114 + "AND (:bodyEntityId IS NULL OR lower(json_body->>'entityId') LIKE concat('%', lower(:bodyEntityId\\:\\:varchar), '%')) " +
  115 + "AND (:msgType IS NULL OR lower(json_body->>'msgType') LIKE concat('%', lower(:msgType\\:\\:varchar), '%')) " +
  116 + "AND ((:isError = FALSE) OR (json_body->>'error') IS NOT NULL) " +
  117 + "AND (:error IS NULL OR lower(json_body->>'error') LIKE concat('%', lower(:error\\:\\:varchar), '%')) " +
  118 + "AND (:data IS NULL OR lower(json_body->>'data') LIKE concat('%', lower(:data\\:\\:varchar), '%')) " +
  119 + "AND (:metadata IS NULL OR lower(json_body->>'metadata') LIKE concat('%', lower(:metadata\\:\\:varchar), '%'))"
  120 + )
  121 + Page<EventEntity> findDebugRuleNodeEvents(@Param("tenantId") UUID tenantId,
  122 + @Param("entityId") UUID entityId,
  123 + @Param("entityType") String entityType,
  124 + @Param("eventType") String eventType,
  125 + @Param("startTime") Long startTime,
  126 + @Param("endTime") Long endTime,
  127 + @Param("type") String type,
  128 + @Param("server") String server,
  129 + @Param("entityName") String entityName,
  130 + @Param("relationType") String relationType,
  131 + @Param("bodyEntityId") String bodyEntityId,
  132 + @Param("msgType") String msgType,
  133 + @Param("isError") boolean isError,
  134 + @Param("error") String error,
  135 + @Param("data") String data,
  136 + @Param("metadata") String metadata,
  137 + Pageable pageable);
  138 +
  139 + @Query(nativeQuery = true,
  140 + 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 " +
  141 + "(SELECT *, e.body\\:\\:jsonb as json_body FROM event e WHERE " +
  142 + "e.tenant_id = :tenantId " +
  143 + "AND e.entity_type = :entityType " +
  144 + "AND e.entity_id = :entityId " +
  145 + "AND e.event_type = 'ERROR' " +
  146 + "AND e.created_time >= :startTime AND (:endTime = 0 OR e.created_time <= :endTime) " +
  147 + ") AS e WHERE " +
  148 + "(:server IS NULL OR lower(json_body->>'server') LIKE concat('%', lower(:server\\:\\:varchar), '%')) " +
  149 + "AND (:method IS NULL OR lower(json_body->>'method') LIKE concat('%', lower(:method\\:\\:varchar), '%')) " +
  150 + "AND (:error IS NULL OR lower(json_body->>'error') LIKE concat('%', lower(:error\\:\\:varchar), '%'))",
  151 + countQuery = "SELECT count(*) FROM " +
  152 + "(SELECT *, e.body\\:\\:jsonb as json_body FROM event e WHERE " +
  153 + "e.tenant_id = :tenantId " +
  154 + "AND e.entity_type = :entityType " +
  155 + "AND e.entity_id = :entityId " +
  156 + "AND e.event_type = 'ERROR' " +
  157 + "AND e.created_time >= :startTime AND (:endTime = 0 OR e.created_time <= :endTime) " +
  158 + ") AS e WHERE " +
  159 + "(:server IS NULL OR lower(json_body->>'server') LIKE concat('%', lower(:server\\:\\:varchar), '%')) " +
  160 + "AND (:method IS NULL OR lower(json_body->>'method') LIKE concat('%', lower(:method\\:\\:varchar), '%')) " +
  161 + "AND (:error IS NULL OR lower(json_body->>'error') LIKE concat('%', lower(:error\\:\\:varchar), '%'))")
  162 + Page<EventEntity> findErrorEvents(@Param("tenantId") UUID tenantId,
  163 + @Param("entityId") UUID entityId,
  164 + @Param("entityType") String entityType,
  165 + @Param("startTime") Long startTime,
  166 + @Param("endTime") Long endTIme,
  167 + @Param("server") String server,
  168 + @Param("method") String method,
  169 + @Param("error") String error,
  170 + Pageable pageable);
  171 +
  172 + @Query(nativeQuery = true,
  173 + 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 " +
  174 + "(SELECT *, e.body\\:\\:jsonb as json_body FROM event e WHERE " +
  175 + "e.tenant_id = :tenantId " +
  176 + "AND e.entity_type = :entityType " +
  177 + "AND e.entity_id = :entityId " +
  178 + "AND e.event_type = 'LC_EVENT' " +
  179 + "AND e.created_time >= :startTime AND (:endTime = 0 OR e.created_time <= :endTime) " +
  180 + ") AS e WHERE " +
  181 + "(:server IS NULL OR lower(json_body->>'server') LIKE concat('%', lower(:server\\:\\:varchar), '%')) " +
  182 + "AND (:event IS NULL OR lower(json_body->>'event') LIKE concat('%', lower(:event\\:\\:varchar), '%')) " +
  183 + "AND ((:statusFilterEnabled = FALSE) OR lower(json_body->>'success')\\:\\:boolean = :statusFilter) " +
  184 + "AND (:error IS NULL OR lower(json_body->>'error') LIKE concat('%', lower(:error\\:\\:varchar), '%'))"
  185 + ,
  186 + countQuery = "SELECT count(*) FROM " +
  187 + "(SELECT *, e.body\\:\\:jsonb as json_body FROM event e WHERE " +
  188 + "e.tenant_id = :tenantId " +
  189 + "AND e.entity_type = :entityType " +
  190 + "AND e.entity_id = :entityId " +
  191 + "AND e.event_type = 'LC_EVENT' " +
  192 + "AND e.created_time >= :startTime AND (:endTime = 0 OR e.created_time <= :endTime) " +
  193 + ") AS e WHERE " +
  194 + "(:server IS NULL OR lower(json_body->>'server') LIKE concat('%', lower(:server\\:\\:varchar), '%')) " +
  195 + "AND (:event IS NULL OR lower(json_body->>'event') LIKE concat('%', lower(:event\\:\\:varchar), '%')) " +
  196 + "AND ((:statusFilterEnabled = FALSE) OR lower(json_body->>'success')\\:\\:boolean = :statusFilter) " +
  197 + "AND (:error IS NULL OR lower(json_body->>'error') LIKE concat('%', lower(:error\\:\\:varchar), '%'))"
  198 + )
  199 + Page<EventEntity> findLifeCycleEvents(@Param("tenantId") UUID tenantId,
  200 + @Param("entityId") UUID entityId,
  201 + @Param("entityType") String entityType,
  202 + @Param("startTime") Long startTime,
  203 + @Param("endTime") Long endTIme,
  204 + @Param("server") String server,
  205 + @Param("event") String event,
  206 + @Param("statusFilterEnabled") boolean statusFilterEnabled,
  207 + @Param("statusFilter") boolean statusFilter,
  208 + @Param("error") String error,
  209 + Pageable pageable);
  210 +
  211 + @Query(nativeQuery = true,
  212 + 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 " +
  213 + "(SELECT *, e.body\\:\\:jsonb as json_body FROM event e WHERE " +
  214 + "e.tenant_id = :tenantId " +
  215 + "AND e.entity_type = :entityType " +
  216 + "AND e.entity_id = :entityId " +
  217 + "AND e.event_type = 'STATS' " +
  218 + "AND e.created_time >= :startTime AND (:endTime = 0 OR e.created_time <= :endTime) " +
  219 + ") AS e WHERE " +
  220 + "(:server IS NULL OR lower(e.body\\:\\:json->>'server') LIKE concat('%', lower(:server\\:\\:varchar), '%')) " +
  221 + "AND (:messagesProcessed = 0 OR (json_body->>'messagesProcessed')\\:\\:integer >= :messagesProcessed) " +
  222 + "AND (:errorsOccurred = 0 OR (json_body->>'errorsOccurred')\\:\\:integer >= :errorsOccurred) ",
  223 + countQuery = "SELECT count(*) FROM " +
  224 + "(SELECT *, e.body\\:\\:jsonb as json_body FROM event e WHERE " +
  225 + "e.tenant_id = :tenantId " +
  226 + "AND e.entity_type = :entityType " +
  227 + "AND e.entity_id = :entityId " +
  228 + "AND e.event_type = 'LC_EVENT' " +
  229 + "AND e.created_time >= :startTime AND (:endTime = 0 OR e.created_time <= :endTime) " +
  230 + ") AS e WHERE " +
  231 + "(:server IS NULL OR lower(e.body\\:\\:json->>'server') LIKE concat('%', lower(:server\\:\\:varchar), '%')) " +
  232 + "AND (:messagesProcessed = 0 OR (json_body->>'messagesProcessed')\\:\\:integer >= :messagesProcessed) " +
  233 + "AND (:errorsOccurred = 0 OR (json_body->>'errorsOccurred')\\:\\:integer >= :errorsOccurred) ")
  234 + Page<EventEntity> findStatisticsEvents(@Param("tenantId") UUID tenantId,
  235 + @Param("entityId") UUID entityId,
  236 + @Param("entityType") String entityType,
  237 + @Param("startTime") Long startTime,
  238 + @Param("endTime") Long endTIme,
  239 + @Param("server") String server,
  240 + @Param("messagesProcessed") Integer messagesProcessed,
  241 + @Param("errorsOccurred") Integer errorsOccurred,
  242 + Pageable pageable);
  243 +
83 244 }
... ...
... ... @@ -24,6 +24,12 @@ import org.springframework.data.domain.PageRequest;
24 24 import org.springframework.data.repository.CrudRepository;
25 25 import org.springframework.stereotype.Component;
26 26 import org.thingsboard.server.common.data.Event;
  27 +import org.thingsboard.server.common.data.event.DebugEvent;
  28 +import org.thingsboard.server.common.data.event.ErrorEventFilter;
  29 +import org.thingsboard.server.common.data.event.EventFilter;
  30 +import org.thingsboard.server.common.data.event.EventType;
  31 +import org.thingsboard.server.common.data.event.LifeCycleEventFilter;
  32 +import org.thingsboard.server.common.data.event.StatisticsEventFilter;
27 33 import org.thingsboard.server.common.data.id.EntityId;
28 34 import org.thingsboard.server.common.data.id.EventId;
29 35 import org.thingsboard.server.common.data.id.TenantId;
... ... @@ -148,6 +154,98 @@ public class JpaBaseEventDao extends JpaAbstractDao<EventEntity, Event> implemen
148 154 }
149 155
150 156 @Override
  157 + public PageData<Event> findEventByFilter(UUID tenantId, EntityId entityId, EventFilter eventFilter, TimePageLink pageLink) {
  158 + if (eventFilter.hasFilterForJsonBody()) {
  159 + switch (eventFilter.getEventType()) {
  160 + case DEBUG_RULE_NODE:
  161 + case DEBUG_RULE_CHAIN:
  162 + return findEventByFilter(tenantId, entityId, (DebugEvent) eventFilter, pageLink);
  163 + case LC_EVENT:
  164 + return findEventByFilter(tenantId, entityId, (LifeCycleEventFilter) eventFilter, pageLink);
  165 + case ERROR:
  166 + return findEventByFilter(tenantId, entityId, (ErrorEventFilter) eventFilter, pageLink);
  167 + case STATS:
  168 + return findEventByFilter(tenantId, entityId, (StatisticsEventFilter) eventFilter, pageLink);
  169 + default:
  170 + throw new RuntimeException("Not supported event type: " + eventFilter.getEventType());
  171 + }
  172 + } else {
  173 + return findEvents(tenantId, entityId, eventFilter.getEventType().name(), pageLink);
  174 + }
  175 + }
  176 +
  177 + private PageData<Event> findEventByFilter(UUID tenantId, EntityId entityId, DebugEvent eventFilter, TimePageLink pageLink) {
  178 + return DaoUtil.toPageData(
  179 + eventRepository.findDebugRuleNodeEvents(
  180 + tenantId,
  181 + entityId.getId(),
  182 + entityId.getEntityType().name(),
  183 + eventFilter.getEventType().name(),
  184 + notNull(pageLink.getStartTime()),
  185 + notNull(pageLink.getEndTime()),
  186 + eventFilter.getMsgDirectionType(),
  187 + eventFilter.getServer(),
  188 + eventFilter.getEntityName(),
  189 + eventFilter.getRelationType(),
  190 + eventFilter.getEntityId(),
  191 + eventFilter.getMsgType(),
  192 + eventFilter.isError(),
  193 + eventFilter.getError(),
  194 + eventFilter.getDataSearch(),
  195 + eventFilter.getMetadataSearch(),
  196 + DaoUtil.toPageable(pageLink)));
  197 + }
  198 +
  199 + private PageData<Event> findEventByFilter(UUID tenantId, EntityId entityId, ErrorEventFilter eventFilter, TimePageLink pageLink) {
  200 + return DaoUtil.toPageData(
  201 + eventRepository.findErrorEvents(
  202 + tenantId,
  203 + entityId.getId(),
  204 + entityId.getEntityType().name(),
  205 + notNull(pageLink.getStartTime()),
  206 + notNull(pageLink.getEndTime()),
  207 + eventFilter.getServer(),
  208 + eventFilter.getMethod(),
  209 + eventFilter.getError(),
  210 + DaoUtil.toPageable(pageLink))
  211 + );
  212 + }
  213 +
  214 + private PageData<Event> findEventByFilter(UUID tenantId, EntityId entityId, LifeCycleEventFilter eventFilter, TimePageLink pageLink) {
  215 + boolean statusFilterEnabled = !StringUtils.isEmpty(eventFilter.getStatus());
  216 + boolean statusFilter = statusFilterEnabled && eventFilter.getStatus().equalsIgnoreCase("Success");
  217 + return DaoUtil.toPageData(
  218 + eventRepository.findLifeCycleEvents(
  219 + tenantId,
  220 + entityId.getId(),
  221 + entityId.getEntityType().name(),
  222 + notNull(pageLink.getStartTime()),
  223 + notNull(pageLink.getEndTime()),
  224 + eventFilter.getServer(),
  225 + eventFilter.getEvent(),
  226 + statusFilterEnabled,
  227 + statusFilter,
  228 + eventFilter.getError(),
  229 + DaoUtil.toPageable(pageLink))
  230 + );
  231 + }
  232 +
  233 + private PageData<Event> findEventByFilter(UUID tenantId, EntityId entityId, StatisticsEventFilter eventFilter, TimePageLink pageLink) {
  234 + return DaoUtil.toPageData(
  235 + eventRepository.findStatisticsEvents(
  236 + tenantId,
  237 + entityId.getId(),
  238 + entityId.getEntityType().name(),
  239 + notNull(pageLink.getStartTime()),
  240 + notNull(pageLink.getEndTime()),
  241 + eventFilter.getServer(),
  242 + notNull(eventFilter.getMessagesProcessed()),
  243 + notNull(eventFilter.getErrorsOccurred()),
  244 + DaoUtil.toPageable(pageLink))
  245 + );
  246 + }
  247 +
  248 + @Override
151 249 public List<Event> findLatestEvents(UUID tenantId, EntityId entityId, String eventType, int limit) {
152 250 List<EventEntity> latest = eventRepository.findLatestByTenantIdAndEntityTypeAndEntityIdAndEventType(
153 251 tenantId,
... ... @@ -177,4 +275,12 @@ public class JpaBaseEventDao extends JpaAbstractDao<EventEntity, Event> implemen
177 275 return Optional.of(DaoUtil.getData(eventInsertRepository.saveOrUpdate(entity)));
178 276 }
179 277
  278 + private long notNull(Long value) {
  279 + return value != null ? value : 0;
  280 + }
  281 +
  282 + private int notNull(Integer value) {
  283 + return value != null ? value : 0;
  284 + }
  285 +
180 286 }
... ...
... ... @@ -21,7 +21,7 @@ import { HttpClient } from '@angular/common/http';
21 21 import { TimePageLink } from '@shared/models/page/page-link';
22 22 import { PageData } from '@shared/models/page/page-data';
23 23 import { EntityId } from '@shared/models/id/entity-id';
24   -import { DebugEventType, Event, EventType } from '@shared/models/event.models';
  24 +import { DebugEventType, Event, EventType, FilterEventBody } from '@shared/models/event.models';
25 25
26 26 @Injectable({
27 27 providedIn: 'root'
... ... @@ -39,4 +39,10 @@ export class EventService {
39 39 defaultHttpOptionsFromConfig(config));
40 40 }
41 41
  42 + public getFilterEvents(entityId: EntityId, eventType: EventType | DebugEventType, tenantId: string,
  43 + filters: FilterEventBody, pageLink: TimePageLink, config?: RequestConfig): Observable<PageData<Event>> {
  44 + return this.http.post<PageData<Event>>(`/api/events/${entityId.entityType}/${entityId.id}` +
  45 + `${pageLink.toQuery()}&tenantId=${tenantId}`, {...filters, eventType}, defaultHttpOptionsFromConfig(config));
  46 + }
  47 +
42 48 }
... ...
... ... @@ -43,14 +43,17 @@ export class ResourceService {
43 43 }
44 44
45 45 public downloadResource(resourceId: string): Observable<any> {
46   - return this.http.get(`/api/resource/${resourceId}/download`, { responseType: 'arraybuffer', observe: 'response' }).pipe(
  46 + return this.http.get(`/api/resource/${resourceId}/download`, {
  47 + responseType: 'arraybuffer',
  48 + observe: 'response'
  49 + }).pipe(
47 50 map((response) => {
48 51 const headers = response.headers;
49 52 const filename = headers.get('x-filename');
50 53 const contentType = headers.get('content-type');
51 54 const linkElement = document.createElement('a');
52 55 try {
53   - const blob = new Blob([response.body], { type: contentType });
  56 + const blob = new Blob([response.body], {type: contentType});
54 57 const url = URL.createObjectURL(blob);
55 58 linkElement.setAttribute('href', url);
56 59 linkElement.setAttribute('download', filename);
... ...
... ... @@ -31,7 +31,7 @@ import { isDefined } from '@core/utils';
31 31
32 32 export interface TimeInterval {
33 33 name: string;
34   - translateParams: {[key: string]: any};
  34 + translateParams: { [key: string]: any };
35 35 value: number;
36 36 }
37 37
... ... @@ -56,14 +56,14 @@ export class TimeService {
56 56 public loadMaxDatapointsLimit(): Observable<number> {
57 57 return this.http.get<number>('/api/dashboard/maxDatapointsLimit',
58 58 defaultHttpOptions(true)).pipe(
59   - map( (limit) => {
60   - this.maxDatapointsLimit = limit;
61   - if (!this.maxDatapointsLimit || this.maxDatapointsLimit <= MIN_LIMIT) {
62   - this.maxDatapointsLimit = MIN_LIMIT + 1;
63   - }
64   - return this.maxDatapointsLimit;
65   - })
66   - );
  59 + map((limit) => {
  60 + this.maxDatapointsLimit = limit;
  61 + if (!this.maxDatapointsLimit || this.maxDatapointsLimit <= MIN_LIMIT) {
  62 + this.maxDatapointsLimit = MIN_LIMIT + 1;
  63 + }
  64 + return this.maxDatapointsLimit;
  65 + })
  66 + );
67 67 }
68 68
69 69 public matchesExistingInterval(min: number, max: number, intervalMs: number): boolean {
... ...
... ... @@ -291,7 +291,7 @@ export function deepClone<T>(target: T, ignoreFields?: string[]): T {
291 291 return cp.map((n: any) => deepClone<any>(n)) as any;
292 292 }
293 293 if (typeof target === 'object' && target !== {}) {
294   - const cp = { ...(target as { [key: string]: any }) } as { [key: string]: any };
  294 + const cp = {...(target as { [key: string]: any })} as { [key: string]: any };
295 295 Object.keys(cp).forEach(k => {
296 296 if (!ignoreFields || ignoreFields.indexOf(k) === -1) {
297 297 cp[k] = deepClone<any>(cp[k]);
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2021 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<form fxLayout="column" class="mat-content mat-padding" [formGroup]="eventFilterFormGroup" (ngSubmit)="update()">
  19 + <ng-container *ngFor="let column of showColumns">
  20 + <ng-container [ngSwitch]="column.key">
  21 + <ng-template [ngSwitchCase]="isSelector(column.key)">
  22 + <mat-form-field>
  23 + <mat-label>{{ column.title | translate}}</mat-label>
  24 + <mat-select [formControlName]="column.key">
  25 + <mat-option [value]="">{{ 'event.all-events' | translate}}</mat-option>
  26 + <mat-option *ngFor="let value of selectorValues(column.key)" [value]="value">
  27 + {{ value }}
  28 + </mat-option>
  29 + </mat-select>
  30 + </mat-form-field>
  31 + </ng-template>
  32 + <ng-template [ngSwitchCase]="'isError'">
  33 + <tb-checkbox formControlName="isError" [falseValue]="''">
  34 + {{ 'event.has-error' | translate }}
  35 + </tb-checkbox>
  36 + </ng-template>
  37 + <ng-template [ngSwitchCase]="'error'">
  38 + <mat-form-field fxHide [fxShow]="showErrorMsgFields()">
  39 + <mat-label>{{ column.title | translate}}</mat-label>
  40 + <input matInput type="text" name="errorSearchText" formControlName="error">
  41 + </mat-form-field>
  42 + </ng-template>
  43 + <ng-container *ngSwitchDefault>
  44 + <mat-form-field>
  45 + <mat-label>{{ column.title | translate}}</mat-label>
  46 + <input matInput type="text" [name]="column.key" [formControlName]="column.key">
  47 + </mat-form-field>
  48 + </ng-container>
  49 + </ng-container>
  50 + </ng-container>
  51 + <div fxLayout="row" class="tb-panel-actions" fxLayoutAlign="end center">
  52 + <button type="button"
  53 + mat-button
  54 + (click)="cancel()">
  55 + {{ 'action.cancel' | translate }}
  56 + </button>
  57 + <button type="submit"
  58 + mat-raised-button
  59 + color="primary"
  60 + [disabled]="eventFilterFormGroup.invalid || !eventFilterFormGroup.dirty">
  61 + {{ 'action.update' | translate }}
  62 + </button>
  63 + </div>
  64 +</form>
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +:host {
  17 + width: 100%;
  18 + min-width: 300px;
  19 + overflow: auto;
  20 + background: #fff;
  21 + border-radius: 4px;
  22 + box-shadow:
  23 + 0 7px 8px -4px rgba(0, 0, 0, .2),
  24 + 0 13px 19px 2px rgba(0, 0, 0, .14),
  25 + 0 5px 24px 4px rgba(0, 0, 0, .12);
  26 +
  27 + .mat-content {
  28 + overflow: hidden;
  29 + background-color: #fff;
  30 + }
  31 +
  32 + .mat-padding {
  33 + padding: 16px;
  34 + }
  35 +}
... ...
  1 +///
  2 +/// Copyright © 2016-2021 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { Component, Inject, InjectionToken } from '@angular/core';
  18 +import { FormBuilder, FormGroup } from '@angular/forms';
  19 +import { OverlayRef } from '@angular/cdk/overlay';
  20 +import { EntityType } from '@shared/models/entity-type.models';
  21 +import { FilterEventBody } from '@shared/models/event.models';
  22 +import { deepTrim } from '@core/utils';
  23 +
  24 +export const EVENT_FILTER_PANEL_DATA = new InjectionToken<any>('AlarmFilterPanelData');
  25 +
  26 +export interface EventFilterPanelData {
  27 + filterParams: FilterEventBody;
  28 + columns: Array<FilterEntityColumn>;
  29 +}
  30 +
  31 +export interface FilterEntityColumn {
  32 + key: string;
  33 + title: string;
  34 +}
  35 +
  36 +
  37 +@Component({
  38 + selector: 'tb-event-filter-panel',
  39 + templateUrl: './event-filter-panel.component.html',
  40 + styleUrls: ['./event-filter-panel.component.scss']
  41 +})
  42 +export class EventFilterPanelComponent {
  43 +
  44 + eventFilterFormGroup: FormGroup;
  45 + result: EventFilterPanelData;
  46 +
  47 + private conditionError = false;
  48 +
  49 + private msgDirectionTypes = ['IN', 'OUT'];
  50 + private statusTypes = ['Success', 'Failure'];
  51 + private entityTypes = Object.keys(EntityType);
  52 +
  53 + showColumns: FilterEntityColumn[] = [];
  54 +
  55 + constructor(@Inject(EVENT_FILTER_PANEL_DATA)
  56 + public data: EventFilterPanelData,
  57 + public overlayRef: OverlayRef,
  58 + private fb: FormBuilder) {
  59 + this.eventFilterFormGroup = this.fb.group({});
  60 + this.data.columns.forEach((column) => {
  61 + this.showColumns.push(column);
  62 + this.eventFilterFormGroup.addControl(column.key, this.fb.control(this.data.filterParams[column.key] || ''));
  63 + if (column.key === 'isError') {
  64 + this.conditionError = true;
  65 + }
  66 + });
  67 + }
  68 +
  69 + isSelector(key: string): string {
  70 + return ['msgDirectionType', 'status', 'entityName'].includes(key) ? key : '';
  71 + }
  72 +
  73 + selectorValues(key: string): string[] {
  74 + switch (key) {
  75 + case 'msgDirectionType':
  76 + return this.msgDirectionTypes;
  77 + case 'status':
  78 + return this.statusTypes;
  79 + case 'entityName':
  80 + return this.entityTypes;
  81 + }
  82 + }
  83 +
  84 + update() {
  85 + const filter = deepTrim(Object.fromEntries(Object.entries(this.eventFilterFormGroup.value).filter(([_, v]) => v !== '')));
  86 + this.result = {
  87 + filterParams: filter,
  88 + columns: this.data.columns
  89 + };
  90 + this.overlayRef.dispose();
  91 + }
  92 +
  93 + showErrorMsgFields() {
  94 + return !this.conditionError || this.eventFilterFormGroup.get('isError').value !== '';
  95 + }
  96 +
  97 + cancel() {
  98 + this.overlayRef.dispose();
  99 + }
  100 +}
  101 +
... ...
... ... @@ -20,7 +20,7 @@ import {
20 20 EntityTableColumn,
21 21 EntityTableConfig
22 22 } from '@home/models/entity/entities-table-config.models';
23   -import { DebugEventType, Event, EventType } from '@shared/models/event.models';
  23 +import { DebugEventType, Event, EventType, FilterEventBody } from '@shared/models/event.models';
24 24 import { TimePageLink } from '@shared/models/page/page-link';
25 25 import { TranslateService } from '@ngx-translate/core';
26 26 import { DatePipe } from '@angular/common';
... ... @@ -38,16 +38,29 @@ import {
38 38 EventContentDialogComponent,
39 39 EventContentDialogData
40 40 } from '@home/components/event/event-content-dialog.component';
41   -import { sortObjectKeys } from '@core/utils';
  41 +import { isEqual, sortObjectKeys } from '@core/utils';
  42 +import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
  43 +import { ChangeDetectorRef, Injector, StaticProvider, ViewContainerRef } from '@angular/core';
  44 +import { ComponentPortal } from '@angular/cdk/portal';
  45 +import {
  46 + EVENT_FILTER_PANEL_DATA,
  47 + EventFilterPanelComponent,
  48 + EventFilterPanelData,
  49 + FilterEntityColumn
  50 +} from '@home/components/event/event-filter-panel.component';
42 51
43 52 export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> {
44 53
45 54 eventTypeValue: EventType | DebugEventType;
46 55
  56 + private filterParams: FilterEventBody = {};
  57 + private filterColumns: FilterEntityColumn[] = [];
  58 +
47 59 set eventType(eventType: EventType | DebugEventType) {
48 60 if (this.eventTypeValue !== eventType) {
49 61 this.eventTypeValue = eventType;
50 62 this.updateColumns(true);
  63 + this.updateFilterColumns();
51 64 }
52 65 }
53 66
... ... @@ -66,7 +79,10 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> {
66 79 public tenantId: string,
67 80 private defaultEventType: EventType | DebugEventType,
68 81 private disabledEventTypes: Array<EventType | DebugEventType> = null,
69   - private debugEventTypes: Array<DebugEventType> = null) {
  82 + private debugEventTypes: Array<DebugEventType> = null,
  83 + private overlay: Overlay,
  84 + private viewContainerRef: ViewContainerRef,
  85 + private cd: ChangeDetectorRef) {
70 86 super();
71 87 this.loadDataOnInit = false;
72 88 this.tableTitle = '';
... ... @@ -101,10 +117,20 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> {
101 117 this.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC};
102 118
103 119 this.updateColumns();
  120 + this.updateFilterColumns();
  121 +
  122 + this.headerActionDescriptors.push({
  123 + name: this.translate.instant('event.events-filter'),
  124 + icon: 'filter_list',
  125 + isEnabled: () => true,
  126 + onAction: ($event) => {
  127 + this.editEventFilter($event);
  128 + }
  129 + });
104 130 }
105 131
106 132 fetchEvents(pageLink: TimePageLink): Observable<PageData<Event>> {
107   - return this.eventService.getEvents(this.entityId, this.eventType, this.tenantId, pageLink);
  133 + return this.eventService.getFilterEvents(this.entityId, this.eventType, this.tenantId, this.filterParams, pageLink);
108 134 }
109 135
110 136 updateColumns(updateTableColumns: boolean = false): void {
... ... @@ -169,7 +195,7 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> {
169 195 }), false, key => ({
170 196 padding: '0 12px 0 0'
171 197 })),
172   - new EntityTableColumn<Event>('entity', 'event.entity', '100px',
  198 + new EntityTableColumn<Event>('entityName', 'event.entity-type', '100px',
173 199 (entity) => entity.body.entityName, entity => ({
174 200 padding: '0 12px 0 0',
175 201 }), false, key => ({
... ... @@ -249,5 +275,91 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> {
249 275 }
250 276 });
251 277 }
  278 +
  279 + private updateFilterColumns() {
  280 + this.filterParams = {};
  281 + this.filterColumns = [{key: 'server', title: 'event.server'}];
  282 + switch (this.eventType) {
  283 + case EventType.ERROR:
  284 + this.filterColumns.push(
  285 + {key: 'method', title: 'event.method'},
  286 + {key: 'error', title: 'event.error'}
  287 + );
  288 + break;
  289 + case EventType.LC_EVENT:
  290 + this.filterColumns.push(
  291 + {key: 'method', title: 'event.event'},
  292 + {key: 'status', title: 'event.status'},
  293 + {key: 'error', title: 'event.error'}
  294 + );
  295 + break;
  296 + case EventType.STATS:
  297 + this.filterColumns.push(
  298 + {key: 'messagesProcessed', title: 'event.messages-processed'},
  299 + {key: 'errorsOccurred', title: 'event.errors-occurred'}
  300 + );
  301 + break;
  302 + case DebugEventType.DEBUG_RULE_NODE:
  303 + case DebugEventType.DEBUG_RULE_CHAIN:
  304 + this.filterColumns.push(
  305 + {key: 'msgDirectionType', title: 'event.type'},
  306 + {key: 'entityId', title: 'event.entity-id'},
  307 + {key: 'entityName', title: 'event.entity-type'},
  308 + {key: 'msgType', title: 'event.message-type'},
  309 + {key: 'relationType', title: 'event.relation-type'},
  310 + {key: 'dataSearch', title: 'event.data'},
  311 + {key: 'metadataSearch', title: 'event.metadata'},
  312 + {key: 'isError', title: 'event.error'},
  313 + {key: 'error', title: 'event.error'}
  314 + );
  315 + break;
  316 + }
  317 + }
  318 +
  319 + private editEventFilter($event: MouseEvent) {
  320 + if ($event) {
  321 + $event.stopPropagation();
  322 + }
  323 + const target = $event.target || $event.srcElement || $event.currentTarget;
  324 + const config = new OverlayConfig();
  325 + config.backdropClass = 'cdk-overlay-transparent-backdrop';
  326 + config.hasBackdrop = true;
  327 + const connectedPosition: ConnectedPosition = {
  328 + originX: 'end',
  329 + originY: 'bottom',
  330 + overlayX: 'end',
  331 + overlayY: 'top'
  332 + };
  333 + config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement)
  334 + .withPositions([connectedPosition]);
  335 +
  336 + const overlayRef = this.overlay.create(config);
  337 + overlayRef.backdropClick().subscribe(() => {
  338 + overlayRef.dispose();
  339 + });
  340 + const providers: StaticProvider[] = [
  341 + {
  342 + provide: EVENT_FILTER_PANEL_DATA,
  343 + useValue: {
  344 + columns: this.filterColumns,
  345 + filterParams: this.filterParams
  346 + } as EventFilterPanelData
  347 + },
  348 + {
  349 + provide: OverlayRef,
  350 + useValue: overlayRef
  351 + }
  352 + ];
  353 + const injector = Injector.create({parent: this.viewContainerRef.injector, providers});
  354 + const componentRef = overlayRef.attach(new ComponentPortal(EventFilterPanelComponent,
  355 + this.viewContainerRef, injector));
  356 + componentRef.onDestroy(() => {
  357 + if (componentRef.instance.result && !isEqual(this.filterParams, componentRef.instance.result.filterParams)) {
  358 + this.filterParams = componentRef.instance.result.filterParams;
  359 + this.table.updateData();
  360 + }
  361 + });
  362 + this.cd.detectChanges();
  363 + }
252 364 }
253 365
... ...
... ... @@ -14,7 +14,7 @@
14 14 /// limitations under the License.
15 15 ///
16 16
17   -import { Component, Input, OnInit, ViewChild } from '@angular/core';
  17 +import { ChangeDetectorRef, Component, Input, OnInit, ViewChild, ViewContainerRef } from '@angular/core';
18 18 import { TranslateService } from '@ngx-translate/core';
19 19 import { DatePipe } from '@angular/common';
20 20 import { MatDialog } from '@angular/material/dialog';
... ... @@ -24,6 +24,7 @@ import { EventTableConfig } from './event-table-config';
24 24 import { EventService } from '@core/http/event.service';
25 25 import { DialogService } from '@core/services/dialog.service';
26 26 import { DebugEventType, EventType } from '@shared/models/event.models';
  27 +import { Overlay } from '@angular/cdk/overlay';
27 28
28 29 @Component({
29 30 selector: 'tb-event-table',
... ... @@ -80,7 +81,10 @@ export class EventTableComponent implements OnInit {
80 81 private dialogService: DialogService,
81 82 private translate: TranslateService,
82 83 private datePipe: DatePipe,
83   - private dialog: MatDialog) {
  84 + private dialog: MatDialog,
  85 + private overlay: Overlay,
  86 + private viewContainerRef: ViewContainerRef,
  87 + private cd: ChangeDetectorRef) {
84 88 }
85 89
86 90 ngOnInit() {
... ... @@ -95,7 +99,10 @@ export class EventTableComponent implements OnInit {
95 99 this.tenantId,
96 100 this.defaultEventType,
97 101 this.disabledEventTypes,
98   - this.debugEventTypes
  102 + this.debugEventTypes,
  103 + this.overlay,
  104 + this.viewContainerRef,
  105 + this.cd
99 106 );
100 107 }
101 108
... ...
... ... @@ -25,6 +25,7 @@ import { AuditLogDetailsDialogComponent } from '@home/components/audit-log/audit
25 25 import { AuditLogTableComponent } from '@home/components/audit-log/audit-log-table.component';
26 26 import { EventTableHeaderComponent } from '@home/components/event/event-table-header.component';
27 27 import { EventTableComponent } from '@home/components/event/event-table.component';
  28 +import { EventFilterPanelComponent } from '@home/components/event/event-filter-panel.component';
28 29 import { RelationTableComponent } from '@home/components/relation/relation-table.component';
29 30 import { RelationDialogComponent } from '@home/components/relation/relation-dialog.component';
30 31 import { AlarmTableHeaderComponent } from '@home/components/alarm/alarm-table-header.component';
... ... @@ -149,6 +150,7 @@ import { DisplayWidgetTypesPanelComponent } from '@home/components/dashboard-pag
149 150 EventContentDialogComponent,
150 151 EventTableHeaderComponent,
151 152 EventTableComponent,
  153 + EventFilterPanelComponent,
152 154 EdgeDownlinkTableHeaderComponent,
153 155 EdgeDownlinkTableComponent,
154 156 RelationTableComponent,
... ...
... ... @@ -14,12 +14,12 @@
14 14 /// limitations under the License.
15 15 ///
16 16
17   -import {DeviceProfileTransportConfiguration, DeviceTransportType} from '@shared/models/device.models';
18   -import {Component, forwardRef, Inject, Input} from '@angular/core';
19   -import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators} from '@angular/forms';
20   -import {Store} from '@ngrx/store';
21   -import {AppState} from '@app/core/core.state';
22   -import {coerceBooleanProperty} from '@angular/cdk/coercion';
  17 +import { DeviceProfileTransportConfiguration } from '@shared/models/device.models';
  18 +import { Component, forwardRef, Inject, Input } from '@angular/core';
  19 +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
  20 +import { Store } from '@ngrx/store';
  21 +import { AppState } from '@app/core/core.state';
  22 +import { coerceBooleanProperty } from '@angular/cdk/coercion';
23 23 import {
24 24 ATTRIBUTE,
25 25 DEFAULT_BINDING,
... ... @@ -34,11 +34,11 @@ import {
34 34 RESOURCES,
35 35 TELEMETRY
36 36 } from './lwm2m-profile-config.models';
37   -import {DeviceProfileService} from '@core/http/device-profile.service';
38   -import {deepClone, isDefinedAndNotNull, isEmpty, isUndefined} from '@core/utils';
39   -import {WINDOW} from '@core/services/window.service';
40   -import {JsonArray, JsonObject} from '@angular/compiler-cli/ngcc/src/packages/entry_point';
41   -import {Direction} from '@shared/models/page/sort-order';
  37 +import { DeviceProfileService } from '@core/http/device-profile.service';
  38 +import { deepClone, isDefinedAndNotNull, isEmpty, isUndefined } from '@core/utils';
  39 +import { WINDOW } from '@core/services/window.service';
  40 +import { JsonArray, JsonObject } from '@angular/compiler-cli/ngcc/src/packages/entry_point';
  41 +import { Direction } from '@shared/models/page/sort-order';
42 42
43 43 @Component({
44 44 selector: 'tb-profile-lwm2m-device-transport-configuration',
... ... @@ -173,7 +173,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
173 173 }
174 174
175 175 private updateObserveAttrTelemetryObjectFormGroup = (objectsList: ObjectLwM2M[]): void => {
176   - this.lwm2mDeviceProfileFormGroup.patchValue({
  176 + this.lwm2mDeviceProfileFormGroup.patchValue({
177 177 observeAttrTelemetry: deepClone(this.getObserveAttrTelemetryObjects(objectsList))
178 178 },
179 179 {emitEvent: false});
... ... @@ -358,6 +358,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
358 358 this.configurationValue.observeAttr.observe = observeArray;
359 359 this.configurationValue.observeAttr.attribute = attributeArray;
360 360 this.configurationValue.observeAttr.telemetry = telemetryArray;
  361 + this.configurationValue.observeAttr.keyName = this.sortObjectKeyPathJson(KEY_NAME, keyNameNew);
361 362 this.configurationValue.observeAttr.attributeLwm2m = attributeLwm2m;
362 363 }
363 364 }
... ...
... ... @@ -47,6 +47,7 @@
47 47 <ng-template matExpansionPanelContent>
48 48 <div fxLayout="column" fxLayoutGap="8px" formArrayName="instances">
49 49 <mat-expansion-panel
  50 + class="instance-list"
50 51 *ngFor="let instances of instancesLwm2mFormArray(objectLwM2M).controls; let y = index;"
51 52 [formGroupName]="y"
52 53 [expanded]="getExpended(objectLwM2M)"
... ... @@ -96,7 +97,7 @@
96 97 </div>
97 98 <div fxFlex="10">
98 99 </div>
99   - <div fxFlex="37" class="resource-name-lw-end" fxFlexOffset="5" disabled="false">
  100 + <div fxFlex="37" class="resource-name-lw-end" fxFlexOffset="5">
100 101 <tb-profile-lwm2m-attributes
101 102 formControlName="attributeLwm2m"
102 103 [attributeLwm2m]="instances.get('attributeLwm2m').value"
... ...
... ... @@ -22,3 +22,11 @@
22 22 padding-left: 22px;
23 23 text-align:center;
24 24 }
  25 +
  26 +:host{
  27 + .instance-list {
  28 + mat-expansion-panel-header {
  29 + color: inherit;
  30 + }
  31 + }
  32 +}
... ...
... ... @@ -146,7 +146,7 @@ export class JsonInputWidgetComponent extends PageComponent implements OnInit {
146 146 this.attributeUpdateFormGroup = this.fb.group({
147 147 currentValue: [{}, validators]
148 148 });
149   - this.attributeUpdateFormGroup.valueChanges.subscribe( () => {
  149 + this.attributeUpdateFormGroup.valueChanges.subscribe(() => {
150 150 this.ctx.detectChanges();
151 151 });
152 152 }
... ... @@ -191,14 +191,14 @@ export class JsonInputWidgetComponent extends PageComponent implements OnInit {
191 191 saveAttributeObservable = this.attributeService.saveEntityAttributes(
192 192 entityId,
193 193 this.settings.attributeScope,
194   - [ attributeToSave ],
  194 + [attributeToSave],
195 195 {}
196 196 );
197 197 } else {
198 198 saveAttributeObservable = this.attributeService.saveEntityTimeseries(
199 199 entityId,
200 200 LatestTelemetry.LATEST_TELEMETRY,
201   - [ attributeToSave ],
  201 + [attributeToSave],
202 202 {}
203 203 );
204 204 }
... ...
... ... @@ -87,4 +87,5 @@ import { JsonInputWidgetComponent } from '@home/components/widget/lib/json-input
87 87 ImportExportService
88 88 ]
89 89 })
90   -export class WidgetComponentsModule { }
  90 +export class WidgetComponentsModule {
  91 +}
... ...
... ... @@ -89,7 +89,7 @@ export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<Asse
89 89 this.config.entityTranslations = entityTypeTranslations.get(EntityType.ASSET);
90 90 this.config.entityResources = entityTypeResources.get(EntityType.ASSET);
91 91
92   - this.config.deleteEntityTitle = asset => this.translate.instant('asset.delete-asset-title', { assetName: asset.name });
  92 + this.config.deleteEntityTitle = asset => this.translate.instant('asset.delete-asset-title', {assetName: asset.name});
93 93 this.config.deleteEntityContent = () => this.translate.instant('asset.delete-asset-text');
94 94 this.config.deleteEntitiesTitle = count => this.translate.instant('asset.delete-assets-title', {count});
95 95 this.config.deleteEntitiesContent = () => this.translate.instant('asset.delete-assets-text');
... ...
... ... @@ -93,7 +93,7 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
93 93 this.config.entityResources = entityTypeResources.get(EntityType.DASHBOARD);
94 94
95 95 this.config.deleteEntityTitle = dashboard =>
96   - this.translate.instant('dashboard.delete-dashboard-title', { dashboardTitle: dashboard.title });
  96 + this.translate.instant('dashboard.delete-dashboard-title', {dashboardTitle: dashboard.title});
97 97 this.config.deleteEntityContent = () => this.translate.instant('dashboard.delete-dashboard-text');
98 98 this.config.deleteEntitiesTitle = count => this.translate.instant('dashboard.delete-dashboards-title', {count});
99 99 this.config.deleteEntitiesContent = () => this.translate.instant('dashboard.delete-dashboards-text');
... ... @@ -363,8 +363,7 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
363 363 this.router.navigateByUrl(`customers/${this.config.componentsData.customerId}/dashboards/${dashboard.id.id}`);
364 364 } else if (this.config.componentsData.dashboardScope === 'edge') {
365 365 this.router.navigateByUrl(`edges/${this.config.componentsData.edgeId}/dashboards/${dashboard.id.id}`);
366   - }
367   - else {
  366 + } else {
368 367 this.router.navigateByUrl(`dashboards/${dashboard.id.id}`);
369 368 }
370 369 }
... ... @@ -421,7 +420,7 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
421 420 }
422 421 }).afterClosed()
423 422 .subscribe(() => {
424   - this.config.table.updateData();
  423 + this.config.table.updateData();
425 424 });
426 425 }
427 426 );
... ...
... ... @@ -97,7 +97,7 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
97 97
98 98 this.config.addDialogStyle = {width: '600px'};
99 99
100   - this.config.deleteEntityTitle = device => this.translate.instant('device.delete-device-title', { deviceName: device.name });
  100 + this.config.deleteEntityTitle = device => this.translate.instant('device.delete-device-title', {deviceName: device.name});
101 101 this.config.deleteEntityContent = () => this.translate.instant('device.delete-device-text');
102 102 this.config.deleteEntitiesTitle = count => this.translate.instant('device.delete-devices-title', {count});
103 103 this.config.deleteEntitiesContent = () => this.translate.instant('device.delete-devices-text');
... ... @@ -109,7 +109,7 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
109 109 this.broadcast.broadcast('deviceSaved');
110 110 }),
111 111 mergeMap((savedDevice) => this.deviceService.getDeviceInfo(savedDevice.id.id)
112   - ));
  112 + ));
113 113 };
114 114 this.config.onEntityAction = action => this.onDeviceAction(action);
115 115 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<EntityTableConfig<Dev
208 208 this.config.entitiesFetchFunction = pageLink =>
209 209 this.deviceService.getCustomerDeviceInfosByDeviceProfileId(this.customerId, pageLink,
210 210 this.config.componentsData.deviceProfileId !== null ?
211   - this.config.componentsData.deviceProfileId.id : '');
  211 + this.config.componentsData.deviceProfileId.id : '');
212 212 this.config.deleteEntity = id => this.deviceService.unassignDeviceFromCustomer(id.id);
213 213 }
214 214 }
... ... @@ -250,26 +250,26 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
250 250 );
251 251 }
252 252 if (deviceScope === 'customer') {
253   - actions.push(
254   - {
255   - name: this.translate.instant('device.unassign-from-customer'),
256   - icon: 'assignment_return',
257   - isEnabled: (entity) => (entity.customerId && entity.customerId.id !== NULL_UUID && !entity.customerIsPublic),
258   - onAction: ($event, entity) => this.unassignFromCustomer($event, entity)
259   - },
260   - {
261   - name: this.translate.instant('device.make-private'),
262   - icon: 'reply',
263   - isEnabled: (entity) => (entity.customerId && entity.customerId.id !== NULL_UUID && entity.customerIsPublic),
264   - onAction: ($event, entity) => this.unassignFromCustomer($event, entity)
265   - },
266   - {
267   - name: this.translate.instant('device.manage-credentials'),
268   - icon: 'security',
269   - isEnabled: () => true,
270   - onAction: ($event, entity) => this.manageCredentials($event, entity)
271   - }
272   - );
  253 + actions.push(
  254 + {
  255 + name: this.translate.instant('device.unassign-from-customer'),
  256 + icon: 'assignment_return',
  257 + isEnabled: (entity) => (entity.customerId && entity.customerId.id !== NULL_UUID && !entity.customerIsPublic),
  258 + onAction: ($event, entity) => this.unassignFromCustomer($event, entity)
  259 + },
  260 + {
  261 + name: this.translate.instant('device.make-private'),
  262 + icon: 'reply',
  263 + isEnabled: (entity) => (entity.customerId && entity.customerId.id !== NULL_UUID && entity.customerIsPublic),
  264 + onAction: ($event, entity) => this.unassignFromCustomer($event, entity)
  265 + },
  266 + {
  267 + name: this.translate.instant('device.manage-credentials'),
  268 + icon: 'security',
  269 + isEnabled: () => true,
  270 + onAction: ($event, entity) => this.manageCredentials($event, entity)
  271 + }
  272 + );
273 273 }
274 274 if (deviceScope === 'customer_user' || deviceScope === 'edge_customer_user') {
275 275 actions.push(
... ... @@ -452,10 +452,10 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
452 452 }
453 453 }).afterClosed()
454 454 .subscribe((res) => {
455   - if (res) {
456   - this.config.table.updateData();
457   - }
458   - });
  455 + if (res) {
  456 + this.config.table.updateData();
  457 + }
  458 + });
459 459 }
460 460
461 461 unassignFromCustomer($event: Event, device: DeviceInfo) {
... ...
... ... @@ -33,8 +33,11 @@ import { RuleChainType } from '@shared/models/rule-chain.models';
33 33 import {
34 34 importRuleChainBreadcumbLabelFunction,
35 35 ResolvedRuleChainMetaDataResolver,
36   - ruleChainBreadcumbLabelFunction, RuleChainImportGuard,
37   - RuleChainResolver, RuleNodeComponentsResolver, TooltipsterResolver
  36 + ruleChainBreadcumbLabelFunction,
  37 + RuleChainImportGuard,
  38 + RuleChainResolver,
  39 + RuleNodeComponentsResolver,
  40 + TooltipsterResolver
38 41 } from '@home/pages/rulechain/rulechain-routing.module';
39 42
40 43 const routes: Routes = [
... ... @@ -249,7 +252,7 @@ const routes: Routes = [
249 252 }
250 253 ]
251 254 }
252   - ]
  255 + ]
253 256 }];
254 257
255 258 @NgModule({
... ... @@ -259,4 +262,5 @@ const routes: Routes = [
259 262 EdgesTableConfigResolver
260 263 ]
261 264 })
262   -export class EdgeRoutingModule { }
  265 +export class EdgeRoutingModule {
  266 +}
... ...
... ... @@ -89,7 +89,7 @@ export class EntityViewsTableConfigResolver implements Resolve<EntityTableConfig
89 89 this.config.addDialogStyle = {maxWidth: '800px'};
90 90
91 91 this.config.deleteEntityTitle = entityView =>
92   - this.translate.instant('entity-view.delete-entity-view-title', { entityViewName: entityView.name });
  92 + this.translate.instant('entity-view.delete-entity-view-title', {entityViewName: entityView.name});
93 93 this.config.deleteEntityContent = () => this.translate.instant('entity-view.delete-entity-view-text');
94 94 this.config.deleteEntitiesTitle = count => this.translate.instant('entity-view.delete-entity-views-title', {count});
95 95 this.config.deleteEntitiesContent = () => this.translate.instant('entity-view.delete-entity-views-text');
... ... @@ -143,8 +143,7 @@ export class EntityViewsTableConfigResolver implements Resolve<EntityTableConfig
143 143 this.edgeService.getEdge(this.config.componentsData.edgeId).subscribe(
144 144 edge => this.config.tableTitle = edge.name + ': ' + this.translate.instant('entity-view.entity-views')
145 145 );
146   - }
147   - else {
  146 + } else {
148 147 this.config.tableTitle = this.translate.instant('entity-view.entity-views');
149 148 }
150 149 this.config.columns = this.configureColumns(this.config.componentsData.entityViewScope);
... ...
... ... @@ -90,8 +90,10 @@ export class ResourcesLibraryComponent extends EntityComponent<Resource> impleme
90 90 buildForm(entity: Resource): FormGroup {
91 91 return this.fb.group(
92 92 {
93   - resourceType: [{value: entity?.resourceType ? entity.resourceType : ResourceType.LWM2M_MODEL,
94   - disabled: this.isEdit }, [Validators.required]],
  93 + resourceType: [{
  94 + value: entity?.resourceType ? entity.resourceType : ResourceType.LWM2M_MODEL,
  95 + disabled: this.isEdit
  96 + }, [Validators.required]],
95 97 data: [entity ? entity.data : null, [Validators.required]],
96 98 fileName: [entity ? entity.fileName : null, [Validators.required]],
97 99 title: [entity ? entity.title : '', []]
... ...
... ... @@ -73,7 +73,7 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
73 73 this.config.entityResources = entityTypeResources.get(EntityType.RULE_CHAIN);
74 74
75 75 this.config.deleteEntityTitle = ruleChain => this.translate.instant('rulechain.delete-rulechain-title',
76   - { ruleChainName: ruleChain.name });
  76 + {ruleChainName: ruleChain.name});
77 77 this.config.deleteEntityContent = () => this.translate.instant('rulechain.delete-rulechain-text');
78 78 this.config.deleteEntitiesTitle = count => this.translate.instant('rulechain.delete-rulechains-title', {count});
79 79 this.config.deleteEntitiesContent = () => this.translate.instant('rulechain.delete-rulechains-text');
... ... @@ -121,11 +121,11 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
121 121 columns.push(
122 122 new EntityTableColumn<RuleChain>('root', 'rulechain.root', '60px',
123 123 entity => {
124   - if (ruleChainScope === 'edge') {
125   - return checkBoxCell((this.config.componentsData.edge.rootRuleChainId.id === entity.id.id));
126   - } else {
127   - return checkBoxCell(entity.root);
128   - }
  124 + if (ruleChainScope === 'edge') {
  125 + return checkBoxCell((this.config.componentsData.edge.rootRuleChainId.id === entity.id.id));
  126 + } else {
  127 + return checkBoxCell(entity.root);
  128 + }
129 129 })
130 130 );
131 131 } else if (ruleChainScope === 'edges') {
... ... @@ -421,32 +421,32 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
421 421 }
422 422 }).afterClosed()
423 423 .subscribe((res) => {
424   - if (res) {
425   - this.edgeService.findMissingToRelatedRuleChains(this.config.componentsData.edgeId).subscribe(
426   - (missingRuleChains) => {
427   - if (missingRuleChains && Object.keys(missingRuleChains).length > 0) {
428   - const formattedMissingRuleChains: Array<string> = new Array<string>();
429   - for (const missingRuleChain of Object.keys(missingRuleChains)) {
430   - const arrayOfMissingRuleChains = missingRuleChains[missingRuleChain];
431   - const tmp = '- \'' + missingRuleChain + '\': \'' + arrayOfMissingRuleChains.join('\', ') + '\'';
432   - formattedMissingRuleChains.push(tmp);
433   - }
434   - const message = this.translate.instant('edge.missing-related-rule-chains-text',
435   - {missingRuleChains: formattedMissingRuleChains.join('<br>')});
436   - this.dialogService.alert(this.translate.instant('edge.missing-related-rule-chains-title'),
437   - message, this.translate.instant('action.close'), true).subscribe(
438   - () => {
439   - this.config.table.updateData();
  424 + if (res) {
  425 + this.edgeService.findMissingToRelatedRuleChains(this.config.componentsData.edgeId).subscribe(
  426 + (missingRuleChains) => {
  427 + if (missingRuleChains && Object.keys(missingRuleChains).length > 0) {
  428 + const formattedMissingRuleChains: Array<string> = new Array<string>();
  429 + for (const missingRuleChain of Object.keys(missingRuleChains)) {
  430 + const arrayOfMissingRuleChains = missingRuleChains[missingRuleChain];
  431 + const tmp = '- \'' + missingRuleChain + '\': \'' + arrayOfMissingRuleChains.join('\', ') + '\'';
  432 + formattedMissingRuleChains.push(tmp);
440 433 }
441   - );
442   - } else {
443   - this.config.table.updateData();
  434 + const message = this.translate.instant('edge.missing-related-rule-chains-text',
  435 + {missingRuleChains: formattedMissingRuleChains.join('<br>')});
  436 + this.dialogService.alert(this.translate.instant('edge.missing-related-rule-chains-title'),
  437 + message, this.translate.instant('action.close'), true).subscribe(
  438 + () => {
  439 + this.config.table.updateData();
  440 + }
  441 + );
  442 + } else {
  443 + this.config.table.updateData();
  444 + }
444 445 }
445   - }
446   - );
  446 + );
  447 + }
447 448 }
448   - }
449   - );
  449 + );
450 450 }
451 451
452 452 unassignFromEdge($event: Event, ruleChain: RuleChain) {
... ... @@ -510,13 +510,13 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
510 510 this.translate.instant('action.yes'),
511 511 true
512 512 ).subscribe((res) => {
513   - if (res) {
514   - this.ruleChainService.setAutoAssignToEdgeRuleChain(ruleChain.id.id).subscribe(
515   - () => {
516   - this.config.table.updateData();
517   - }
518   - );
519   - }
  513 + if (res) {
  514 + this.ruleChainService.setAutoAssignToEdgeRuleChain(ruleChain.id.id).subscribe(
  515 + () => {
  516 + this.config.table.updateData();
  517 + }
  518 + );
  519 + }
520 520 }
521 521 );
522 522 }
... ...
... ... @@ -73,9 +73,11 @@ export class FileInputComponent extends PageComponent implements AfterViewInit,
73 73 contentConvertFunction: (content: string) => any;
74 74
75 75 private requiredValue: boolean;
  76 +
76 77 get required(): boolean {
77 78 return this.requiredValue;
78 79 }
  80 +
79 81 @Input()
80 82 set required(value: boolean) {
81 83 const newVal = coerceBooleanProperty(value);
... ... @@ -85,9 +87,11 @@ export class FileInputComponent extends PageComponent implements AfterViewInit,
85 87 }
86 88
87 89 private requiredAsErrorValue: boolean;
  90 +
88 91 get requiredAsError(): boolean {
89 92 return this.requiredAsErrorValue;
90 93 }
  94 +
91 95 @Input()
92 96 set requiredAsError(value: boolean) {
93 97 const newVal = coerceBooleanProperty(value);
... ...
... ... @@ -22,7 +22,7 @@ import { ActionNotificationHide, ActionNotificationShow } from '@core/notificati
22 22 import { Store } from '@ngrx/store';
23 23 import { AppState } from '@core/core.state';
24 24 import { CancelAnimationFrame, RafService } from '@core/services/raf.service';
25   -import { guid, isUndefined, isDefinedAndNotNull, isLiteralObject } from '@core/utils';
  25 +import { guid, isDefinedAndNotNull, isLiteralObject, isUndefined } from '@core/utils';
26 26 import { ResizeObserver } from '@juggle/resize-observer';
27 27 import { getAce } from '@shared/models/ace/ace.models';
28 28
... ...
... ... @@ -19,6 +19,7 @@ import { TenantId } from '@shared/models/id/tenant-id';
19 19 import { EntityId } from '@shared/models/id/entity-id';
20 20 import { EventId } from './id/event-id';
21 21 import { ContentType } from '@shared/models/constants';
  22 +import { EntityType } from '@shared/models/entity-type.models';
22 23
23 24 export enum EventType {
24 25 ERROR = 'ERROR',
... ... @@ -83,3 +84,36 @@ export interface Event extends BaseData<EventId> {
83 84 uid: string;
84 85 body: EventBody;
85 86 }
  87 +
  88 +export interface BaseFilterEventBody {
  89 + server?: string;
  90 +}
  91 +
  92 +export interface ErrorFilterEventBody extends BaseFilterEventBody {
  93 + method?: string;
  94 +}
  95 +
  96 +export interface LcFilterEventEventBody extends BaseFilterEventBody {
  97 + method?: string;
  98 + status?: string;
  99 + isError?: boolean;
  100 +}
  101 +
  102 +export interface StatsFilterEventBody extends BaseFilterEventBody {
  103 + messagesProcessed?: string;
  104 + errorsOccurred?: string;
  105 +}
  106 +
  107 +export interface DebugFilterRuleNodeEventBody extends BaseFilterEventBody {
  108 + msgDirectionType?: string;
  109 + entityId?: string;
  110 + entityName?: EntityType;
  111 + msgId?: string;
  112 + msgType?: string;
  113 + relationType?: string;
  114 + dataSearch?: string;
  115 + metadataSearch?: string;
  116 + isError?: boolean;
  117 +}
  118 +
  119 +export type FilterEventBody = ErrorFilterEventBody & LcFilterEventEventBody & StatsFilterEventBody & DebugFilterRuleNodeEventBody;
... ...
... ... @@ -45,7 +45,7 @@ export const ResourceTypeTranslationMap = new Map<ResourceType, string>(
45 45 [ResourceType.LWM2M_MODEL, 'LWM2M model'],
46 46 [ResourceType.PKCS_12, 'PKCS #12'],
47 47 [ResourceType.JKS, 'JKS']
48   - ]
  48 + ]
49 49 );
50 50
51 51 export interface ResourceInfo extends BaseData<TbResourceId> {
... ...
... ... @@ -1332,7 +1332,6 @@
1332 1332 "body": "Tělo",
1333 1333 "method": "Metoda",
1334 1334 "type": "Typ",
1335   - "entity": "Entita",
1336 1335 "message-id": "Id zprávy",
1337 1336 "message-type": "Typ zprávy",
1338 1337 "data-type": "Typ dat",
... ... @@ -1344,7 +1343,9 @@
1344 1343 "success": "Úspěch",
1345 1344 "failed": "Neúspěch",
1346 1345 "messages-processed": "Zpracované zprávy",
1347   - "errors-occurred": "Vyskytly se chyby"
  1346 + "errors-occurred": "Vyskytly se chyby",
  1347 + "all-events": "Vše",
  1348 + "entity-type": "Typ entity"
1348 1349 },
1349 1350 "extension": {
1350 1351 "extensions": "Rozšíření",
... ...
... ... @@ -1094,7 +1094,6 @@
1094 1094 "body": "Inhalt",
1095 1095 "method": "Methode",
1096 1096 "type": "Typ",
1097   - "entity": "Entität",
1098 1097 "message-id": "Nachrichten-Id",
1099 1098 "message-type": "Nachrichten-Typ",
1100 1099 "data-type": "Datentyp",
... ... @@ -1106,7 +1105,9 @@
1106 1105 "success": "Erfolg",
1107 1106 "failed": "Fehlgeschlagen",
1108 1107 "messages-processed": "Nachrichten verarbeitet",
1109   - "errors-occurred": "Fehler aufgetreten"
  1108 + "errors-occurred": "Fehler aufgetreten",
  1109 + "all-events": "Alle",
  1110 + "entity-type": "Entitätstyp"
1110 1111 },
1111 1112 "extension": {
1112 1113 "extensions": "Erweiterungen",
... ...
... ... @@ -1247,7 +1247,6 @@
1247 1247 "out": "Έξοδος",
1248 1248 "metadata": "Μεταδεδομένα",
1249 1249 "message": "Μήνυμα",
1250   - "entity": "Οντότητα",
1251 1250 "message-id": "ID Μηνύματος",
1252 1251 "message-type": "Τύπος Μηνύματος",
1253 1252 "data-type": "Τύπος Δεδομένων",
... ... @@ -1258,7 +1257,9 @@
1258 1257 "success": "Επιτυχία",
1259 1258 "failed": "Απέτυχε",
1260 1259 "messages-processed": "Επεξεργασμένα μηνύματα",
1261   - "errors-occurred": "Παρουσιάστηκαν σφάλματα"
  1260 + "errors-occurred": "Παρουσιάστηκαν σφάλματα",
  1261 + "all-events": "Όλοι",
  1262 + "entity-type": "Τύπος οντοτήτων"
1262 1263 },
1263 1264 "extension": {
1264 1265 "extensions": "Επεκτάσεις",
... ...
... ... @@ -1616,6 +1616,7 @@
1616 1616 },
1617 1617 "event": {
1618 1618 "event-type": "Event type",
  1619 + "events-filter": "Events Filter",
1619 1620 "type-error": "Error",
1620 1621 "type-lc-event": "Lifecycle event",
1621 1622 "type-stats": "Statistics",
... ... @@ -1629,7 +1630,6 @@
1629 1630 "body": "Body",
1630 1631 "method": "Method",
1631 1632 "type": "Type",
1632   - "entity": "Entity",
1633 1633 "message-id": "Message Id",
1634 1634 "message-type": "Message Type",
1635 1635 "data-type": "Data Type",
... ... @@ -1641,7 +1641,11 @@
1641 1641 "success": "Success",
1642 1642 "failed": "Failed",
1643 1643 "messages-processed": "Messages processed",
1644   - "errors-occurred": "Errors occurred"
  1644 + "errors-occurred": "Errors occurred",
  1645 + "all-events": "All",
  1646 + "has-error": "Has error",
  1647 + "entity-id": "Entity Id",
  1648 + "entity-type": "Entity type"
1645 1649 },
1646 1650 "extension": {
1647 1651 "extensions": "Extensions",
... ...
... ... @@ -1521,7 +1521,6 @@
1521 1521 "body": "Cuerpo",
1522 1522 "method": "Método",
1523 1523 "type": "Tipo",
1524   - "entity": "Entidad",
1525 1524 "message-id": "Id Mensaje",
1526 1525 "message-type": "Tipo Mensaje",
1527 1526 "data-type": "Tipo de Datos",
... ... @@ -1533,7 +1532,9 @@
1533 1532 "success": "Éxito",
1534 1533 "failed": "Fallo",
1535 1534 "messages-processed": "Mensajes procesados",
1536   - "errors-occurred": "Ocurrieron errores"
  1535 + "errors-occurred": "Ocurrieron errores",
  1536 + "all-events": "Todos",
  1537 + "entity-type": "Tipo de entidad"
1537 1538 },
1538 1539 "extension": {
1539 1540 "extensions": "Extensiones",
... ...
... ... @@ -881,7 +881,6 @@
881 881 "body": "بدنه",
882 882 "method": "روش",
883 883 "type": "نوع",
884   - "entity": "موجودي",
885 884 "message-id": "پيام ID",
886 885 "message-type": "نوع پيام",
887 886 "data-type": "نوع داده",
... ... @@ -893,7 +892,9 @@
893 892 "success": "موفقيت",
894 893 "failed": "عدم موفقيت",
895 894 "messages-processed": "پيام پردازش شد",
896   - "errors-occurred": "خطاها رخ دادند"
  895 + "errors-occurred": "خطاها رخ دادند",
  896 + "all-events": "همه",
  897 + "entity-type": "نوع موجودي"
897 898 },
898 899 "extension": {
899 900 "extensions": "دنباله ها",
... ...
... ... @@ -1115,7 +1115,6 @@
1115 1115 "body": "Corps",
1116 1116 "data": "Données",
1117 1117 "data-type": "Type de données",
1118   - "entity": "Entité",
1119 1118 "error": "erreur",
1120 1119 "type-edge-event": "Downlink",
1121 1120 "errors-occurred": "Des erreurs sont survenues",
... ... @@ -1138,7 +1137,9 @@
1138 1137 "type-debug-rule-node": "Debug",
1139 1138 "type-error": "Erreur",
1140 1139 "type-lc-event": "Evénement du cycle de vie",
1141   - "type-stats": "Statistiques"
  1140 + "type-stats": "Statistiques",
  1141 + "all-events": "Tout",
  1142 + "entity-type": "Type d'entité"
1142 1143 },
1143 1144 "extension": {
1144 1145 "add": "Ajouter une extension",
... ...
... ... @@ -915,7 +915,6 @@
915 915 "body": "Body",
916 916 "method": "Metodo",
917 917 "type": "Tipo",
918   - "entity": "Entità",
919 918 "message-id": "Id Messaggio",
920 919 "message-type": "Tipo Messaggio",
921 920 "data-type": "Tipo di dato",
... ... @@ -927,7 +926,9 @@
927 926 "success": "Success",
928 927 "failed": "Failed",
929 928 "messages-processed": "Messaggi elaborati",
930   - "errors-occurred": "Si sono verificati degli errori"
  929 + "errors-occurred": "Si sono verificati degli errori",
  930 + "all-events": "Tutte",
  931 + "entity-type": "Tipo entità"
931 932 },
932 933 "extension": {
933 934 "extensions": "Estensioni",
... ...
... ... @@ -768,7 +768,6 @@
768 768 "body": "体",
769 769 "method": "方法",
770 770 "type": "タイプ",
771   - "entity": "エンティティ",
772 771 "message-id": "メッセージID",
773 772 "message-type": "メッセージタイプ",
774 773 "data-type": "データ・タイプ",
... ... @@ -780,7 +779,9 @@
780 779 "success": "成功",
781 780 "failed": "失敗",
782 781 "messages-processed": "処理されたメッセージ",
783   - "errors-occurred": "エラーが発生しました"
  782 + "errors-occurred": "エラーが発生しました",
  783 + "all-events": "すべて",
  784 + "entity-type": "エンティティタイプ"
784 785 },
785 786 "extension": {
786 787 "extensions": "拡張機能",
... ...
... ... @@ -959,7 +959,6 @@
959 959 "body": "სხეული",
960 960 "method": "მეთოდი",
961 961 "type": "ტიპი",
962   - "entity": "ობიექტი",
963 962 "message-id": "მესიჯის-ID",
964 963 "message-type": "მესიჯის ტიპი",
965 964 "data-type": "მონაცემთა ტიპი",
... ... @@ -971,7 +970,9 @@
971 970 "success": "წარმატება",
972 971 "failed": "ვერ მოხერხდა",
973 972 "messages-processed": "შეტყობინებების დამუშავება",
974   - "errors-occurred": "შეცდომები მოხდა"
  973 + "errors-occurred": "შეცდომები მოხდა",
  974 + "all-events": "ყველა",
  975 + "entity-type": "ობიექტის ტიპი"
975 976 },
976 977 "extension": {
977 978 "extensions": "დამატებითი აპი",
... ...
... ... @@ -1329,7 +1329,6 @@
1329 1329 "body": "Body",
1330 1330 "method": "방법",
1331 1331 "type": "유형",
1332   - "entity": "개체",
1333 1332 "message-id": "메시지 ID",
1334 1333 "message-type": "메시지 유형",
1335 1334 "data-type": "데이터 유형",
... ... @@ -1341,7 +1340,9 @@
1341 1340 "success": "성공",
1342 1341 "failed": "실패",
1343 1342 "messages-processed": "처리된 메시지",
1344   - "errors-occurred": "오류가 발생했습니다"
  1343 + "errors-occurred": "오류가 발생했습니다",
  1344 + "all-events": "모두",
  1345 + "entity-type": "개체 유형"
1345 1346 },
1346 1347 "extension": {
1347 1348 "extensions": "확장",
... ...
... ... @@ -894,7 +894,6 @@
894 894 "body": "Galvenā daļa",
895 895 "method": "Metode",
896 896 "type": "Tips",
897   - "entity": "Vienība",
898 897 "message-id": "Ziņojuma Id",
899 898 "message-type": "Ziņojuma tips",
900 899 "data-type": "Datu tips",
... ... @@ -906,7 +905,9 @@
906 905 "success": "Sekmīgi",
907 906 "failed": "Kļūda",
908 907 "messages-processed": "Ziņojumi apstrādāti",
909   - "errors-occurred": "Kļūdas konstatētas"
  908 + "errors-occurred": "Kļūdas konstatētas",
  909 + "all-events": "Visi",
  910 + "entity-type": "Vienības tips"
910 911 },
911 912 "extension": {
912 913 "extensions": "Paplašinājumi",
... ...
... ... @@ -997,7 +997,6 @@
997 997 "body": "Corpo",
998 998 "method": "Método",
999 999 "type": "Tipo",
1000   - "entity": "Entidade",
1001 1000 "message-id": "ID de mensagem",
1002 1001 "message-type": "Tipo de Mensagem",
1003 1002 "data-type": "Tipo de Dados",
... ... @@ -1009,7 +1008,9 @@
1009 1008 "success": "Êxito",
1010 1009 "failed": "Falhou",
1011 1010 "messages-processed": "Mensagens processadas",
1012   - "errors-occurred": "Erros"
  1011 + "errors-occurred": "Erros",
  1012 + "all-events": "Tudo",
  1013 + "entity-type": "Tipo de entidade"
1013 1014 },
1014 1015 "extension": {
1015 1016 "extensions": "Extensões",
... ...
... ... @@ -945,7 +945,6 @@
945 945 "body": "Corp",
946 946 "method": "Metodă",
947 947 "type": "Tip",
948   - "entity": "Entitate",
949 948 "message-id": "ID Mesaj",
950 949 "message-type": "Tip Mesaj",
951 950 "data-type": "Tip Date",
... ... @@ -957,7 +956,9 @@
957 956 "success": "Succes",
958 957 "failed": "Eşuat",
959 958 "messages-processed": "Mesaje procesate",
960   - "errors-occurred": "Au apărut erori"
  959 + "errors-occurred": "Au apărut erori",
  960 + "all-events": "Toate",
  961 + "entity-type": "Tip Entitate"
961 962 },
962 963 "extension": {
963 964 "extensions": "Extensii",
... ... @@ -1174,7 +1175,7 @@
1174 1175 "entity-field": "Câmp Entitate",
1175 1176 "access-token": "Token De Acces"
1176 1177 },
1177   - "stepper-text":{
  1178 + "stepper-text": {
1178 1179 "select-file": "Selectează un fişier",
1179 1180 "configuration": "Importă configurație",
1180 1181 "column-type": "Selectează tipul de coloane",
... ... @@ -1796,4 +1797,4 @@
1796 1797 "language": {
1797 1798 "language": "Limba"
1798 1799 }
1799   -}
  1800 +}
\ No newline at end of file
... ...
... ... @@ -948,7 +948,6 @@
948 948 "body": "Тело",
949 949 "method": "Метод",
950 950 "type": "Тип",
951   - "entity": "Объект",
952 951 "message-id": "ИД сообщения",
953 952 "message-type": "Тип сообщения",
954 953 "data-type": "Тип данных",
... ... @@ -960,7 +959,9 @@
960 959 "success": "Успех",
961 960 "failed": "Неудача",
962 961 "messages-processed": "Сообщения обработаны",
963   - "errors-occurred": "Возникли ошибки"
  962 + "errors-occurred": "Возникли ошибки",
  963 + "all-events": "Все",
  964 + "entity-type": "Тип объекта"
964 965 },
965 966 "extension": {
966 967 "extensions": "Расширение",
... ...
... ... @@ -1329,7 +1329,6 @@
1329 1329 "body": "Vsebina",
1330 1330 "method": "Metoda",
1331 1331 "type": "Vrsta",
1332   - "entity": "Entiteta",
1333 1332 "message-id": "ID sporočila",
1334 1333 "message-type": "Vrsta sporočila",
1335 1334 "data-type": "Vrsta podatkov",
... ... @@ -1341,7 +1340,9 @@
1341 1340 "success": "Uspeh",
1342 1341 "failed": "Ni uspelo",
1343 1342 "messages-processed": "Obdelana sporočila",
1344   - "errors-occurred": "Prišlo je do napak"
  1343 + "errors-occurred": "Prišlo je do napak",
  1344 + "all-events": "Vse",
  1345 + "entity-type": "Vrsta entitete"
1345 1346 },
1346 1347 "extension": {
1347 1348 "extensions": "Razširitve",
... ...
... ... @@ -915,7 +915,6 @@
915 915 "body": "İçerik //(Body)",
916 916 "method": "Yöntem",
917 917 "type": "Tür",
918   - "entity": "Varlık",
919 918 "message-id": "Mesaj Kimliği",
920 919 "message-type": "Mesaj tipi",
921 920 "data-type": "Veri tipi",
... ... @@ -927,7 +926,9 @@
927 926 "success": "Başarı",
928 927 "failed": "Başarısız oldu",
929 928 "messages-processed": "Mesajlar işlendi",
930   - "errors-occurred": "Hatalar oluştu"
  929 + "errors-occurred": "Hatalar oluştu",
  930 + "all-events": "Tümü",
  931 + "entity-type": "Öğe türü"
931 932 },
932 933 "extension": {
933 934 "extensions": "Uzantılar",
... ...
... ... @@ -1201,7 +1201,6 @@
1201 1201 "out": "Out",
1202 1202 "metadata": "Метадані",
1203 1203 "message": "Повідомлення",
1204   - "entity": "Сутність",
1205 1204 "message-id": "Id повідомлення",
1206 1205 "message-type": "Тип повідомлення",
1207 1206 "data-type": "Тип даних",
... ... @@ -1212,7 +1211,9 @@
1212 1211 "success": "Успіх",
1213 1212 "failed": "Невдача",
1214 1213 "messages-processed": "Повідомлення опрацьовані",
1215   - "errors-occurred": "Виникли помилки"
  1214 + "errors-occurred": "Виникли помилки",
  1215 + "all-events": "Всі",
  1216 + "entity-type": "Тип сутності"
1216 1217 },
1217 1218 "extension": {
1218 1219 "extensions": "Розширення",
... ...
... ... @@ -1334,7 +1334,6 @@
1334 1334 "body": "整体",
1335 1335 "data": "数据",
1336 1336 "data-type": "数据类型",
1337   - "entity": "实体",
1338 1337 "error": "错误",
1339 1338 "errors-occurred": "错误发生",
1340 1339 "event": "事件",
... ... @@ -1356,7 +1355,9 @@
1356 1355 "type-debug-rule-node": "调试",
1357 1356 "type-error": "错误",
1358 1357 "type-lc-event": "生命周期事件",
1359   - "type-stats": "类型统计"
  1358 + "type-stats": "类型统计",
  1359 + "all-events": "全部",
  1360 + "entity-type": "实体类型"
1360 1361 },
1361 1362 "extension": {
1362 1363 "add": "添加扩展",
... ...
... ... @@ -865,7 +865,6 @@
865 865 "body": "整體",
866 866 "method": "方法",
867 867 "type": "類型",
868   - "entity": "實體",
869 868 "message-id": "消息ID",
870 869 "message-type": "消息類型",
871 870 "data-type": "資料類型",
... ... @@ -877,7 +876,9 @@
877 876 "success": "成功",
878 877 "failed": "失敗",
879 878 "messages-processed": "消息處理",
880   - "errors-occurred": "錯誤發生"
  879 + "errors-occurred": "錯誤發生",
  880 + "all-events": "所有",
  881 + "entity-type": "實體類型"
881 882 },
882 883 "extension": {
883 884 "extensions": "擴展",
... ...